Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
8.57% |
6 / 70 |
|
0.00% |
0 / 6 |
CRAP | |
0.00% |
0 / 1 |
ConfigHooks | |
8.57% |
6 / 70 |
|
0.00% |
0 / 6 |
464.22 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
onEditFilterMergedContent | |
54.55% |
6 / 11 |
|
0.00% |
0 / 1 |
7.35 | |||
onJsonValidateSave | |
0.00% |
0 / 12 |
|
0.00% |
0 / 1 |
20 | |||
onPageSaveComplete | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
12 | |||
onSkinTemplateNavigation__Universal | |
0.00% |
0 / 19 |
|
0.00% |
0 / 1 |
72 | |||
onSpecialPage_initList | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | |
3 | namespace GrowthExperiments\Config; |
4 | |
5 | use GrowthExperiments\Config\Validation\ConfigValidatorFactory; |
6 | use GrowthExperiments\Specials\SpecialEditGrowthConfig; |
7 | use GrowthExperiments\Specials\SpecialEditGrowthConfigRedirect; |
8 | use MediaWiki\Config\Config; |
9 | use MediaWiki\Content\Content; |
10 | use MediaWiki\Content\Hook\JsonValidateSaveHook; |
11 | use MediaWiki\Content\JsonContent; |
12 | use MediaWiki\Content\TextContent; |
13 | use MediaWiki\Context\IContextSource; |
14 | use MediaWiki\Deferred\DeferredUpdates; |
15 | use MediaWiki\Hook\EditFilterMergedContentHook; |
16 | use MediaWiki\Hook\SkinTemplateNavigation__UniversalHook; |
17 | use MediaWiki\Json\FormatJson; |
18 | use MediaWiki\Page\PageIdentity; |
19 | use MediaWiki\Registration\ExtensionRegistry; |
20 | use MediaWiki\SpecialPage\Hook\SpecialPage_initListHook; |
21 | use MediaWiki\SpecialPage\SpecialPage; |
22 | use MediaWiki\Status\Status; |
23 | use MediaWiki\Storage\Hook\PageSaveCompleteHook; |
24 | use MediaWiki\Title\TitleFactory; |
25 | use MediaWiki\Title\TitleValue; |
26 | use MediaWiki\User\User; |
27 | use StatusValue; |
28 | |
29 | class ConfigHooks implements |
30 | EditFilterMergedContentHook, |
31 | JsonValidateSaveHook, |
32 | PageSaveCompleteHook, |
33 | SkinTemplateNavigation__UniversalHook, |
34 | SpecialPage_initListHook |
35 | { |
36 | private ConfigValidatorFactory $configValidatorFactory; |
37 | private WikiPageConfigLoader $configLoader; |
38 | private TitleFactory $titleFactory; |
39 | private Config $config; |
40 | |
41 | /** |
42 | * @param ConfigValidatorFactory $configValidatorFactory |
43 | * @param WikiPageConfigLoader $configLoader |
44 | * @param TitleFactory $titleFactory |
45 | * @param Config $config |
46 | */ |
47 | public function __construct( |
48 | ConfigValidatorFactory $configValidatorFactory, |
49 | WikiPageConfigLoader $configLoader, |
50 | TitleFactory $titleFactory, |
51 | Config $config |
52 | ) { |
53 | $this->configValidatorFactory = $configValidatorFactory; |
54 | $this->configLoader = $configLoader; |
55 | $this->titleFactory = $titleFactory; |
56 | $this->config = $config; |
57 | } |
58 | |
59 | /** |
60 | * @inheritDoc |
61 | */ |
62 | public function onEditFilterMergedContent( |
63 | IContextSource $context, |
64 | Content $content, |
65 | Status $status, |
66 | $summary, |
67 | User $user, |
68 | $minoredit |
69 | ) { |
70 | // Check whether this is a config page edited |
71 | $title = $context->getTitle(); |
72 | foreach ( $this->configValidatorFactory->getSupportedConfigPages() as $configTitle ) { |
73 | if ( $title->equals( $configTitle ) ) { |
74 | // Check content model |
75 | if ( |
76 | $content->getModel() !== CONTENT_MODEL_JSON || |
77 | !( $content instanceof TextContent ) |
78 | ) { |
79 | $status->fatal( |
80 | 'growthexperiments-config-validator-contentmodel-mismatch', |
81 | $content->getModel() |
82 | ); |
83 | return false; |
84 | } |
85 | } |
86 | } |
87 | return true; |
88 | } |
89 | |
90 | /** |
91 | * @inheritDoc |
92 | */ |
93 | public function onJsonValidateSave( |
94 | JsonContent $content, PageIdentity $pageIdentity, StatusValue $status |
95 | ) { |
96 | foreach ( $this->configValidatorFactory->getSupportedConfigPages() as $configTitle ) { |
97 | if ( $pageIdentity->isSamePageAs( $configTitle ) ) { |
98 | $data = FormatJson::parse( $content->getText(), FormatJson::FORCE_ASSOC )->getValue(); |
99 | $status->merge( |
100 | $this->configValidatorFactory |
101 | // @phan-suppress-next-line PhanTypeMismatchArgumentNullable |
102 | ->newConfigValidator( TitleValue::castPageToLinkTarget( $pageIdentity ) ) |
103 | ->validate( $data ) |
104 | ); |
105 | if ( !$status->isGood() ) { |
106 | // JsonValidateSave expects a fatal status on failure, but the validator uses isGood() |
107 | $status->setOK( false ); |
108 | } |
109 | return $status->isOK(); |
110 | } |
111 | } |
112 | } |
113 | |
114 | /** |
115 | * Invalidate configuration cache when needed. |
116 | * @inheritDoc |
117 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/PageSaveComplete |
118 | */ |
119 | public function onPageSaveComplete( |
120 | $wikiPage, $user, $summary, $flags, $revisionRecord, $editResult |
121 | ) { |
122 | DeferredUpdates::addCallableUpdate( function () use ( $wikiPage ) { |
123 | $title = $wikiPage->getTitle(); |
124 | foreach ( $this->configValidatorFactory->getSupportedConfigPages() as $configTitle ) { |
125 | if ( $title->equals( $configTitle ) ) { |
126 | $this->configLoader->invalidate( $configTitle ); |
127 | } |
128 | } |
129 | } ); |
130 | } |
131 | |
132 | /** |
133 | * @inheritDoc |
134 | */ |
135 | public function onSkinTemplateNavigation__Universal( $sktemplate, &$links ): void { |
136 | // Code inspired by MassMessageHooks::onSkinTemplateNavigation |
137 | $title = $sktemplate->getTitle(); |
138 | $geConfigTitle = $this->titleFactory |
139 | ->newFromText( |
140 | $this->config->get( 'GEWikiConfigPageTitle' ) |
141 | ); |
142 | $newcomerTasksConfigTitle = $this->titleFactory |
143 | ->newFromText( |
144 | $this->config->get( 'GENewcomerTasksConfigTitle' ) |
145 | ); |
146 | if ( |
147 | array_key_exists( 'edit', $links['views'] ) && |
148 | ( |
149 | ( $geConfigTitle !== null && $title->equals( $geConfigTitle ) ) || |
150 | ( $newcomerTasksConfigTitle !== null && $title->equals( $newcomerTasksConfigTitle ) ) |
151 | ) && |
152 | $title->hasContentModel( CONTENT_MODEL_JSON ) |
153 | ) { |
154 | // Get the revision being viewed, if applicable |
155 | $request = $sktemplate->getRequest(); |
156 | // $oldid is guaranteed to be an integer, 0 if invalid |
157 | $oldid = $request->getInt( 'oldid' ); |
158 | |
159 | // Show normal JSON editor if the user is trying to edit old version |
160 | if ( $oldid == 0 ) { |
161 | $links['views']['edit']['href'] = SpecialPage::getTitleFor( |
162 | 'EditGrowthConfig' |
163 | )->getFullUrl(); |
164 | } |
165 | } |
166 | } |
167 | |
168 | /** |
169 | * @inheritDoc |
170 | */ |
171 | public function onSpecialPage_initList( &$list ) { |
172 | if ( |
173 | ExtensionRegistry::getInstance()->isLoaded( 'CommunityConfiguration' ) && |
174 | $this->config->get( 'GEUseCommunityConfigurationExtension' ) |
175 | ) { |
176 | $list['EditGrowthConfig'] = [ |
177 | 'class' => SpecialEditGrowthConfigRedirect::class, |
178 | ]; |
179 | } else { |
180 | $list['EditGrowthConfig'] = [ |
181 | 'class' => SpecialEditGrowthConfig::class, |
182 | 'services' => [ |
183 | 'TitleFactory', |
184 | 'RevisionLookup', |
185 | 'PageProps', |
186 | 'DBLoadBalancer', |
187 | 'ReadOnlyMode', |
188 | 'GrowthExperimentsWikiPageConfigLoader', |
189 | 'GrowthExperimentsWikiPageConfigWriterFactory', |
190 | 'GrowthExperimentsCommunityConfig' |
191 | ] |
192 | ]; |
193 | } |
194 | } |
195 | } |