Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.45% covered (success)
95.45%
42 / 44
83.33% covered (warning)
83.33%
5 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
DefaultOptionsLookup
97.67% covered (success)
97.67%
42 / 43
83.33% covered (warning)
83.33%
5 / 6
15
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 getGenericDefaultOptions
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
5
 getDefaultOptions
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 getOption
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getOptions
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 verifyUsable
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
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 */
20
21namespace MediaWiki\User\Options;
22
23use IDBAccessObject;
24use Language;
25use LanguageConverter;
26use MediaWiki\Config\ServiceOptions;
27use MediaWiki\HookContainer\HookContainer;
28use MediaWiki\HookContainer\HookRunner;
29use MediaWiki\MainConfigNames;
30use MediaWiki\Title\NamespaceInfo;
31use MediaWiki\User\UserIdentity;
32use Skin;
33use Wikimedia\Assert\Assert;
34
35/**
36 * A service class to control default user options
37 * @since 1.35
38 */
39class DefaultOptionsLookup extends UserOptionsLookup {
40
41    /**
42     * @internal For use by ServiceWiring
43     */
44    public const CONSTRUCTOR_OPTIONS = [
45        MainConfigNames::DefaultSkin,
46        MainConfigNames::DefaultUserOptions,
47        MainConfigNames::NamespacesToBeSearchedDefault
48    ];
49
50    private ServiceOptions $serviceOptions;
51    private Language $contentLang;
52    private NamespaceInfo $nsInfo;
53    private ConditionalDefaultsLookup $conditionalDefaultsLookup;
54
55    /** @var array|null Cached default options */
56    private $defaultOptions = null;
57
58    private HookRunner $hookRunner;
59
60    /**
61     * @param ServiceOptions $options
62     * @param Language $contentLang
63     * @param HookContainer $hookContainer
64     * @param NamespaceInfo $nsInfo
65     * @param ConditionalDefaultsLookup $conditionalUserOptionsDefaultsLookup
66     */
67    public function __construct(
68        ServiceOptions $options,
69        Language $contentLang,
70        HookContainer $hookContainer,
71        NamespaceInfo $nsInfo,
72        ConditionalDefaultsLookup $conditionalUserOptionsDefaultsLookup
73    ) {
74        $options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
75        $this->serviceOptions = $options;
76        $this->contentLang = $contentLang;
77        $this->hookRunner = new HookRunner( $hookContainer );
78        $this->nsInfo = $nsInfo;
79        $this->conditionalDefaultsLookup = $conditionalUserOptionsDefaultsLookup;
80    }
81
82    /**
83     * Get default user options from $wgDefaultUserOptions (ignoring any conditional defaults)
84     *
85     * @return array
86     */
87    private function getGenericDefaultOptions(): array {
88        if ( $this->defaultOptions !== null ) {
89            return $this->defaultOptions;
90        }
91
92        $this->defaultOptions = $this->serviceOptions->get( MainConfigNames::DefaultUserOptions );
93
94        // Default language setting
95        // NOTE: don't use the content language code since the static default variant would
96        //  NOT always be the same as the content language code.
97        $contentLangCode = $this->contentLang->getCode();
98        $LangsWithStaticDefaultVariant = LanguageConverter::$languagesWithStaticDefaultVariant;
99        $staticDefaultVariant = $LangsWithStaticDefaultVariant[$contentLangCode] ?? $contentLangCode;
100        $this->defaultOptions['language'] = $contentLangCode;
101        $this->defaultOptions['variant'] = $staticDefaultVariant;
102        foreach ( LanguageConverter::$languagesWithVariants as $langCode ) {
103            $staticDefaultVariant = $LangsWithStaticDefaultVariant[$langCode] ?? $langCode;
104            $this->defaultOptions["variant-$langCode"] = $staticDefaultVariant;
105        }
106
107        // NOTE: don't use SearchEngineConfig::getSearchableNamespaces here,
108        // since extensions may change the set of searchable namespaces depending
109        // on user groups/permissions.
110        $nsSearchDefault = $this->serviceOptions->get( MainConfigNames::NamespacesToBeSearchedDefault );
111        foreach ( $this->nsInfo->getValidNamespaces() as $n ) {
112            $this->defaultOptions['searchNs' . $n] = ( $nsSearchDefault[$n] ?? false ) ? 1 : 0;
113        }
114        $this->defaultOptions['skin'] = Skin::normalizeKey(
115            $this->serviceOptions->get( MainConfigNames::DefaultSkin ) );
116
117        $this->hookRunner->onUserGetDefaultOptions( $this->defaultOptions );
118
119        return $this->defaultOptions;
120    }
121
122    /**
123     * @inheritDoc
124     */
125    public function getDefaultOptions( ?UserIdentity $userIdentity = null ): array {
126        $defaultOptions = $this->getGenericDefaultOptions();
127
128        // If requested, process any conditional defaults
129        if ( $userIdentity ) {
130            $conditionallyDefaultOptions = $this->conditionalDefaultsLookup->getConditionallyDefaultOptions();
131            foreach ( $conditionallyDefaultOptions as $optionName ) {
132                $conditionalDefault = $this->conditionalDefaultsLookup->getOptionDefaultForUser(
133                    $optionName, $userIdentity
134                );
135                if ( $conditionalDefault !== null ) {
136                    $defaultOptions[$optionName] = $conditionalDefault;
137                }
138            }
139        }
140
141        return $defaultOptions;
142    }
143
144    /**
145     * @inheritDoc
146     */
147    public function getOption(
148        UserIdentity $user,
149        string $oname,
150        $defaultOverride = null,
151        bool $ignoreHidden = false,
152        int $queryFlags = IDBAccessObject::READ_NORMAL
153    ) {
154        $this->verifyUsable( $user, __METHOD__ );
155        return $this->getDefaultOption( $oname ) ?? $defaultOverride;
156    }
157
158    /**
159     * @inheritDoc
160     */
161    public function getOptions(
162        UserIdentity $user,
163        int $flags = 0,
164        int $queryFlags = IDBAccessObject::READ_NORMAL
165    ): array {
166        $this->verifyUsable( $user, __METHOD__ );
167        if ( $flags & self::EXCLUDE_DEFAULTS ) {
168            return [];
169        }
170        return $this->getDefaultOptions();
171    }
172
173    /**
174     * Checks if the DefaultOptionsLookup is usable as an instance of UserOptionsLookup.
175     *
176     * It only makes sense in an installer context when UserOptionsManager cannot be yet instantiated
177     * as the database is not available. Thus, this can only be called for an anon user,
178     * calling under different circumstances indicates a bug, or that a system user is being used.
179     *
180     * The only exception to this is database-less PHPUnit tests, where sometimes fake registered users are
181     * used and end up being passed to this class. This should not be considered a bug, and using the default
182     * preferences in this scenario is probably the intended behaviour.
183     *
184     * @param UserIdentity $user
185     * @param string $fname
186     */
187    private function verifyUsable( UserIdentity $user, string $fname ) {
188        if ( defined( 'MEDIAWIKI_INSTALL' ) ) {
189            return;
190        }
191        Assert::precondition( !$user->isRegistered(), "$fname called on a registered user" );
192    }
193}
194
195/** @deprecated class alias since 1.41 */
196class_alias( DefaultOptionsLookup::class, 'MediaWiki\\User\\DefaultOptionsLookup' );