Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
84.62% covered (warning)
84.62%
22 / 26
57.14% covered (warning)
57.14%
4 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
GrowthExperimentsMultiConfig
84.62% covered (warning)
84.62%
22 / 26
57.14% covered (warning)
57.14%
4 / 7
16.93
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
 variableIsAllowed
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isWikiConfigEnabled
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 get
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getWithFlags
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
7
 has
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 hasWithFlags
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
4.07
1<?php
2
3namespace GrowthExperiments\Config;
4
5use MediaWiki\Config\Config;
6use MediaWiki\Config\ConfigException;
7use MediaWiki\Settings\Config\MergeStrategy;
8
9/**
10 * Config loader for wiki page config
11 *
12 * This class consults the allow list
13 * in GrowthExperimentsMultiConfig::ALLOW_LIST, and runs
14 * WikiPageConfig if requested config variable is there. Otherwise,
15 * it throws an exception.
16 *
17 * Fallback to GlobalVarConfig is implemented, so developer setup
18 * works without any config page, and also to not let wikis break
19 * GE setup by removing an arbitrary config variable.
20 */
21class GrowthExperimentsMultiConfig implements Config, ICustomReadConstants {
22
23    private WikiPageConfig $wikiPageConfig;
24    private Config $globalVarConfig;
25
26    // This should be in sync with SpecialEditGrowthConfig::getFormFields
27    public const ALLOW_LIST = [
28        'GEHelpPanelReadingModeNamespaces',
29        'GEHelpPanelExcludedNamespaces',
30        'GEHelpPanelHelpDeskTitle',
31        'GEHelpPanelHelpDeskPostOnTop',
32        'GEHelpPanelViewMoreTitle',
33        'GEHelpPanelSearchNamespaces',
34        'GEHelpPanelLinks',
35        'GEHelpPanelAskMentor',
36        'GEMentorshipEnabled',
37        'GEHomepageSuggestedEditsIntroLinks',
38        'GEInfoboxTemplates',
39        'GEInfoboxTemplatesTest',
40        'GECampaigns',
41        'GECampaignTopics',
42        'GEMentorshipAutomaticEligibility',
43        'GEMentorshipMinimumAge',
44        'GEMentorshipMinimumEditcount',
45        'GEPersonalizedPraiseDefaultNotificationsFrequency',
46        'GEPersonalizedPraiseDays',
47        'GEPersonalizedPraiseMinEdits',
48        'GEPersonalizedPraiseMaxEdits',
49        'GEPersonalizedPraiseMaxReverts',
50        'GELevelingUpGetStartedMaxTotalEdits',
51        'GELevelingUpKeepGoingNotificationThresholds'
52    ];
53
54    /**
55     * Map of variable name => merge strategy. Defaults to replace.
56     * @see MergeStrategy
57     */
58    public const MERGE_STRATEGIES = [
59        'GECampaigns' => 'array_merge',
60    ];
61
62    /**
63     * @param WikiPageConfig $wikiPageConfig
64     * @param Config $globalVarConfig
65     */
66    public function __construct(
67        WikiPageConfig $wikiPageConfig,
68        Config $globalVarConfig
69    ) {
70        $this->wikiPageConfig = $wikiPageConfig;
71        $this->globalVarConfig = $globalVarConfig;
72    }
73
74    /**
75     * @param string $name
76     * @return bool
77     */
78    private function variableIsAllowed( $name ) {
79        return in_array( $name, self::ALLOW_LIST );
80    }
81
82    /**
83     * Determine if on-wiki config is enabled or not
84     *
85     * If this returns false, all calls to get()/has() will be immediately
86     * forwarded to GlobalVarConfig, as if there was no on-wiki config.
87     *
88     * @return bool
89     */
90    public function isWikiConfigEnabled(): bool {
91        return (bool)$this->globalVarConfig->get( 'GEWikiConfigEnabled' );
92    }
93
94    /**
95     * @inheritDoc
96     */
97    public function get( $name ) {
98        return $this->getWithFlags( $name );
99    }
100
101    /**
102     * @param string $name
103     * @param int $flags bit field, see IDBAccessObject::READ_XXX
104     * @return mixed Config value
105     */
106    public function getWithFlags( $name, int $flags = 0 ) {
107        if ( !$this->isWikiConfigEnabled() ) {
108            return $this->globalVarConfig->get( $name );
109        }
110
111        if ( !$this->variableIsAllowed( $name ) ) {
112            throw new ConfigException( 'Config key cannot be retrieved via GrowthExperimentsMultiConfig' );
113        }
114
115        if ( $this->wikiPageConfig->hasWithFlags( $name, $flags ) ) {
116            $wikiValue = $this->wikiPageConfig->getWithFlags( $name, $flags );
117            $mergeStrategy = self::MERGE_STRATEGIES[$name] ?? null;
118            if ( !$mergeStrategy || !$this->globalVarConfig->has( $name ) ) {
119                return $wikiValue;
120            }
121            $globalValue = $this->globalVarConfig->get( $name );
122            return MergeStrategy::newFromName( $mergeStrategy )->merge( $globalValue, $wikiValue );
123        } elseif ( $this->globalVarConfig->has( $name ) ) {
124            return $this->globalVarConfig->get( $name );
125        } else {
126            throw new ConfigException( 'Config key was not found in GrowthExperimentsMultiConfig' );
127        }
128    }
129
130    /**
131     * @inheritDoc
132     */
133    public function has( $name ) {
134        return $this->hasWithFlags( $name );
135    }
136
137    /**
138     * @param string $name
139     * @param int $flags
140     * @return bool
141     */
142    public function hasWithFlags( $name, int $flags = 0 ) {
143        if ( !$this->isWikiConfigEnabled() ) {
144            return $this->globalVarConfig->has( $name );
145        }
146
147        return $this->variableIsAllowed( $name ) && (
148            $this->wikiPageConfig->hasWithFlags( $name, $flags ) ||
149            $this->globalVarConfig->has( $name )
150        );
151    }
152}