Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 236 |
|
0.00% |
0 / 14 |
CRAP | |
0.00% |
0 / 1 |
EventBusHooks | |
0.00% |
0 / 236 |
|
0.00% |
0 / 14 |
1190 | |
0.00% |
0 / 1 |
getRevisionLookup | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
sendResourceChangedEvent | |
0.00% |
0 / 6 |
|
0.00% |
0 / 1 |
2 | |||
onPageDeleteComplete | |
0.00% |
0 / 23 |
|
0.00% |
0 / 1 |
12 | |||
onPageUndeleteComplete | |
0.00% |
0 / 15 |
|
0.00% |
0 / 1 |
2 | |||
onPageMoveComplete | |
0.00% |
0 / 17 |
|
0.00% |
0 / 1 |
2 | |||
onArticleRevisionVisibilitySet | |
0.00% |
0 / 41 |
|
0.00% |
0 / 1 |
42 | |||
onArticlePurge | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 | |||
onPageSaveComplete | |
0.00% |
0 / 8 |
|
0.00% |
0 / 1 |
12 | |||
onRevisionRecordInserted | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
sendRevisionCreateEvent | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
2 | |||
onBlockIpComplete | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
2 | |||
onLinksUpdateComplete | |
0.00% |
0 / 56 |
|
0.00% |
0 / 1 |
110 | |||
onArticleProtectComplete | |
0.00% |
0 / 18 |
|
0.00% |
0 / 1 |
2 | |||
onChangeTagsAfterUpdateTags | |
0.00% |
0 / 22 |
|
0.00% |
0 / 1 |
12 |
1 | <?php |
2 | |
3 | /** |
4 | * Hooks for production of events to an HTTP service. |
5 | * |
6 | * This program is free software; you can redistribute it and/or modify |
7 | * it under the terms of the GNU General Public License as published by |
8 | * the Free Software Foundation; either version 2 of the License, or |
9 | * (at your option) any later version. |
10 | * |
11 | * This program is distributed in the hope that it will be useful, |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
14 | * GNU General Public License for more details. |
15 | * |
16 | * You should have received a copy of the GNU General Public License along |
17 | * with this program; if not, write to the Free Software Foundation, Inc., |
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
19 | * http://www.gnu.org/copyleft/gpl.html |
20 | * |
21 | * @file |
22 | * @author Eric Evans, Andrew Otto |
23 | */ |
24 | |
25 | namespace MediaWiki\Extension\EventBus; |
26 | |
27 | use IDBAccessObject; |
28 | use ManualLogEntry; |
29 | use MediaWiki\Block\DatabaseBlock; |
30 | use MediaWiki\ChangeTags\Hook\ChangeTagsAfterUpdateTagsHook; |
31 | use MediaWiki\Deferred\DeferredUpdates; |
32 | use MediaWiki\Deferred\LinksUpdate\LinksTable; |
33 | use MediaWiki\Deferred\LinksUpdate\LinksUpdate; |
34 | use MediaWiki\Extension\EventBus\HookHandlers\MediaWiki\PageChangeHooks; |
35 | use MediaWiki\Hook\ArticleRevisionVisibilitySetHook; |
36 | use MediaWiki\Hook\BlockIpCompleteHook; |
37 | use MediaWiki\Hook\LinksUpdateCompleteHook; |
38 | use MediaWiki\Hook\PageMoveCompleteHook; |
39 | use MediaWiki\Linker\LinkTarget; |
40 | use MediaWiki\MediaWikiServices; |
41 | use MediaWiki\Page\Hook\ArticleProtectCompleteHook; |
42 | use MediaWiki\Page\Hook\ArticlePurgeHook; |
43 | use MediaWiki\Page\Hook\PageDeleteCompleteHook; |
44 | use MediaWiki\Page\Hook\PageUndeleteCompleteHook; |
45 | use MediaWiki\Page\ProperPageIdentity; |
46 | use MediaWiki\Permissions\Authority; |
47 | use MediaWiki\Revision\Hook\RevisionRecordInsertedHook; |
48 | use MediaWiki\Revision\RevisionLookup; |
49 | use MediaWiki\Revision\RevisionRecord; |
50 | use MediaWiki\Storage\EditResult; |
51 | use MediaWiki\Storage\Hook\PageSaveCompleteHook; |
52 | use MediaWiki\Title\Title; |
53 | use MediaWiki\User\User; |
54 | use MediaWiki\User\UserIdentity; |
55 | use RecentChange; |
56 | use RequestContext; |
57 | use Wikimedia\Assert\Assert; |
58 | use WikiPage; |
59 | |
60 | /** |
61 | * @deprecated since EventBus 0.5.0 Use specific feature based hooks in HookHandlers/, |
62 | * or even better, put them in your own extension instead of in EventBus. |
63 | */ |
64 | class EventBusHooks implements |
65 | PageSaveCompleteHook, |
66 | PageMoveCompleteHook, |
67 | PageDeleteCompleteHook, |
68 | PageUndeleteCompleteHook, |
69 | ArticleRevisionVisibilitySetHook, |
70 | ArticlePurgeHook, |
71 | BlockIpCompleteHook, |
72 | LinksUpdateCompleteHook, |
73 | ArticleProtectCompleteHook, |
74 | ChangeTagsAfterUpdateTagsHook, |
75 | RevisionRecordInsertedHook |
76 | { |
77 | |
78 | /** |
79 | * @return RevisionLookup |
80 | */ |
81 | private static function getRevisionLookup(): RevisionLookup { |
82 | return MediaWikiServices::getInstance()->getRevisionLookup(); |
83 | } |
84 | |
85 | /** |
86 | * Creates and sends a single resource_change event to EventBus |
87 | * |
88 | * @param LinkTarget $title article title object |
89 | * @param array $tags the array of tags to use in the event |
90 | */ |
91 | private static function sendResourceChangedEvent( |
92 | LinkTarget $title, |
93 | array $tags |
94 | ) { |
95 | $stream = 'resource_change'; |
96 | $eventbus = EventBus::getInstanceForStream( $stream ); |
97 | $event = $eventbus->getFactory()->createResourceChangeEvent( $stream, $title, $tags ); |
98 | |
99 | DeferredUpdates::addCallableUpdate( static function () use ( $eventbus, $event ) { |
100 | $eventbus->send( [ $event ] ); |
101 | } ); |
102 | } |
103 | |
104 | /** |
105 | * Occurs after the delete article request has been processed. |
106 | * |
107 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/ArticleDeleteComplete |
108 | * |
109 | * @param ProperPageIdentity $page Page that was deleted. |
110 | * @param Authority $deleter Who deleted the page |
111 | * @param string $reason Reason the page was deleted |
112 | * @param int $pageID ID of the page that was deleted |
113 | * @param RevisionRecord $deletedRev Last revision of the deleted page |
114 | * @param ManualLogEntry $logEntry ManualLogEntry used to record the deletion |
115 | * @param int $archivedRevisionCount Number of revisions archived during the deletion |
116 | * @return true|void |
117 | */ |
118 | public function onPageDeleteComplete( |
119 | ProperPageIdentity $page, |
120 | Authority $deleter, |
121 | string $reason, |
122 | int $pageID, |
123 | RevisionRecord $deletedRev, |
124 | ManualLogEntry $logEntry, |
125 | int $archivedRevisionCount |
126 | ) { |
127 | $stream = $logEntry->getType() === 'suppress' ? |
128 | 'mediawiki.page-suppress' : 'mediawiki.page-delete'; |
129 | $eventbus = EventBus::getInstanceForStream( $stream ); |
130 | |
131 | // Don't set performer in the event if this delete suppresses the page from other admins. |
132 | // https://phabricator.wikimedia.org/T342487 |
133 | $performerForEvent = $logEntry->getType() == 'suppress' ? null : $deleter->getUser(); |
134 | |
135 | $eventBusFactory = $eventbus->getFactory(); |
136 | $eventBusFactory->setCommentFormatter( MediaWikiServices::getInstance()->getCommentFormatter() ); |
137 | $title = Title::castFromPageIdentity( $page ); |
138 | Assert::postcondition( $title !== null, '$page can be cast to a LinkTarget' ); |
139 | |
140 | $event = $eventBusFactory->createPageDeleteEvent( |
141 | $stream, |
142 | $performerForEvent, |
143 | $pageID, |
144 | $title, |
145 | $title->isRedirect(), |
146 | $archivedRevisionCount, |
147 | $deletedRev, |
148 | $reason |
149 | ); |
150 | |
151 | DeferredUpdates::addCallableUpdate( |
152 | static function () use ( $eventbus, $event ) { |
153 | $eventbus->send( [ $event ] ); |
154 | } |
155 | ); |
156 | } |
157 | |
158 | /** |
159 | * When one or more revisions of an article are restored. |
160 | * |
161 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/PageUndeleteComplete |
162 | * |
163 | * @param ProperPageIdentity $page Page that was undeleted. |
164 | * @param Authority $restorer Who undeleted the page |
165 | * @param string $reason Reason the page was undeleted |
166 | * @param RevisionRecord $restoredRev Last revision of the undeleted page |
167 | * @param ManualLogEntry $logEntry Log entry generated by the restoration |
168 | * @param int $restoredRevisionCount Number of revisions restored during the deletion |
169 | * @param bool $created Whether the undeletion result in a page being created |
170 | * @param array $restoredPageIds Array of all undeleted page IDs. |
171 | * This will have multiple page IDs if there was more than one deleted page with the same page title. |
172 | * @return void This hook must not abort, it must return no value |
173 | */ |
174 | public function onPageUndeleteComplete( |
175 | ProperPageIdentity $page, |
176 | Authority $restorer, |
177 | string $reason, |
178 | RevisionRecord $restoredRev, |
179 | ManualLogEntry $logEntry, |
180 | int $restoredRevisionCount, |
181 | bool $created, |
182 | array $restoredPageIds |
183 | ): void { |
184 | $stream = 'mediawiki.page-undelete'; |
185 | $eventBus = EventBus::getInstanceForStream( $stream ); |
186 | $eventBusFactory = $eventBus->getFactory(); |
187 | $eventBusFactory->setCommentFormatter( MediaWikiServices::getInstance()->getCommentFormatter() ); |
188 | |
189 | $event = $eventBusFactory->createPageUndeleteEvent( |
190 | $stream, |
191 | $restorer->getUser(), |
192 | // @phan-suppress-next-line PhanTypeMismatchArgumentNullable PageIdentity is not null |
193 | Title::castFromPageIdentity( $page ), |
194 | $reason, |
195 | $page->getId(), |
196 | $restoredRev, |
197 | ); |
198 | |
199 | DeferredUpdates::addCallableUpdate( static function () use ( $eventBus, $event ) { |
200 | $eventBus->send( [ $event ] ); |
201 | } ); |
202 | } |
203 | |
204 | /** |
205 | * Occurs whenever a request to move an article is completed. |
206 | * |
207 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/PageMoveComplete |
208 | * |
209 | * @param LinkTarget $oldTitle the old title |
210 | * @param LinkTarget $newTitle the new title |
211 | * @param UserIdentity $userIdentity User who did the move |
212 | * @param int $pageid database page_id of the page that's been moved |
213 | * @param int $redirid database page_id of the created redirect, or 0 if suppressed |
214 | * @param string $reason reason for the move |
215 | * @param RevisionRecord $newRevisionRecord revision created by the move |
216 | */ |
217 | public function onPageMoveComplete( |
218 | $oldTitle, |
219 | $newTitle, |
220 | $userIdentity, |
221 | $pageid, |
222 | $redirid, |
223 | $reason, |
224 | $newRevisionRecord |
225 | ) { |
226 | $stream = 'mediawiki.page-move'; |
227 | $eventBus = EventBus::getInstanceForStream( $stream ); |
228 | $eventBusFactory = $eventBus->getFactory(); |
229 | $eventBusFactory->setCommentFormatter( MediaWikiServices::getInstance()->getCommentFormatter() ); |
230 | $event = $eventBusFactory->createPageMoveEvent( |
231 | $stream, |
232 | $oldTitle, |
233 | $newTitle, |
234 | $newRevisionRecord, |
235 | $userIdentity, |
236 | $reason |
237 | ); |
238 | |
239 | DeferredUpdates::addCallableUpdate( |
240 | static function () use ( $eventBus, $event ) { |
241 | $eventBus->send( [ $event ] ); |
242 | } |
243 | ); |
244 | } |
245 | |
246 | /** |
247 | * Called when changing visibility of one or more revisions of an article. |
248 | * Produces mediawiki.revision-visibility-change events. |
249 | * |
250 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/ArticleRevisionVisibilitySet |
251 | * |
252 | * @param Title $title title object of the article |
253 | * @param array $revIds array of integer revision IDs |
254 | * @param array $visibilityChangeMap map of revision id to oldBits and newBits. |
255 | * This array can be examined to determine exactly what visibility |
256 | * bits have changed for each revision. This array is of the form |
257 | * [id => ['oldBits' => $oldBits, 'newBits' => $newBits], ... ] |
258 | */ |
259 | public function onArticleRevisionVisibilitySet( |
260 | $title, |
261 | $revIds, |
262 | $visibilityChangeMap |
263 | ) { |
264 | $stream = 'mediawiki.revision-visibility-change'; |
265 | $events = []; |
266 | $eventBus = EventBus::getInstanceForStream( $stream ); |
267 | // https://phabricator.wikimedia.org/T321411 |
268 | $performer = RequestContext::getMain()->getUser(); |
269 | $performer->loadFromId(); |
270 | |
271 | // Create an event for each revId that was changed. |
272 | foreach ( $revIds as $revId ) { |
273 | // Read from primary since due to replication lag the updated field visibility |
274 | // might still not be available on a replica and we are at risk of leaking |
275 | // just suppressed data. |
276 | $revision = self::getRevisionLookup() |
277 | ->getRevisionById( $revId, IDBAccessObject::READ_LATEST ); |
278 | |
279 | // If the page is deleted simultaneously (null $revision) or if |
280 | // this revId is not in the $visibilityChangeMap, then we can't |
281 | // send a meaningful event. |
282 | if ( $revision === null ) { |
283 | wfDebug( |
284 | __METHOD__ . ' revision ' . $revId . |
285 | ' could not be found and may have been deleted. Cannot ' . |
286 | "create mediawiki/revision/visibility-change event.\n" |
287 | ); |
288 | continue; |
289 | } elseif ( !array_key_exists( $revId, $visibilityChangeMap ) ) { |
290 | // This should not happen, log it. |
291 | wfDebug( |
292 | __METHOD__ . ' revision id ' . $revId . |
293 | ' not found in visibilityChangeMap. Cannot create ' . |
294 | "mediawiki/revision/visibility-change event.\n" |
295 | ); |
296 | continue; |
297 | } else { |
298 | $eventBusFactory = $eventBus->getFactory(); |
299 | $eventBusFactory->setCommentFormatter( MediaWikiServices::getInstance()->getCommentFormatter() ); |
300 | |
301 | // If this revision is 'suppressed' AKA restricted, then the person performing |
302 | // 'RevisionDelete' should not be visible in public data. |
303 | // https://phabricator.wikimedia.org/T342487 |
304 | // |
305 | // NOTE: This event stream tries to match the visibility of MediaWiki core logs, |
306 | // where regular delete/revision events are public, and suppress/revision events |
307 | // are private. In MediaWiki core logs, private events are fully hidden from |
308 | // the public. Here, we need to produce a 'private' event to the |
309 | // mediawiki.page_change stream, to indicate to consumers that |
310 | // they should also 'suppress' the revision. When this is done, we need to |
311 | // make sure that we do not reproduce the data that has been suppressed |
312 | // in the event itself. E.g. if the username of the editor of the revision has been |
313 | // suppressed, we should not include any information about that editor in the event. |
314 | $performerForEvent = PageChangeHooks::isSecretRevisionVisibilityChange( |
315 | $visibilityChangeMap[$revId]['oldBits'], |
316 | $visibilityChangeMap[$revId]['newBits'] |
317 | ) ? null : $performer; |
318 | |
319 | $events[] = $eventBusFactory->createRevisionVisibilityChangeEvent( |
320 | $stream, |
321 | $revision, |
322 | $performerForEvent, |
323 | $visibilityChangeMap[$revId] |
324 | ); |
325 | } |
326 | } |
327 | |
328 | if ( $events === [] ) { |
329 | // For revision-visibility-set it's possible that |
330 | // the page was deleted simultaneously and we can not |
331 | // send a meaningful event. |
332 | return; |
333 | } |
334 | |
335 | DeferredUpdates::addCallableUpdate( |
336 | static function () use ( $eventBus, $events ) { |
337 | $eventBus->send( $events ); |
338 | } |
339 | ); |
340 | } |
341 | |
342 | /** |
343 | * Callback for article purge. |
344 | * |
345 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/ArticlePurge |
346 | * |
347 | * @param WikiPage $wikiPage |
348 | */ |
349 | public function onArticlePurge( $wikiPage ) { |
350 | self::sendResourceChangedEvent( $wikiPage->getTitle(), [ 'purge' ] ); |
351 | } |
352 | |
353 | /** |
354 | * Occurs after the save page request has been processed. |
355 | * |
356 | * Sends an event if the new revision was also a page creation |
357 | * |
358 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/PageSaveComplete |
359 | * |
360 | * @param WikiPage $wikiPage |
361 | * @param UserIdentity $userIdentity |
362 | * @param string $summary |
363 | * @param int $flags |
364 | * @param RevisionRecord $revisionRecord |
365 | * @param EditResult $editResult |
366 | */ |
367 | public function onPageSaveComplete( |
368 | $wikiPage, |
369 | $userIdentity, |
370 | $summary, |
371 | $flags, |
372 | $revisionRecord, |
373 | $editResult |
374 | ) { |
375 | if ( $editResult->isNullEdit() ) { |
376 | self::sendResourceChangedEvent( $wikiPage->getTitle(), [ 'null_edit' ] ); |
377 | return; |
378 | } |
379 | |
380 | if ( $flags & EDIT_NEW ) { |
381 | // Not just a new revision, but a new page |
382 | self::sendRevisionCreateEvent( |
383 | 'mediawiki.page-create', |
384 | $revisionRecord |
385 | ); |
386 | } |
387 | } |
388 | |
389 | /** |
390 | * Occurs after a revision is inserted into the database. |
391 | * |
392 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/RevisionRecordInserted |
393 | * |
394 | * @param RevisionRecord $revisionRecord RevisionRecord that has just been inserted |
395 | */ |
396 | public function onRevisionRecordInserted( $revisionRecord ) { |
397 | self::sendRevisionCreateEvent( |
398 | 'mediawiki.revision-create', |
399 | $revisionRecord |
400 | ); |
401 | } |
402 | |
403 | /** |
404 | * @param string $stream |
405 | * @param RevisionRecord $revisionRecord |
406 | */ |
407 | private static function sendRevisionCreateEvent( |
408 | string $stream, |
409 | RevisionRecord $revisionRecord |
410 | ) { |
411 | $eventBus = EventBus::getInstanceForStream( $stream ); |
412 | $eventBusFactory = $eventBus->getFactory(); |
413 | $eventBusFactory->setCommentFormatter( |
414 | MediaWikiServices::getInstance()->getCommentFormatter() |
415 | ); |
416 | $event = $eventBusFactory->createRevisionCreateEvent( |
417 | $stream, |
418 | $revisionRecord |
419 | ); |
420 | |
421 | DeferredUpdates::addCallableUpdate( |
422 | static function () use ( $eventBus, $event ) { |
423 | $eventBus->send( [ $event ] ); |
424 | } |
425 | ); |
426 | } |
427 | |
428 | /** |
429 | * Occurs after the request to block an IP or user has been processed |
430 | * |
431 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/BlockIpComplete |
432 | * |
433 | * @param DatabaseBlock $block the block object that was saved |
434 | * @param User $user the user who did the block (not the one being blocked) |
435 | * @param DatabaseBlock|null $previousBlock the previous block state for the block target. |
436 | * null if this is a new block target. |
437 | */ |
438 | public function onBlockIpComplete( |
439 | $block, |
440 | $user, |
441 | $previousBlock |
442 | ) { |
443 | $stream = 'mediawiki.user-blocks-change'; |
444 | $eventBus = EventBus::getInstanceForStream( 'mediawiki.user-blocks-change' ); |
445 | $eventFactory = $eventBus->getFactory(); |
446 | $event = $eventFactory->createUserBlockChangeEvent( |
447 | $stream, $user, $block, $previousBlock ); |
448 | |
449 | DeferredUpdates::addCallableUpdate( |
450 | static function () use ( $eventBus, $event ) { |
451 | $eventBus->send( [ $event ] ); |
452 | } |
453 | ); |
454 | } |
455 | |
456 | /** |
457 | * Sends page-properties-change and page-links-change events |
458 | * |
459 | * Emits two events separately: one when the page properties change, and |
460 | * the other when links are added to or removed from the page. |
461 | * |
462 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/LinksUpdateComplete |
463 | * |
464 | * @param LinksUpdate $linksUpdate the update object |
465 | * @param mixed $ticket |
466 | */ |
467 | public function onLinksUpdateComplete( |
468 | $linksUpdate, $ticket |
469 | ) { |
470 | $addedProps = $linksUpdate->getAddedProperties(); |
471 | $removedProps = $linksUpdate->getRemovedProperties(); |
472 | $arePropsEmpty = !$removedProps && !$addedProps; |
473 | |
474 | $addedLinks = $linksUpdate->getPageReferenceArray( 'pagelinks', LinksTable::INSERTED ); |
475 | $addedExternalLinks = $linksUpdate->getAddedExternalLinks(); |
476 | $removedLinks = $linksUpdate->getPageReferenceArray( 'pagelinks', LinksTable::DELETED ); |
477 | $removedExternalLinks = $linksUpdate->getRemovedExternalLinks(); |
478 | $areLinksEmpty = !$removedLinks && !$addedLinks |
479 | && !$removedExternalLinks && !$addedExternalLinks; |
480 | |
481 | if ( $arePropsEmpty && $areLinksEmpty ) { |
482 | return; |
483 | } |
484 | |
485 | $title = $linksUpdate->getTitle(); |
486 | $user = $linksUpdate->getTriggeringUser(); |
487 | |
488 | // Use triggering revision's rev_id if it is set. |
489 | // If the LinksUpdate didn't have a triggering revision |
490 | // (probably because it was triggered by sysadmin maintenance). |
491 | // Use the page's latest revision. |
492 | $revRecord = $linksUpdate->getRevisionRecord(); |
493 | if ( $revRecord ) { |
494 | $revId = $revRecord->getId(); |
495 | } else { |
496 | $revId = $title->getLatestRevID(); |
497 | } |
498 | $pageId = $linksUpdate->getPageId(); |
499 | |
500 | if ( !$arePropsEmpty ) { |
501 | $stream = 'mediawiki.page-properties-change'; |
502 | $eventBus = EventBus::getInstanceForStream( $stream ); |
503 | $eventFactory = $eventBus->getFactory(); |
504 | $propEvent = $eventFactory->createPagePropertiesChangeEvent( |
505 | $stream, |
506 | $title, |
507 | $addedProps, |
508 | $removedProps, |
509 | $user, |
510 | $revId, |
511 | $pageId |
512 | ); |
513 | |
514 | DeferredUpdates::addCallableUpdate( |
515 | static function () use ( $eventBus, $propEvent ) { |
516 | $eventBus->send( [ $propEvent ] ); |
517 | } |
518 | ); |
519 | } |
520 | |
521 | if ( !$areLinksEmpty ) { |
522 | $stream = 'mediawiki.page-properties-change'; |
523 | $eventBus = EventBus::getInstanceForStream( $stream ); |
524 | $eventFactory = $eventBus->getFactory(); |
525 | $linkEvent = $eventFactory->createPageLinksChangeEvent( |
526 | 'mediawiki.page-links-change', |
527 | $title, |
528 | $addedLinks, |
529 | $addedExternalLinks, |
530 | $removedLinks, |
531 | $removedExternalLinks, |
532 | $user, |
533 | $revId, |
534 | $pageId |
535 | ); |
536 | |
537 | DeferredUpdates::addCallableUpdate( |
538 | static function () use ( $eventBus, $linkEvent ) { |
539 | $eventBus->send( [ $linkEvent ] ); |
540 | } |
541 | ); |
542 | } |
543 | } |
544 | |
545 | /** |
546 | * Sends a page-restrictions-change event |
547 | * |
548 | * @param WikiPage $wikiPage the article which restrictions were changed |
549 | * @param User $user the user who have changed the article |
550 | * @param string[] $protect set of new restrictions details |
551 | * @param string $reason the reason for page protection |
552 | */ |
553 | public function onArticleProtectComplete( |
554 | $wikiPage, |
555 | $user, |
556 | $protect, |
557 | $reason |
558 | ) { |
559 | $stream = 'mediawiki.page-restrictions-change'; |
560 | $eventBus = EventBus::getInstanceForStream( $stream ); |
561 | $eventFactory = $eventBus->getFactory(); |
562 | |
563 | $event = $eventFactory->createPageRestrictionsChangeEvent( |
564 | $stream, |
565 | $user, |
566 | $wikiPage->getTitle(), |
567 | $wikiPage->getId(), |
568 | $wikiPage->getRevisionRecord(), |
569 | $wikiPage->isRedirect(), |
570 | $reason, |
571 | $protect |
572 | ); |
573 | |
574 | DeferredUpdates::addCallableUpdate( |
575 | static function () use ( $eventBus, $event ) { |
576 | $eventBus->send( [ $event ] ); |
577 | } |
578 | ); |
579 | } |
580 | |
581 | /** Called after tags have been updated with the ChangeTags::updateTags function. |
582 | * |
583 | * @see https://www.mediawiki.org/wiki/Manual:Hooks/ChangeTagsAfterUpdateTags |
584 | * |
585 | * @param array $addedTags tags effectively added in the update |
586 | * @param array $removedTags tags effectively removed in the update |
587 | * @param array $prevTags tags that were present prior to the update |
588 | * @param int|null $rc_id recentchanges table id |
589 | * @param int|null $rev_id revision table id |
590 | * @param int|null $log_id logging table id |
591 | * @param string|null $params tag params |
592 | * @param RecentChange|null $rc RecentChange being tagged when the tagging accompanies |
593 | * the action, or null |
594 | * @param User|null $user User who performed the tagging when the tagging is subsequent |
595 | * to the action, or null |
596 | */ |
597 | public function onChangeTagsAfterUpdateTags( |
598 | $addedTags, |
599 | $removedTags, |
600 | $prevTags, |
601 | $rc_id, |
602 | $rev_id, |
603 | $log_id, |
604 | $params, |
605 | $rc, |
606 | $user |
607 | ) { |
608 | if ( $rev_id === null ) { |
609 | // We're only interested for revision (edits) tags for now. |
610 | return; |
611 | } |
612 | |
613 | $revisionRecord = self::getRevisionLookup()->getRevisionById( $rev_id ); |
614 | if ( $revisionRecord === null ) { |
615 | // Revision might already have been deleted, so we're not interested in tagging those. |
616 | return; |
617 | } |
618 | |
619 | $stream = 'mediawiki.revision-tags-change'; |
620 | $eventBus = EventBus::getInstanceForStream( $stream ); |
621 | $eventBusFactory = $eventBus->getFactory(); |
622 | $eventBusFactory->setCommentFormatter( MediaWikiServices::getInstance()->getCommentFormatter() ); |
623 | $event = $eventBusFactory->createRevisionTagsChangeEvent( |
624 | $stream, |
625 | $revisionRecord, |
626 | $prevTags, |
627 | $addedTags, |
628 | $removedTags, |
629 | $user |
630 | ); |
631 | |
632 | DeferredUpdates::addCallableUpdate( |
633 | static function () use ( $eventBus, $event ) { |
634 | $eventBus->send( [ $event ] ); |
635 | } |
636 | ); |
637 | } |
638 | |
639 | } |