Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
18.97% covered (danger)
18.97%
11 / 58
50.00% covered (danger)
50.00%
3 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
SkinOptions
18.97% covered (danger)
18.97%
11 / 58
50.00% covered (danger)
50.00%
3 / 6
190.41
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 setMultiple
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
3.14
 get
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 getAll
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasSkinOptions
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 setMinervaSkinOptions
0.00% covered (danger)
0.00%
0 / 44
0.00% covered (danger)
0.00%
0 / 1
72
1<?php
2/**
3 * This program is free software; you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation; either version 2 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License along
14 * with this program; if not, write to the Free Software Foundation, Inc.,
15 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
16 * http://www.gnu.org/copyleft/gpl.html
17 *
18 * @file
19 */
20namespace MediaWiki\Minerva;
21
22use MediaWiki\HookContainer\HookContainer;
23use MediaWiki\MediaWikiServices;
24use MediaWiki\Minerva\Hooks\HookRunner;
25use MediaWiki\Minerva\Skins\SkinMinerva;
26use MediaWiki\Minerva\Skins\SkinUserPageHelper;
27use MobileContext;
28use Skin;
29
30/**
31 * A wrapper for all available Skin options.
32 */
33final class SkinOptions {
34
35    public const MOBILE_OPTIONS = 'mobileOptionsLink';
36    public const CATEGORIES = 'categories';
37    public const PAGE_ISSUES = 'pageIssues';
38    public const BETA_MODE = 'beta';
39    public const TALK_AT_TOP = 'talkAtTop';
40    public const SHOW_DONATE = 'donate';
41    public const HISTORY_IN_PAGE_ACTIONS = 'historyInPageActions';
42    public const TOOLBAR_SUBMENU = 'overflowSubmenu';
43    public const TABS_ON_SPECIALS = 'tabsOnSpecials';
44    public const MAIN_MENU_EXPANDED = 'mainMenuExpanded';
45    public const PERSONAL_MENU = 'personalMenu';
46    public const SINGLE_ECHO_BUTTON = 'echo';
47    public const NIGHT_MODE = 'nightMode';
48
49    /**
50     * Note stable skin options default to true for desktop-Minerva and are expected to be
51     * overridden on mobile.
52     * @var array skin specific options, initialized with default values
53     */
54    private $skinOptions = [
55        self::BETA_MODE => false,
56        self::SHOW_DONATE => true,
57        /**
58         * Whether the main menu should include a link to
59         * Special:Preferences of Special:MobileOptions
60         */
61        self::MOBILE_OPTIONS => false,
62        /** Whether a categories button should appear at the bottom of the skin. */
63        self::CATEGORIES => false,
64        /** requires a wiki using Template:Ambox */
65        self::PAGE_ISSUES => false,
66        /** no extension requirements */
67        self::TALK_AT_TOP => true,
68        /** no extension requirements */
69        self::HISTORY_IN_PAGE_ACTIONS => true,
70        /** no extension requirements */
71        self::TOOLBAR_SUBMENU => true,
72        /** Whether to show tabs on special pages */
73        self::TABS_ON_SPECIALS => true,
74        /** whether to show a personal menu */
75        self::PERSONAL_MENU => true,
76        /** whether to show a main menu with additional items */
77        self::MAIN_MENU_EXPANDED => true,
78        /** whether Echo should be replaced with a single button */
79        self::SINGLE_ECHO_BUTTON => false,
80        /** whether night mode is available to the user */
81        self::NIGHT_MODE => false,
82    ];
83
84    private HookContainer $hookContainer;
85    private SkinUserPageHelper $skinUserPageHelper;
86
87    public function __construct(
88        HookContainer $hookContainer,
89        SkinUserPageHelper $skinUserPageHelper
90    ) {
91        $this->hookContainer = $hookContainer;
92        $this->skinUserPageHelper = $skinUserPageHelper;
93    }
94
95    /**
96     * override an existing option or options with new values
97     * @param array $options
98     */
99    public function setMultiple( array $options ) {
100        foreach ( $options as $option => $value ) {
101            if ( !array_key_exists( $option, $this->skinOptions ) ) {
102                throw new \OutOfBoundsException( "SkinOption $option is not defined" );
103            }
104        }
105        $this->skinOptions = array_merge( $this->skinOptions, $options );
106    }
107
108    /**
109     * Return whether a skin option is truthy. Should be one of self:* constants
110     * @param string $key
111     * @return bool
112     */
113    public function get( $key ) {
114        if ( !array_key_exists( $key, $this->skinOptions ) ) {
115            throw new \OutOfBoundsException( "SkinOption $key doesn't exist" );
116        }
117        return $this->skinOptions[$key];
118    }
119
120    /**
121     * Get all skin options
122     * @return array
123     */
124    public function getAll() {
125        return $this->skinOptions;
126    }
127
128    /**
129     * Return whether any of the skin options have been set
130     * @return bool
131     */
132    public function hasSkinOptions() {
133        foreach ( $this->skinOptions as $key => $val ) {
134            if ( $val ) {
135                return true;
136            }
137        }
138        return false;
139    }
140
141    /**
142     * Set the skin options for Minerva
143     *
144     * @param MobileContext $mobileContext
145     * @param Skin $skin
146     */
147    public function setMinervaSkinOptions(
148        MobileContext $mobileContext, Skin $skin
149    ) {
150        // setSkinOptions is not available
151        if ( $skin instanceof SkinMinerva ) {
152            $services = MediaWikiServices::getInstance();
153            $featureManager = $services
154                ->getService( 'MobileFrontend.FeaturesManager' );
155            $title = $skin->getTitle();
156
157            // T245162 - this should only apply if the context relates to a page view.
158            // Examples:
159            // - parsing wikitext during an REST response
160            // - a ResourceLoader response
161            if ( $title !== null ) {
162                // T232653: TALK_AT_TOP, HISTORY_IN_PAGE_ACTIONS, TOOLBAR_SUBMENU should
163                // be true on user pages and user talk pages for all users
164                $this->skinUserPageHelper
165                    ->setContext( $mobileContext )
166                    ->setTitle(
167                        $title->inNamespace( NS_USER_TALK ) ? $title->getSubjectPage() : $title
168                    );
169
170                $isUserPage = $this->skinUserPageHelper->isUserPage();
171                $isUserPageAccessible = $this->skinUserPageHelper->isUserPageAccessibleToCurrentUser();
172                $isUserPageOrUserTalkPage = $isUserPage && $isUserPageAccessible;
173            } else {
174                // If no title this must be false
175                $isUserPageOrUserTalkPage = false;
176            }
177
178            $isBeta = $mobileContext->isBetaGroupMember();
179            $this->setMultiple( [
180                self::SHOW_DONATE => $featureManager->isFeatureAvailableForCurrentUser( 'MinervaDonateLink' ),
181                self::TALK_AT_TOP => $isUserPageOrUserTalkPage ?
182                    true : $featureManager->isFeatureAvailableForCurrentUser( 'MinervaTalkAtTop' ),
183                self::BETA_MODE
184                    => $isBeta,
185                self::CATEGORIES
186                    => $featureManager->isFeatureAvailableForCurrentUser( 'MinervaShowCategories' ),
187                self::PAGE_ISSUES
188                    => $featureManager->isFeatureAvailableForCurrentUser( 'MinervaPageIssuesNewTreatment' ),
189                self::MOBILE_OPTIONS => true,
190                self::PERSONAL_MENU => $featureManager->isFeatureAvailableForCurrentUser(
191                    'MinervaPersonalMenu'
192                ),
193                self::MAIN_MENU_EXPANDED => $featureManager->isFeatureAvailableForCurrentUser(
194                    'MinervaAdvancedMainMenu'
195                ),
196                // In mobile, always resort to single icon.
197                self::SINGLE_ECHO_BUTTON => true,
198                self::HISTORY_IN_PAGE_ACTIONS => $isUserPageOrUserTalkPage ?
199                    true : $featureManager->isFeatureAvailableForCurrentUser( 'MinervaHistoryInPageActions' ),
200                self::TOOLBAR_SUBMENU => $isUserPageOrUserTalkPage ?
201                    true : $featureManager->isFeatureAvailableForCurrentUser(
202                        Hooks::FEATURE_OVERFLOW_PAGE_ACTIONS
203                    ),
204                self::TABS_ON_SPECIALS => true,
205                self::NIGHT_MODE => $featureManager->isFeatureAvailableForCurrentUser( 'MinervaNightMode' ),
206            ] );
207            ( new HookRunner( $this->hookContainer ) )->onSkinMinervaOptionsInit( $skin, $this );
208        }
209    }
210}