Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
38.46% |
20 / 52 |
|
50.00% |
5 / 10 |
CRAP | |
0.00% |
0 / 1 |
CampaignHooks | |
38.46% |
20 / 52 |
|
50.00% |
5 / 10 |
183.54 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
onPageDeleteComplete | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
onEditFilterMergedContent | |
55.56% |
5 / 9 |
|
0.00% |
0 / 1 |
7.19 | |||
onLinksUpdateComplete | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
20 | |||
onPageSaveComplete | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
12 | |||
doCampaignUpdate | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
onPageDelete | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
onMovePageIsValidMove | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
12 | |||
isGlobalConfigAnchor | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
isMagicUser | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
20 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\MediaUploader\Hooks; |
4 | |
5 | use Content; |
6 | use DeferredUpdates; |
7 | use IContextSource; |
8 | use LinksUpdate; |
9 | use ManualLogEntry; |
10 | use MediaWiki\Extension\MediaUploader\Campaign\CampaignContent; |
11 | use MediaWiki\Extension\MediaUploader\Campaign\CampaignStore; |
12 | use MediaWiki\Extension\MediaUploader\Config\ConfigCacheInvalidator; |
13 | use MediaWiki\Extension\MediaUploader\MediaUploaderServices; |
14 | use MediaWiki\Hook\EditFilterMergedContentHook; |
15 | use MediaWiki\Hook\LinksUpdateCompleteHook; |
16 | use MediaWiki\Hook\MovePageIsValidMoveHook; |
17 | use MediaWiki\Linker\LinkTarget; |
18 | use MediaWiki\Page\Hook\PageDeleteCompleteHook; |
19 | use MediaWiki\Page\Hook\PageDeleteHook; |
20 | use MediaWiki\Page\ProperPageIdentity; |
21 | use MediaWiki\Permissions\Authority; |
22 | use MediaWiki\Revision\RevisionRecord; |
23 | use MediaWiki\Storage\EditResult; |
24 | use MediaWiki\Storage\Hook\PageSaveCompleteHook; |
25 | use MediaWiki\User\UserIdentity; |
26 | use Status; |
27 | use StatusValue; |
28 | use Title; |
29 | use TitleValue; |
30 | use User; |
31 | use Wikimedia\Assert\PreconditionException; |
32 | use WikiPage; |
33 | |
34 | /** |
35 | * Hooks related to handling events happening on pages in the Campaign: namespace. |
36 | */ |
37 | class CampaignHooks implements |
38 | EditFilterMergedContentHook, |
39 | LinksUpdateCompleteHook, |
40 | MovePageIsValidMoveHook, |
41 | PageDeleteCompleteHook, |
42 | PageDeleteHook, |
43 | PageSaveCompleteHook |
44 | { |
45 | |
46 | /** @var CampaignStore */ |
47 | private $campaignStore; |
48 | |
49 | /** @var ConfigCacheInvalidator */ |
50 | private $cacheInvalidator; |
51 | |
52 | /** |
53 | * @param CampaignStore $campaignStore |
54 | * @param ConfigCacheInvalidator $cacheInvalidator |
55 | */ |
56 | public function __construct( |
57 | CampaignStore $campaignStore, |
58 | ConfigCacheInvalidator $cacheInvalidator |
59 | ) { |
60 | $this->campaignStore = $campaignStore; |
61 | $this->cacheInvalidator = $cacheInvalidator; |
62 | } |
63 | |
64 | /** |
65 | * Deletes entries from mu_campaign table when a Campaign is deleted |
66 | * |
67 | * @param ProperPageIdentity $page |
68 | * @param Authority $deleter |
69 | * @param string $reason |
70 | * @param int $pageID |
71 | * @param RevisionRecord $deletedRev |
72 | * @param ManualLogEntry $logEntry |
73 | * @param int $archivedRevisionCount |
74 | * |
75 | * @return true |
76 | */ |
77 | public function onPageDeleteComplete( |
78 | ProperPageIdentity $page, Authority $deleter, string $reason, int $pageID, |
79 | RevisionRecord $deletedRev, ManualLogEntry $logEntry, int $archivedRevisionCount |
80 | ): bool { |
81 | if ( $page->getNamespace() !== NS_CAMPAIGN ) { |
82 | return true; |
83 | } |
84 | |
85 | $this->campaignStore->deleteCampaignByPageId( $pageID ); |
86 | return true; |
87 | } |
88 | |
89 | /** |
90 | * Validates that the revised contents of a campaign are valid YAML. |
91 | * If not valid, rejects edit with error message. |
92 | * |
93 | * @param IContextSource $context |
94 | * @param Content $content |
95 | * @param Status $status |
96 | * @param string $summary |
97 | * @param User $user |
98 | * @param bool $minoredit |
99 | * |
100 | * @return bool |
101 | */ |
102 | public function onEditFilterMergedContent( |
103 | IContextSource $context, |
104 | Content $content, |
105 | Status $status, |
106 | $summary, |
107 | User $user, |
108 | $minoredit |
109 | ): bool { |
110 | if ( !$context->getTitle()->inNamespace( NS_CAMPAIGN ) |
111 | || !$content instanceof CampaignContent |
112 | ) { |
113 | return true; |
114 | } |
115 | |
116 | if ( $this->isGlobalConfigAnchor( $context->getTitle() ) ) { |
117 | // There's no need to validate the anchor's contents, it doesn't |
118 | // matter anyway. |
119 | return true; |
120 | } |
121 | |
122 | if ( MediaUploaderServices::isSystemUser( $user ) ) { |
123 | return true; |
124 | } |
125 | |
126 | $status->merge( $content->getValidationStatus() ); |
127 | |
128 | return $status->isOK(); |
129 | } |
130 | |
131 | /** |
132 | * Invalidates the cache for a campaign when any of its dependents are edited. The |
133 | * 'dependents' are tracked by entries in the templatelinks table, which are inserted |
134 | * by CampaignContent. |
135 | * |
136 | * This is usually run via the Job Queue mechanism. |
137 | * |
138 | * @param LinksUpdate $linksUpdate |
139 | * @param mixed $ticket |
140 | * |
141 | * @return bool |
142 | */ |
143 | public function onLinksUpdateComplete( $linksUpdate, $ticket ): bool { |
144 | if ( !$linksUpdate->getTitle()->inNamespace( NS_CAMPAIGN ) ) { |
145 | return true; |
146 | } |
147 | |
148 | // Invalidate global config cache. |
149 | if ( $this->isGlobalConfigAnchor( $linksUpdate->getTitle() ) ) { |
150 | // Ignore edits by MediaUploader itself. |
151 | // The cache was invalidated recently anyway. |
152 | if ( !$this->isMagicUser( $linksUpdate->getTriggeringUser() ) ) { |
153 | $this->cacheInvalidator->invalidate(); |
154 | } |
155 | return true; |
156 | } |
157 | |
158 | $this->cacheInvalidator->invalidate( $linksUpdate->getTitle()->getDBkey() ); |
159 | |
160 | return true; |
161 | } |
162 | |
163 | /** |
164 | * Sets up appropriate entries in the uc_campaigns table for each Campaign |
165 | * Acts everytime a page in the NS_CAMPAIGN namespace is saved |
166 | * |
167 | * The real update is done in doCampaignUpdate |
168 | * |
169 | * @param WikiPage $wikiPage |
170 | * @param UserIdentity $userIdentity |
171 | * @param string $summary |
172 | * @param int $flags |
173 | * @param RevisionRecord $revisionRecord |
174 | * @param EditResult $editResult |
175 | * |
176 | * @return bool |
177 | */ |
178 | public function onPageSaveComplete( |
179 | $wikiPage, $userIdentity, $summary, $flags, $revisionRecord, $editResult |
180 | ): bool { |
181 | $content = $wikiPage->getContent(); |
182 | if ( !$content instanceof CampaignContent |
183 | || $this->isGlobalConfigAnchor( $wikiPage->getTitle() ) |
184 | ) { |
185 | return true; |
186 | } |
187 | |
188 | DeferredUpdates::addCallableUpdate( |
189 | function () use ( $wikiPage, $content ) { |
190 | $this->doCampaignUpdate( $wikiPage, $content ); |
191 | } |
192 | ); |
193 | |
194 | return true; |
195 | } |
196 | |
197 | /** |
198 | * Performs the actual campaign data update after the campaign page is saved. |
199 | * |
200 | * @param WikiPage $wikiPage |
201 | * @param CampaignContent $content |
202 | */ |
203 | public function doCampaignUpdate( WikiPage $wikiPage, CampaignContent $content ): void { |
204 | $campaignRecord = $content->newCampaignRecord( $wikiPage, $wikiPage->getId() ); |
205 | $this->campaignStore->upsertCampaign( $campaignRecord ); |
206 | } |
207 | |
208 | /** |
209 | * Prevent the global config anchor from being deleted. |
210 | * |
211 | * @param ProperPageIdentity $page |
212 | * @param Authority $deleter |
213 | * @param string $reason |
214 | * @param StatusValue $status |
215 | * @param bool $suppress |
216 | * |
217 | * @return bool |
218 | */ |
219 | public function onPageDelete( |
220 | ProperPageIdentity $page, Authority $deleter, string $reason, StatusValue $status, bool $suppress |
221 | ): bool { |
222 | if ( $this->isGlobalConfigAnchor( TitleValue::newFromPage( $page ) ) ) { |
223 | $status->fatal( 'mediauploader-global-config-anchor' ); |
224 | return false; |
225 | } |
226 | return true; |
227 | } |
228 | |
229 | /** |
230 | * Prevent the global config anchor from being moved. |
231 | * |
232 | * @param Title $oldTitle |
233 | * @param Title $newTitle |
234 | * @param Status $status |
235 | * |
236 | * @return bool |
237 | */ |
238 | public function onMovePageIsValidMove( $oldTitle, $newTitle, $status ): bool { |
239 | if ( $this->isGlobalConfigAnchor( $oldTitle ) || |
240 | $this->isGlobalConfigAnchor( $newTitle ) |
241 | ) { |
242 | $status->fatal( 'mediauploader-global-config-anchor' ); |
243 | } |
244 | return true; |
245 | } |
246 | |
247 | /** |
248 | * Checks whether $linkTarget is of the global config anchor page. |
249 | * |
250 | * @param LinkTarget $linkTarget |
251 | * |
252 | * @return bool |
253 | */ |
254 | private function isGlobalConfigAnchor( LinkTarget $linkTarget ): bool { |
255 | return $linkTarget->isSameLinkAs( |
256 | CampaignContent::getGlobalConfigAnchorLinkTarget() |
257 | ); |
258 | } |
259 | |
260 | /** |
261 | * Checks whether $identity is of the "magic" built-in MediaUploader user. |
262 | * |
263 | * @param UserIdentity|null $identity |
264 | * |
265 | * @return bool |
266 | */ |
267 | private function isMagicUser( ?UserIdentity $identity ): bool { |
268 | if ( $identity === null ) { |
269 | return false; |
270 | } |
271 | try { |
272 | $identity->assertWiki( UserIdentity::LOCAL ); |
273 | } catch ( PreconditionException $ex ) { |
274 | return false; |
275 | } |
276 | return $identity->isRegistered() && $identity->getName() === 'MediaUploader'; |
277 | } |
278 | } |