Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
96.12% |
446 / 464 |
|
79.41% |
27 / 34 |
CRAP | |
0.00% |
0 / 1 |
EventFactory | |
96.12% |
446 / 464 |
|
79.41% |
27 / 34 |
107 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
1 | |||
setCommentFormatter | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getUserPageURL | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
bitsToVisibilityObject | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
isHidden | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getArticleURL | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
1 | |||
createRevisionRecordAttrs | |
91.18% |
31 / 34 |
|
0.00% |
0 / 1 |
12.10 | |||
createSlotRecordsAttrs | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
3 | |||
createPerformerAttrs | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
5 | |||
createEvent | |
100.00% |
19 / 19 |
|
100.00% |
1 / 1 |
3 | |||
createMediaWikiCommonAttrs | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 | |||
createDTAttr | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
createCommonCentralNoticeAttrs | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
2 | |||
createCentralNoticeCampignSettingsAttrs | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
getUserBlocksChangeAttributes | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
2 | |||
signEvent | |
77.78% |
7 / 9 |
|
0.00% |
0 / 1 |
2.04 | |||
getEventSignature | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
createPageDeleteEvent | |
95.65% |
22 / 23 |
|
0.00% |
0 / 1 |
8 | |||
createPageUndeleteEvent | |
100.00% |
23 / 23 |
|
100.00% |
1 / 1 |
6 | |||
createPageMoveEvent | |
97.37% |
37 / 38 |
|
0.00% |
0 / 1 |
8 | |||
createResourceChangeEvent | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
createRevisionTagsChangeEvent | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
1 | |||
createRevisionVisibilityChangeEvent | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
1 | |||
createRevisionCreateEvent | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
4 | |||
createPagePropertiesChangeEvent | |
100.00% |
26 / 26 |
|
100.00% |
1 / 1 |
4 | |||
createPageLinksChangeEvent | |
100.00% |
42 / 42 |
|
100.00% |
1 / 1 |
11 | |||
createUserBlockChangeEvent | |
100.00% |
22 / 22 |
|
100.00% |
1 / 1 |
4 | |||
createPageRestrictionsChangeEvent | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
3 | |||
createRecentChangeEvent | |
90.91% |
10 / 11 |
|
0.00% |
0 / 1 |
4.01 | |||
createJobEvent | |
78.57% |
22 / 28 |
|
0.00% |
0 / 1 |
7.48 | |||
createCentralNoticeCampaignCreateEvent | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 | |||
createCentralNoticeCampaignChangeEvent | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
2 | |||
createCentralNoticeCampaignDeleteEvent | |
100.00% |
9 / 9 |
|
100.00% |
1 / 1 |
2 | |||
createRecommendationCreateEvent | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
1 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\EventBus; |
4 | |
5 | use IJobSpecification; |
6 | use MediaWiki\Block\DatabaseBlock; |
7 | use MediaWiki\Block\Restriction\Restriction; |
8 | use MediaWiki\CommentFormatter\CommentFormatter; |
9 | use MediaWiki\Config\ServiceOptions; |
10 | use MediaWiki\Content\IContentHandlerFactory; |
11 | use MediaWiki\Http\Telemetry; |
12 | use MediaWiki\Language\Language; |
13 | use MediaWiki\Linker\LinkTarget; |
14 | use MediaWiki\MediaWikiServices; |
15 | use MediaWiki\Page\PageReferenceValue; |
16 | use MediaWiki\Page\WikiPageFactory; |
17 | use MediaWiki\Revision\RevisionRecord; |
18 | use MediaWiki\Revision\RevisionSlots; |
19 | use MediaWiki\Revision\RevisionStore; |
20 | use MediaWiki\Revision\SlotRecord; |
21 | use MediaWiki\Revision\SuppressedDataException; |
22 | use MediaWiki\Title\Title; |
23 | use MediaWiki\Title\TitleFormatter; |
24 | use MediaWiki\User\UserEditTracker; |
25 | use MediaWiki\User\UserFactory; |
26 | use MediaWiki\User\UserGroupManager; |
27 | use MediaWiki\User\UserIdentity; |
28 | use MediaWiki\WikiMap\WikiMap; |
29 | use MWUnknownContentModelException; |
30 | use Psr\Log\LoggerInterface; |
31 | |
32 | /** |
33 | * Used to create events of particular types. |
34 | * |
35 | * @deprecated since EventBus 0.5.0. Use EventSerializer and specific Serializer instances instead. |
36 | */ |
37 | class EventFactory { |
38 | |
39 | public const CONSTRUCTOR_OPTIONS = [ |
40 | 'ArticlePath', |
41 | 'CanonicalServer', |
42 | 'ServerName', |
43 | 'SecretKey' |
44 | ]; |
45 | |
46 | /** @var ServiceOptions */ |
47 | private $options; |
48 | |
49 | /** @var Language */ |
50 | private $contentLanguage; |
51 | |
52 | /** @var TitleFormatter */ |
53 | private $titleFormatter; |
54 | |
55 | /** @var RevisionStore */ |
56 | private $revisionStore; |
57 | |
58 | /** @var UserGroupManager */ |
59 | private $userGroupManager; |
60 | |
61 | /** @var UserEditTracker */ |
62 | private $userEditTracker; |
63 | |
64 | /** @var UserFactory */ |
65 | private $userFactory; |
66 | |
67 | /** @var string */ |
68 | private $dbDomain; |
69 | |
70 | /** @var WikiPageFactory */ |
71 | private $wikiPageFactory; |
72 | |
73 | /** |
74 | * @var CommentFormatter|null Will be null unless set by caller with setCommentFormatter(). |
75 | */ |
76 | private ?CommentFormatter $commentFormatter = null; |
77 | |
78 | /** @var IContentHandlerFactory */ |
79 | private $contentHandlerFactory; |
80 | |
81 | /** @var LoggerInterface */ |
82 | private $logger; |
83 | |
84 | private Telemetry $telemetry; |
85 | |
86 | /** |
87 | * @param ServiceOptions $serviceOptions |
88 | * @param string $dbDomain |
89 | * @param Language $contentLanguage |
90 | * @param RevisionStore $revisionStore |
91 | * @param TitleFormatter $titleFormatter |
92 | * @param UserGroupManager $userGroupManager |
93 | * @param UserEditTracker $userEditTracker |
94 | * @param WikiPageFactory $wikiPageFactory |
95 | * @param UserFactory $userFactory |
96 | * @param IContentHandlerFactory $contentHandlerFactory |
97 | * @param LoggerInterface $logger |
98 | * @param Telemetry $telemetry |
99 | */ |
100 | public function __construct( |
101 | ServiceOptions $serviceOptions, |
102 | string $dbDomain, |
103 | Language $contentLanguage, |
104 | RevisionStore $revisionStore, |
105 | TitleFormatter $titleFormatter, |
106 | UserGroupManager $userGroupManager, |
107 | UserEditTracker $userEditTracker, |
108 | WikiPageFactory $wikiPageFactory, |
109 | UserFactory $userFactory, |
110 | IContentHandlerFactory $contentHandlerFactory, |
111 | LoggerInterface $logger, |
112 | Telemetry $telemetry |
113 | ) { |
114 | $serviceOptions->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS ); |
115 | $this->options = $serviceOptions; |
116 | $this->dbDomain = $dbDomain; |
117 | $this->contentLanguage = $contentLanguage; |
118 | $this->titleFormatter = $titleFormatter; |
119 | $this->revisionStore = $revisionStore; |
120 | $this->userGroupManager = $userGroupManager; |
121 | $this->userEditTracker = $userEditTracker; |
122 | $this->wikiPageFactory = $wikiPageFactory; |
123 | $this->userFactory = $userFactory; |
124 | $this->contentHandlerFactory = $contentHandlerFactory; |
125 | $this->logger = $logger; |
126 | $this->telemetry = $telemetry; |
127 | } |
128 | |
129 | /** |
130 | * Inject a CommentFormatter for EventFactory's use. Only needed if you need comment_html populated (T327065). |
131 | * @param CommentFormatter $commentFormatter |
132 | * @return void |
133 | */ |
134 | public function setCommentFormatter( CommentFormatter $commentFormatter ): void { |
135 | $this->commentFormatter = $commentFormatter; |
136 | } |
137 | |
138 | /** |
139 | * Creates a full user page path |
140 | * |
141 | * @param string $userName |
142 | * @return string |
143 | */ |
144 | private function getUserPageURL( $userName ) { |
145 | $prefixedUserURL = $this->contentLanguage->getNsText( NS_USER ) . ':' . $userName; |
146 | $encodedUserURL = wfUrlencode( strtr( $prefixedUserURL, ' ', '_' ) ); |
147 | // The ArticlePath contains '$1' string where the article title should appear. |
148 | return $this->options->get( 'CanonicalServer' ) . |
149 | str_replace( '$1', $encodedUserURL, $this->options->get( 'ArticlePath' ) ); |
150 | } |
151 | |
152 | /** |
153 | * Converts a revision visibility hidden bitfield to an array with keys |
154 | * of each of the possible visibility settings name mapped to a boolean. |
155 | * |
156 | * @param int $bits revision visibility bitfield |
157 | * @return array |
158 | */ |
159 | private static function bitsToVisibilityObject( $bits ) { |
160 | return [ |
161 | 'text' => !self::isHidden( $bits, RevisionRecord::DELETED_TEXT ), |
162 | 'user' => !self::isHidden( $bits, RevisionRecord::DELETED_USER ), |
163 | 'comment' => !self::isHidden( $bits, RevisionRecord::DELETED_COMMENT ), |
164 | ]; |
165 | } |
166 | |
167 | /** |
168 | * Checks if RevisionRecord::DELETED_* field is set in the $hiddenBits |
169 | * |
170 | * @param int $hiddenBits revision visibility bitfield |
171 | * @param int $field RevisionRecord::DELETED_* field to check |
172 | * @return bool |
173 | */ |
174 | private static function isHidden( $hiddenBits, $field ) { |
175 | return ( $hiddenBits & $field ) == $field; |
176 | } |
177 | |
178 | /** |
179 | * Creates a full article path |
180 | * |
181 | * @param LinkTarget $target article title object |
182 | * @return string |
183 | */ |
184 | private function getArticleURL( $target ) { |
185 | $titleURL = wfUrlencode( $this->titleFormatter->getPrefixedDBkey( $target ) ); |
186 | // The ArticlePath contains '$1' string where the article title should appear. |
187 | return $this->options->get( 'CanonicalServer' ) . |
188 | str_replace( '$1', $titleURL, $this->options->get( 'ArticlePath' ) ); |
189 | } |
190 | |
191 | /** |
192 | * Given a RevisionRecord $revision, returns an array suitable for |
193 | * use in mediawiki/revision entity schemas. |
194 | * |
195 | * @param RevisionRecord $revision |
196 | * @param UserIdentity|null $performer |
197 | * @return array |
198 | */ |
199 | private function createRevisionRecordAttrs( |
200 | RevisionRecord $revision, |
201 | ?UserIdentity $performer = null |
202 | ) { |
203 | $linkTarget = $revision->getPageAsLinkTarget(); |
204 | $attrs = [ |
205 | // Common MediaWiki entity fields |
206 | 'database' => $this->dbDomain, |
207 | |
208 | // revision entity fields |
209 | 'page_id' => $revision->getPageId(), |
210 | 'page_title' => $this->titleFormatter->getPrefixedDBkey( $linkTarget ), |
211 | 'page_namespace' => $linkTarget->getNamespace(), |
212 | 'rev_id' => $revision->getId(), |
213 | 'rev_timestamp' => self::createDTAttr( $revision->getTimestamp() ), |
214 | 'rev_sha1' => $revision->getSha1(), |
215 | 'rev_minor_edit' => $revision->isMinor(), |
216 | 'rev_len' => $revision->getSize(), |
217 | ]; |
218 | |
219 | $attrs['rev_content_model'] = $contentModel = $revision->getSlot( SlotRecord::MAIN )->getModel(); |
220 | |
221 | $contentFormat = $revision->getSlot( SlotRecord::MAIN )->getFormat(); |
222 | if ( $contentFormat === null ) { |
223 | try { |
224 | $contentFormat = $this->contentHandlerFactory->getContentHandler( $contentModel )->getDefaultFormat(); |
225 | } catch ( MWUnknownContentModelException $e ) { |
226 | // Ignore, the `rev_content_format` is not required. |
227 | } |
228 | } |
229 | if ( $contentFormat !== null ) { |
230 | $attrs['rev_content_format'] = $contentFormat; |
231 | } |
232 | |
233 | if ( $performer !== null ) { |
234 | $attrs['performer'] = $this->createPerformerAttrs( $performer ); |
235 | } |
236 | |
237 | // It is possible that the $revision object does not have any content |
238 | // at the time of RevisionRecordInserted. This might happen during |
239 | // a page restore, if the revision 'created' during the restore |
240 | // has its content hidden. |
241 | // TODO: In MCR Content::isRedirect should not be used to derive a redirect directly. |
242 | try { |
243 | $content = $revision->getContent( SlotRecord::MAIN ); |
244 | if ( $content !== null ) { |
245 | $attrs['page_is_redirect'] = $content->isRedirect(); |
246 | } else { |
247 | $attrs['page_is_redirect'] = false; |
248 | } |
249 | } catch ( SuppressedDataException $e ) { |
250 | $attrs['page_is_redirect'] = false; |
251 | } |
252 | |
253 | if ( $revision->getComment() !== null && strlen( $revision->getComment()->text ) ) { |
254 | $attrs['comment'] = $revision->getComment()->text; |
255 | if ( $this->commentFormatter ) { |
256 | $attrs['parsedcomment'] = $this->commentFormatter->format( $revision->getComment()->text ); |
257 | } |
258 | } |
259 | |
260 | // The rev_parent_id attribute is not required, but when supplied |
261 | // must have a minimum value of 1, so omit it entirely when there is no |
262 | // parent revision (i.e. page creation). |
263 | if ( $revision->getParentId() !== null && $revision->getParentId() > 0 ) { |
264 | $attrs['rev_parent_id'] = $revision->getParentId(); |
265 | } |
266 | |
267 | return $attrs; |
268 | } |
269 | |
270 | /** |
271 | * @param RevisionSlots $slots |
272 | * @return array |
273 | */ |
274 | private function createSlotRecordsAttrs( RevisionSlots $slots ): array { |
275 | $attrs = []; |
276 | foreach ( $slots->getSlots() as $slotRecord ) { |
277 | $slotAttr = [ |
278 | 'rev_slot_content_model' => $slotRecord->getModel(), |
279 | 'rev_slot_sha1' => $slotRecord->getSha1(), |
280 | 'rev_slot_size' => $slotRecord->getSize() |
281 | ]; |
282 | if ( $slotRecord->hasOrigin() ) { |
283 | // unclear if necessary to guard against missing origin in this context but since it |
284 | // might fail on unsaved content we are better safe than sorry |
285 | $slotAttr['rev_slot_origin_rev_id'] = $slotRecord->getOrigin(); |
286 | } |
287 | $attrs[$slotRecord->getRole()] = $slotAttr; |
288 | } |
289 | return $attrs; |
290 | } |
291 | |
292 | /** |
293 | * Given a UserIdentity $user, returns an array suitable for |
294 | * use as the performer JSON object in various MediaWiki |
295 | * entity schemas. |
296 | * @param UserIdentity $user |
297 | * @return array |
298 | */ |
299 | private function createPerformerAttrs( UserIdentity $user ) { |
300 | $legacyUser = $this->userFactory->newFromUserIdentity( $user ); |
301 | $performerAttrs = [ |
302 | 'user_text' => $user->getName(), |
303 | 'user_groups' => $this->userGroupManager->getUserEffectiveGroups( $user ), |
304 | 'user_is_bot' => $user->isRegistered() && $legacyUser->isBot(), |
305 | ]; |
306 | if ( $user->getId() ) { |
307 | $performerAttrs['user_id'] = $user->getId(); |
308 | } |
309 | if ( $legacyUser->getRegistration() ) { |
310 | $performerAttrs['user_registration_dt'] = |
311 | self::createDTAttr( $legacyUser->getRegistration() ); |
312 | } |
313 | if ( $user->isRegistered() ) { |
314 | $performerAttrs['user_edit_count'] = $this->userEditTracker->getUserEditCount( $user ); |
315 | } |
316 | |
317 | return $performerAttrs; |
318 | } |
319 | |
320 | /** |
321 | * Adds a meta subobject to $attrs based on uri and topic and returns it. |
322 | * |
323 | * @param string $uri |
324 | * @param string $schema |
325 | * @param string $stream |
326 | * @param array $attrs |
327 | * @param string|null $wiki wikiId if provided |
328 | * @param string|null $dt |
329 | * @return array $attrs + meta sub object |
330 | */ |
331 | public function createEvent( |
332 | $uri, |
333 | $schema, |
334 | $stream, |
335 | array $attrs, |
336 | ?string $wiki = null, |
337 | ?string $dt = null |
338 | ) { |
339 | if ( $wiki !== null ) { |
340 | $wikiRef = WikiMap::getWiki( $wiki ); |
341 | if ( $wikiRef === null ) { |
342 | $domain = $this->options->get( 'ServerName' ); |
343 | } else { |
344 | $domain = $wikiRef->getDisplayName(); |
345 | } |
346 | } else { |
347 | $domain = $this->options->get( 'ServerName' ); |
348 | } |
349 | |
350 | $gen = MediaWikiServices::getInstance()->getGlobalIdGenerator(); |
351 | $event = [ |
352 | '$schema' => $schema, |
353 | 'meta' => [ |
354 | 'uri' => $uri, |
355 | 'request_id' => $this->telemetry->getRequestId(), |
356 | 'id' => $gen->newUUIDv4(), |
357 | 'dt' => $dt ?? wfTimestamp( TS_ISO_8601 ), |
358 | 'domain' => $domain, |
359 | 'stream' => $stream, |
360 | ], |
361 | ]; |
362 | |
363 | return $event + $attrs; |
364 | } |
365 | |
366 | /** |
367 | * Creates an event fragment suitable for the fragment/mediawiki/common schema fragment. |
368 | * @param UserIdentity $user |
369 | * @return array |
370 | */ |
371 | public function createMediaWikiCommonAttrs( UserIdentity $user ): array { |
372 | return [ |
373 | 'database' => $this->dbDomain, |
374 | 'performer' => $this->createPerformerAttrs( $user ), |
375 | ]; |
376 | } |
377 | |
378 | /** |
379 | * Format a timestamp for a date-time attribute in an event. |
380 | * |
381 | * @param string $timestamp Timestamp, in a format supported by wfTimestamp() |
382 | * @return string|bool |
383 | */ |
384 | public static function createDTAttr( $timestamp ) { |
385 | return wfTimestamp( TS_ISO_8601, $timestamp ); |
386 | } |
387 | |
388 | /** |
389 | * Provides the event attributes common to all CentralNotice events. |
390 | * |
391 | * @param string $campaignName The name of the campaign affected. |
392 | * @param UserIdentity $user The user who performed the action on the campaign. |
393 | * @param string $summary Change summary provided by the user, or empty string if none |
394 | * was provided. |
395 | * @return array |
396 | */ |
397 | private function createCommonCentralNoticeAttrs( |
398 | $campaignName, |
399 | UserIdentity $user, |
400 | $summary |
401 | ) { |
402 | $attrs = [ |
403 | 'database' => $this->dbDomain, |
404 | 'performer' => $this->createPerformerAttrs( $user ), |
405 | 'campaign_name' => $campaignName |
406 | ]; |
407 | |
408 | if ( $summary ) { |
409 | $attrs[ 'summary' ] = $summary; |
410 | } |
411 | |
412 | return $attrs; |
413 | } |
414 | |
415 | /** |
416 | * Takes an array of CentralNotice campaign settings, as provided by the |
417 | * CentralNoticeCampaignChange hook, and outputs an array of settings for use in |
418 | * centralnotice/campaign events. |
419 | * |
420 | * @param array $settings |
421 | * @return array |
422 | */ |
423 | private static function createCentralNoticeCampignSettingsAttrs( array $settings ) { |
424 | return [ |
425 | 'start_dt' => self::createDTAttr( $settings[ 'start' ] ), |
426 | 'end_dt' => self::createDTAttr( $settings[ 'end' ] ), |
427 | 'enabled' => $settings[ 'enabled' ], |
428 | 'archived' => $settings[ 'archived' ], |
429 | 'banners' => $settings[ 'banners' ] |
430 | ]; |
431 | } |
432 | |
433 | /** |
434 | * Given a DatabaseBlock $block, returns an array suitable for use |
435 | * as a 'blocks' object in the user/blocks-change event schema. |
436 | * |
437 | * @param DatabaseBlock $block |
438 | * @return array |
439 | */ |
440 | private static function getUserBlocksChangeAttributes( DatabaseBlock $block ) { |
441 | $blockAttrs = [ |
442 | # Block properties are sometimes a string/int like '0'. |
443 | # Cast to int then to bool to make sure it is a proper bool. |
444 | 'name' => (bool)(int)$block->getHideName(), |
445 | 'email' => (bool)(int)$block->isEmailBlocked(), |
446 | 'user_talk' => !(bool)(int)$block->isUsertalkEditAllowed(), |
447 | 'account_create' => (bool)(int)$block->isCreateAccountBlocked(), |
448 | 'sitewide' => $block->isSitewide(), |
449 | ]; |
450 | $blockAttrs['restrictions'] = array_map( static function ( Restriction $restriction ) { |
451 | return [ |
452 | 'type' => $restriction::getType(), |
453 | 'value' => $restriction->getValue() |
454 | ]; |
455 | }, $block->getRestrictions() ); |
456 | if ( $block->getExpiry() != 'infinity' ) { |
457 | $blockAttrs['expiry_dt'] = self::createDTAttr( $block->getExpiry() ); |
458 | } |
459 | return $blockAttrs; |
460 | } |
461 | |
462 | /** |
463 | * Creates a cryptographic signature for the event |
464 | * |
465 | * @param array &$event the serialized event to sign |
466 | */ |
467 | private function signEvent( &$event ) { |
468 | // Sign the event with mediawiki secret key |
469 | $serialized_event = EventBus::serializeEvents( $event ); |
470 | if ( $serialized_event === null ) { |
471 | $event['mediawiki_signature'] = null; |
472 | return; |
473 | } |
474 | |
475 | $signature = self::getEventSignature( |
476 | $serialized_event, |
477 | $this->options->get( 'SecretKey' ) |
478 | ); |
479 | |
480 | $event['mediawiki_signature'] = $signature; |
481 | } |
482 | |
483 | /** |
484 | * @param string $serialized_event |
485 | * @param string $secretKey |
486 | * @return string |
487 | */ |
488 | public static function getEventSignature( $serialized_event, $secretKey ) { |
489 | return hash_hmac( 'sha1', $serialized_event, $secretKey ); |
490 | } |
491 | |
492 | /** |
493 | * Create a page delete event message |
494 | * @param string $stream the stream to send an event to |
495 | * @param UserIdentity|null $user |
496 | * @param int $id |
497 | * @param LinkTarget $title |
498 | * @param bool $is_redirect |
499 | * @param int $archivedRevisionCount |
500 | * @param RevisionRecord|null $headRevision |
501 | * @param string $reason |
502 | * @return array |
503 | */ |
504 | public function createPageDeleteEvent( |
505 | $stream, |
506 | ?UserIdentity $user, |
507 | $id, |
508 | LinkTarget $title, |
509 | $is_redirect, |
510 | $archivedRevisionCount, |
511 | ?RevisionRecord $headRevision, |
512 | $reason |
513 | ) { |
514 | // Create a mediawiki page delete event. |
515 | $attrs = [ |
516 | // Common MediaWiki entity fields |
517 | 'database' => $this->dbDomain, |
518 | |
519 | // page entity fields |
520 | 'page_id' => $id, |
521 | 'page_title' => $this->titleFormatter->getPrefixedDBkey( $title ), |
522 | 'page_namespace' => $title->getNamespace(), |
523 | 'page_is_redirect' => $is_redirect, |
524 | ]; |
525 | |
526 | if ( $user ) { |
527 | $attrs['performer'] = $this->createPerformerAttrs( $user ); |
528 | } |
529 | |
530 | if ( $headRevision !== null && $headRevision->getId() !== null ) { |
531 | $attrs['rev_id'] = $headRevision->getId(); |
532 | } |
533 | |
534 | // page delete specific fields: |
535 | if ( $archivedRevisionCount !== null ) { |
536 | $attrs['rev_count'] = $archivedRevisionCount; |
537 | } |
538 | |
539 | if ( $reason !== null && strlen( $reason ) ) { |
540 | $attrs['comment'] = $reason; |
541 | if ( $this->commentFormatter ) { |
542 | $attrs['parsedcomment'] = $this->commentFormatter->format( $reason, $title ); |
543 | } |
544 | } |
545 | |
546 | return $this->createEvent( |
547 | $this->getArticleURL( $title ), |
548 | '/mediawiki/page/delete/1.0.0', |
549 | $stream, |
550 | $attrs |
551 | ); |
552 | } |
553 | |
554 | /** |
555 | * Create a page undelete message |
556 | * @param string $stream the stream to send an event to |
557 | * @param UserIdentity $performer |
558 | * @param Title $title |
559 | * @param string $comment |
560 | * @param int $oldPageId |
561 | * @param RevisionRecord $restoredRevision |
562 | * @return array |
563 | */ |
564 | public function createPageUndeleteEvent( |
565 | $stream, |
566 | UserIdentity $performer, |
567 | Title $title, |
568 | $comment, |
569 | $oldPageId, |
570 | RevisionRecord $restoredRevision |
571 | ) { |
572 | // Create a mediawiki page undelete event. |
573 | $attrs = [ |
574 | // Common MediaWiki entity fields |
575 | 'database' => $this->dbDomain, |
576 | 'performer' => $this->createPerformerAttrs( $performer ), |
577 | |
578 | // page entity fields |
579 | 'page_id' => $title->getArticleID(), |
580 | 'page_title' => $this->titleFormatter->getPrefixedDBkey( $title ), |
581 | 'page_namespace' => $title->getNamespace(), |
582 | 'page_is_redirect' => $title->isRedirect(), |
583 | 'rev_id' => $restoredRevision->getId(), |
584 | ]; |
585 | |
586 | // If this page had a different id in the archive table, |
587 | // then save it as the prior_state page_id. This will |
588 | // be the page_id that the page had before it was deleted, |
589 | // which is the same as the page_id that it had while it was |
590 | // in the archive table. |
591 | // Usually page_id will be the same, but there are some historical |
592 | // edge cases where a new page_id is created as part of an undelete. |
593 | if ( $oldPageId && $oldPageId != $attrs['page_id'] ) { |
594 | // page undelete specific fields: |
595 | $attrs['prior_state'] = [ |
596 | 'page_id' => $oldPageId, |
597 | ]; |
598 | } |
599 | |
600 | if ( $comment !== null && strlen( $comment ) ) { |
601 | $attrs['comment'] = $comment; |
602 | if ( $this->commentFormatter ) { |
603 | $attrs['parsedcomment'] = $this->commentFormatter->format( $comment, $title ); |
604 | } |
605 | } |
606 | |
607 | return $this->createEvent( |
608 | $this->getArticleURL( $title ), |
609 | '/mediawiki/page/undelete/1.0.0', |
610 | $stream, |
611 | $attrs |
612 | ); |
613 | } |
614 | |
615 | /** |
616 | * @param string $stream the stream to send an event to |
617 | * @param LinkTarget $oldTitle |
618 | * @param LinkTarget $newTitle |
619 | * @param RevisionRecord $newRevision |
620 | * @param UserIdentity $user the user who made a tags change |
621 | * @param string $reason |
622 | * @param int $redirectPageId |
623 | * @return array |
624 | */ |
625 | public function createPageMoveEvent( |
626 | $stream, |
627 | LinkTarget $oldTitle, |
628 | LinkTarget $newTitle, |
629 | RevisionRecord $newRevision, |
630 | UserIdentity $user, |
631 | $reason, |
632 | $redirectPageId = 0 |
633 | ) { |
634 | // TODO: In MCR Content::isRedirect should not be used to derive a redirect directly. |
635 | $newPageIsRedirect = false; |
636 | try { |
637 | $content = $newRevision->getContent( SlotRecord::MAIN ); |
638 | if ( $content !== null ) { |
639 | $newPageIsRedirect = $content->isRedirect(); |
640 | } |
641 | } catch ( SuppressedDataException $e ) { |
642 | } |
643 | |
644 | $attrs = [ |
645 | // Common MediaWiki entity fields |
646 | 'database' => $this->dbDomain, |
647 | 'performer' => $this->createPerformerAttrs( $user ), |
648 | |
649 | // page entity fields |
650 | 'page_id' => $newRevision->getPageId(), |
651 | 'page_title' => $this->titleFormatter->getPrefixedDBkey( $newTitle ), |
652 | 'page_namespace' => $newTitle->getNamespace(), |
653 | 'page_is_redirect' => $newPageIsRedirect, |
654 | 'rev_id' => $newRevision->getId(), |
655 | |
656 | // page move specific fields: |
657 | 'prior_state' => [ |
658 | 'page_title' => $this->titleFormatter->getPrefixedDBkey( $oldTitle ), |
659 | 'page_namespace' => $oldTitle->getNamespace(), |
660 | 'rev_id' => $newRevision->getParentId(), |
661 | ], |
662 | ]; |
663 | |
664 | // If a new redirect page was created during this move, then include |
665 | // some information about it. |
666 | if ( $redirectPageId ) { |
667 | $redirectWikiPage = $this->wikiPageFactory->newFromID( $redirectPageId ); |
668 | if ( $redirectWikiPage !== null ) { |
669 | $attrs['new_redirect_page'] = [ |
670 | 'page_id' => $redirectPageId, |
671 | // Redirect pages created as part of a page move |
672 | // will have the same title and namespace that |
673 | // the target page had before the move. |
674 | 'page_title' => $attrs['prior_state']['page_title'], |
675 | 'page_namespace' => $attrs['prior_state']['page_namespace'], |
676 | 'rev_id' => $redirectWikiPage->getRevisionRecord()->getId() |
677 | ]; |
678 | } |
679 | } |
680 | |
681 | if ( $reason !== null && strlen( $reason ) ) { |
682 | $attrs['comment'] = $reason; |
683 | if ( $this->commentFormatter ) { |
684 | $attrs['parsedcomment'] = $this->commentFormatter->format( $reason, $newTitle ); |
685 | } |
686 | } |
687 | |
688 | return $this->createEvent( |
689 | $this->getArticleURL( $newTitle ), |
690 | '/mediawiki/page/move/1.0.0', |
691 | $stream, |
692 | $attrs |
693 | ); |
694 | } |
695 | |
696 | /** |
697 | * Create an resource change message |
698 | * @param string $stream the stream to send an event to |
699 | * @param LinkTarget $title |
700 | * @param array $tags |
701 | * @return array |
702 | */ |
703 | public function createResourceChangeEvent( |
704 | $stream, |
705 | LinkTarget $title, |
706 | array $tags |
707 | ) { |
708 | return $this->createEvent( |
709 | $this->getArticleURL( $title ), |
710 | '/resource_change/1.0.0', |
711 | $stream, |
712 | [ 'tags' => $tags ] |
713 | ); |
714 | } |
715 | |
716 | /** |
717 | * @param string $stream the stream to send an event to |
718 | * @param RevisionRecord $revisionRecord the revision record affected by the change. |
719 | * @param array $prevTags an array of previous tags |
720 | * @param array $addedTags an array of added tags |
721 | * @param array $removedTags an array of removed tags |
722 | * @param UserIdentity|null $user the user who made a tags change |
723 | * @return array |
724 | */ |
725 | public function createRevisionTagsChangeEvent( |
726 | $stream, |
727 | RevisionRecord $revisionRecord, |
728 | array $prevTags, |
729 | array $addedTags, |
730 | array $removedTags, |
731 | ?UserIdentity $user |
732 | ) { |
733 | $attrs = $this->createRevisionRecordAttrs( $revisionRecord, $user ); |
734 | |
735 | $newTags = array_values( |
736 | array_unique( array_diff( array_merge( $prevTags, $addedTags ), $removedTags ) ) |
737 | ); |
738 | $attrs['tags'] = $newTags; |
739 | $attrs['prior_state'] = [ 'tags' => $prevTags ]; |
740 | |
741 | return $this->createEvent( |
742 | $this->getArticleURL( $revisionRecord->getPageAsLinkTarget() ), |
743 | '/mediawiki/revision/tags-change/1.0.0', |
744 | $stream, |
745 | $attrs |
746 | ); |
747 | } |
748 | |
749 | /** |
750 | * @param string $stream the stream to send an event to |
751 | * @param RevisionRecord $revisionRecord the revision record affected by the change. |
752 | * @param UserIdentity|null $performer the user who made a tags change |
753 | * @param array $visibilityChanges |
754 | * @return array |
755 | */ |
756 | public function createRevisionVisibilityChangeEvent( |
757 | $stream, |
758 | RevisionRecord $revisionRecord, |
759 | ?UserIdentity $performer, |
760 | array $visibilityChanges |
761 | ) { |
762 | $attrs = $this->createRevisionRecordAttrs( |
763 | $revisionRecord, |
764 | $performer |
765 | ); |
766 | $attrs['visibility'] = self::bitsToVisibilityObject( $visibilityChanges['newBits'] ); |
767 | $attrs['prior_state'] = [ |
768 | 'visibility' => self::bitsToVisibilityObject( $visibilityChanges['oldBits'] ) |
769 | ]; |
770 | |
771 | return $this->createEvent( |
772 | $this->getArticleURL( $revisionRecord->getPageAsLinkTarget() ), |
773 | '/mediawiki/revision/visibility-change/1.0.0', |
774 | $stream, |
775 | $attrs |
776 | ); |
777 | } |
778 | |
779 | /** |
780 | * @param string $stream the stream to send an event to |
781 | * @param RevisionRecord $revisionRecord the revision record affected by the change. |
782 | * @return array |
783 | */ |
784 | public function createRevisionCreateEvent( |
785 | $stream, |
786 | RevisionRecord $revisionRecord |
787 | ) { |
788 | $attrs = $this->createRevisionRecordAttrs( $revisionRecord, $revisionRecord->getUser() ); |
789 | $attrs['dt'] = self::createDTAttr( $revisionRecord->getTimestamp() ); |
790 | // Only add to revision-create for now |
791 | $attrs['rev_slots'] = $this->createSlotRecordsAttrs( $revisionRecord->getSlots() ); |
792 | // The parent_revision_id attribute is not required, but when supplied |
793 | // must have a minimum value of 1, so omit it entirely when there is no |
794 | // parent revision (i.e. page creation). |
795 | $parentId = $revisionRecord->getParentId(); |
796 | if ( $parentId !== null && $parentId !== 0 ) { |
797 | $parentRev = $this->revisionStore->getRevisionById( $parentId ); |
798 | if ( $parentRev !== null ) { |
799 | $attrs['rev_content_changed'] = |
800 | $parentRev->getSha1() !== $revisionRecord->getSha1(); |
801 | } |
802 | } |
803 | |
804 | return $this->createEvent( |
805 | $this->getArticleURL( $revisionRecord->getPageAsLinkTarget() ), |
806 | '/mediawiki/revision/create/2.0.0', |
807 | $stream, |
808 | $attrs |
809 | ); |
810 | } |
811 | |
812 | /** |
813 | * @param string $stream the stream to send an event to |
814 | * @param Title $title |
815 | * @param array|null $addedProps |
816 | * @param array|null $removedProps |
817 | * @param UserIdentity|null $user the user who made a tags change |
818 | * @param int|null $revId |
819 | * @param int $pageId |
820 | * @return array |
821 | */ |
822 | public function createPagePropertiesChangeEvent( |
823 | $stream, |
824 | Title $title, |
825 | ?array $addedProps, |
826 | ?array $removedProps, |
827 | ?UserIdentity $user, |
828 | $revId, |
829 | $pageId |
830 | ) { |
831 | // Create a MediaWiki page delete event. |
832 | $attrs = [ |
833 | // Common MediaWiki entity fields |
834 | 'database' => $this->dbDomain, |
835 | |
836 | // page entity fields |
837 | 'page_id' => $pageId, |
838 | 'page_title' => $this->titleFormatter->getPrefixedDBkey( $title ), |
839 | 'page_namespace' => $title->getNamespace(), |
840 | 'page_is_redirect' => $title->isRedirect(), |
841 | 'rev_id' => $revId |
842 | ]; |
843 | |
844 | if ( $user !== null ) { |
845 | $attrs['performer'] = $this->createPerformerAttrs( $user ); |
846 | } |
847 | |
848 | if ( $addedProps ) { |
849 | $attrs['added_properties'] = array_map( |
850 | [ EventBus::class, 'replaceBinaryValues' ], |
851 | $addedProps |
852 | ); |
853 | } |
854 | |
855 | if ( $removedProps ) { |
856 | $attrs['removed_properties'] = array_map( |
857 | [ EventBus::class, 'replaceBinaryValues' ], |
858 | $removedProps |
859 | ); |
860 | } |
861 | |
862 | return $this->createEvent( |
863 | $this->getArticleURL( $title ), |
864 | '/mediawiki/page/properties-change/1.0.0', |
865 | $stream, |
866 | $attrs |
867 | ); |
868 | } |
869 | |
870 | /** |
871 | * @param string $stream the stream to send an event to |
872 | * @param Title $title |
873 | * @param array|null $addedLinks |
874 | * @param array|null $addedExternalLinks |
875 | * @param array|null $removedLinks |
876 | * @param array|null $removedExternalLinks |
877 | * @param UserIdentity|null $user the user who made a tags change |
878 | * @param int|null $revId |
879 | * @param int $pageId |
880 | * @return array |
881 | */ |
882 | public function createPageLinksChangeEvent( |
883 | $stream, |
884 | Title $title, |
885 | ?array $addedLinks, |
886 | ?array $addedExternalLinks, |
887 | ?array $removedLinks, |
888 | ?array $removedExternalLinks, |
889 | ?UserIdentity $user, |
890 | $revId, |
891 | $pageId |
892 | ) { |
893 | // Create a mediawiki page delete event. |
894 | $attrs = [ |
895 | // Common MediaWiki entity fields |
896 | 'database' => $this->dbDomain, |
897 | |
898 | // page entity fields |
899 | 'page_id' => $pageId, |
900 | 'page_title' => $this->titleFormatter->getPrefixedDBkey( $title ), |
901 | 'page_namespace' => $title->getNamespace(), |
902 | 'page_is_redirect' => $title->isRedirect(), |
903 | 'rev_id' => $revId |
904 | ]; |
905 | |
906 | if ( $user !== null ) { |
907 | $attrs['performer'] = $this->createPerformerAttrs( $user ); |
908 | } |
909 | |
910 | /** |
911 | * Extract URL encoded link and whether it's external |
912 | * @param PageReferenceValue|String $t External links are strings, internal |
913 | * links are PageReferenceValue |
914 | * @return array |
915 | */ |
916 | $getLinkData = static function ( $t ) { |
917 | if ( $t instanceof PageReferenceValue ) { |
918 | $t = Title::castFromPageReference( $t ); |
919 | $link = $t->getLinkURL(); |
920 | $isExternal = false; |
921 | } else { |
922 | $isExternal = true; |
923 | $link = $t; |
924 | } |
925 | return [ |
926 | 'link' => wfUrlencode( $link ), |
927 | 'external' => $isExternal |
928 | ]; |
929 | }; |
930 | |
931 | if ( $addedLinks || $addedExternalLinks ) { |
932 | $addedLinks = $addedLinks === null ? [] : $addedLinks; |
933 | $addedExternalLinks = $addedExternalLinks === null ? [] : $addedExternalLinks; |
934 | |
935 | $addedLinks = array_map( |
936 | $getLinkData, |
937 | array_merge( $addedLinks, $addedExternalLinks ) ); |
938 | |
939 | $attrs['added_links'] = $addedLinks; |
940 | } |
941 | |
942 | if ( $removedLinks || $removedExternalLinks ) { |
943 | $removedLinks = $removedLinks === null ? [] : $removedLinks; |
944 | $removedExternalLinks = $removedExternalLinks === null ? [] : $removedExternalLinks; |
945 | $removedLinks = array_map( |
946 | $getLinkData, |
947 | array_merge( $removedLinks, $removedExternalLinks ) ); |
948 | |
949 | $attrs['removed_links'] = $removedLinks; |
950 | } |
951 | |
952 | return $this->createEvent( |
953 | $this->getArticleURL( $title ), |
954 | '/mediawiki/page/links-change/1.0.0', |
955 | $stream, |
956 | $attrs |
957 | ); |
958 | } |
959 | |
960 | /** |
961 | * Create a user or IP block change event message |
962 | * @param string $stream the stream to send an event to |
963 | * @param UserIdentity $user |
964 | * @param DatabaseBlock $block |
965 | * @param DatabaseBlock|null $previousBlock |
966 | * @return array |
967 | */ |
968 | public function createUserBlockChangeEvent( |
969 | $stream, |
970 | UserIdentity $user, |
971 | DatabaseBlock $block, |
972 | ?DatabaseBlock $previousBlock |
973 | ) { |
974 | $attrs = [ |
975 | // Common MediaWiki entity fields: |
976 | 'database' => $this->dbDomain, |
977 | 'performer' => $this->createPerformerAttrs( $user ), |
978 | ]; |
979 | |
980 | $attrs['comment'] = $block->getReasonComment()->text; |
981 | |
982 | // user entity fields: |
983 | |
984 | // Note that, except for null, it is always safe to treat the target |
985 | // as a string; for UserIdentity objects this will return |
986 | // UserIdentity::getName() |
987 | $attrs['user_text'] = $block->getTargetName(); |
988 | |
989 | $blockTargetIdentity = $block->getTargetUserIdentity(); |
990 | // if the $blockTargetIdentity is a UserIdentity, then set user_id. |
991 | if ( $blockTargetIdentity ) { |
992 | // set user_id if the target UserIdentity has a user_id |
993 | if ( $blockTargetIdentity->getId() ) { |
994 | $attrs['user_id'] = $blockTargetIdentity->getId(); |
995 | } |
996 | |
997 | // set user_groups, all UserIdentities will have this. |
998 | $attrs['user_groups'] = $this->userGroupManager->getUserEffectiveGroups( $blockTargetIdentity ); |
999 | } |
1000 | |
1001 | // blocks-change specific fields: |
1002 | $attrs['blocks'] = self::getUserBlocksChangeAttributes( $block ); |
1003 | |
1004 | // If we had a prior block settings, emit them as prior_state.blocks. |
1005 | if ( $previousBlock ) { |
1006 | $attrs['prior_state'] = [ |
1007 | 'blocks' => self::getUserBlocksChangeAttributes( $previousBlock ) |
1008 | ]; |
1009 | } |
1010 | |
1011 | return $this->createEvent( |
1012 | $this->getUserPageURL( $block->getTargetName() ), |
1013 | '/mediawiki/user/blocks-change/1.1.0', |
1014 | $stream, |
1015 | $attrs |
1016 | ); |
1017 | } |
1018 | |
1019 | /** |
1020 | * Create a page restrictions change event message |
1021 | * @param string $stream the stream to send an event to |
1022 | * @param UserIdentity $user |
1023 | * @param LinkTarget $title |
1024 | * @param int $pageId |
1025 | * @param RevisionRecord|null $revision |
1026 | * @param bool $is_redirect |
1027 | * @param string $reason |
1028 | * @param string[] $protect |
1029 | * @return array |
1030 | */ |
1031 | public function createPageRestrictionsChangeEvent( |
1032 | $stream, |
1033 | UserIdentity $user, |
1034 | LinkTarget $title, |
1035 | $pageId, |
1036 | ?RevisionRecord $revision, |
1037 | $is_redirect, |
1038 | $reason, |
1039 | array $protect |
1040 | ) { |
1041 | // Create a MediaWiki page restrictions change event. |
1042 | $attrs = [ |
1043 | // Common MediaWiki entity fields |
1044 | 'database' => $this->dbDomain, |
1045 | 'performer' => $this->createPerformerAttrs( $user ), |
1046 | |
1047 | // page entity fields |
1048 | 'page_id' => $pageId, |
1049 | 'page_title' => $this->titleFormatter->getPrefixedDBkey( $title ), |
1050 | 'page_namespace' => $title->getNamespace(), |
1051 | 'page_is_redirect' => $is_redirect, |
1052 | |
1053 | // page restrictions change specific fields: |
1054 | 'reason' => $reason, |
1055 | 'page_restrictions' => $protect |
1056 | ]; |
1057 | |
1058 | if ( $revision !== null && $revision->getId() !== null ) { |
1059 | $attrs['rev_id'] = $revision->getId(); |
1060 | } |
1061 | |
1062 | return $this->createEvent( |
1063 | $this->getArticleURL( $title ), |
1064 | '/mediawiki/page/restrictions-change/1.0.0', |
1065 | $stream, |
1066 | $attrs |
1067 | ); |
1068 | } |
1069 | |
1070 | /** |
1071 | * Create a recent change event message |
1072 | * @param string $stream the stream to send an event to |
1073 | * @param LinkTarget $title |
1074 | * @param array $attrs |
1075 | * @return array |
1076 | */ |
1077 | public function createRecentChangeEvent( $stream, LinkTarget $title, $attrs ) { |
1078 | if ( isset( $attrs['comment'] ) && $this->commentFormatter ) { |
1079 | $attrs['parsedcomment'] = $this->commentFormatter->format( $attrs['comment'], $title ); |
1080 | } |
1081 | |
1082 | $event = $this->createEvent( |
1083 | $this->getArticleURL( $title ), |
1084 | '/mediawiki/recentchange/1.0.0', |
1085 | $stream, |
1086 | $attrs |
1087 | ); |
1088 | |
1089 | // If timestamp exists on the recentchange event (it should), |
1090 | // then use it as the meta.dt event datetime. |
1091 | if ( array_key_exists( 'timestamp', $event ) ) { |
1092 | $event['meta']['dt'] = wfTimestamp( TS_ISO_8601, $event['timestamp'] ); |
1093 | } |
1094 | |
1095 | return $event; |
1096 | } |
1097 | |
1098 | /** |
1099 | * Creates an event representing a job specification. |
1100 | * @param string $stream the stream to send an event to |
1101 | * @param string $wiki wikiId |
1102 | * @param IJobSpecification $job the job specification |
1103 | * @return array |
1104 | */ |
1105 | public function createJobEvent( |
1106 | $stream, |
1107 | $wiki, |
1108 | IJobSpecification $job |
1109 | ) { |
1110 | $attrs = [ |
1111 | 'database' => $wiki ?: $this->dbDomain, |
1112 | 'type' => $job->getType(), |
1113 | ]; |
1114 | |
1115 | if ( $job->getReleaseTimestamp() !== null ) { |
1116 | $attrs['delay_until'] = wfTimestamp( TS_ISO_8601, $job->getReleaseTimestamp() ); |
1117 | } |
1118 | |
1119 | if ( $job->ignoreDuplicates() ) { |
1120 | $attrs['sha1'] = sha1( serialize( $job->getDeduplicationInfo() ) ); |
1121 | } |
1122 | |
1123 | $params = $job->getParams(); |
1124 | |
1125 | if ( isset( $params['rootJobTimestamp'] ) && isset( $params['rootJobSignature'] ) ) { |
1126 | $attrs['root_event'] = [ |
1127 | 'signature' => $params['rootJobSignature'], |
1128 | 'dt' => wfTimestamp( TS_ISO_8601, $params['rootJobTimestamp'] ) |
1129 | ]; |
1130 | } |
1131 | |
1132 | $attrs['params'] = $params; |
1133 | |
1134 | // Deprecated, not used. To be removed from the schema. (T221368) |
1135 | $url = 'https://placeholder.invalid/wiki/Special:Badtitle'; |
1136 | |
1137 | $event = $this->createEvent( |
1138 | $url, |
1139 | '/mediawiki/job/1.0.0', |
1140 | $stream, |
1141 | $attrs, |
1142 | $wiki |
1143 | ); |
1144 | |
1145 | // If the job provides a requestId - use it, otherwise try to get one ourselves |
1146 | if ( isset( $event['params']['requestId'] ) ) { |
1147 | $event['meta']['request_id'] = $event['params']['requestId']; |
1148 | } else { |
1149 | $event['meta']['request_id'] = $this->telemetry->getRequestId(); |
1150 | } |
1151 | |
1152 | $this->signEvent( $event ); |
1153 | |
1154 | return $event; |
1155 | } |
1156 | |
1157 | /** |
1158 | * @param string $stream |
1159 | * @param string $campaignName |
1160 | * @param UserIdentity $user |
1161 | * @param array $settings |
1162 | * @param string $summary |
1163 | * @param string $campaignUrl |
1164 | * @return array |
1165 | */ |
1166 | public function createCentralNoticeCampaignCreateEvent( |
1167 | $stream, |
1168 | $campaignName, |
1169 | UserIdentity $user, |
1170 | array $settings, |
1171 | $summary, |
1172 | $campaignUrl |
1173 | ) { |
1174 | $attrs = $this->createCommonCentralNoticeAttrs( $campaignName, $user, $summary ); |
1175 | $attrs += self::createCentralNoticeCampignSettingsAttrs( $settings ); |
1176 | |
1177 | return $this->createEvent( |
1178 | $campaignUrl, |
1179 | '/mediawiki/centralnotice/campaign/create/1.0.0', |
1180 | $stream, |
1181 | $attrs |
1182 | ); |
1183 | } |
1184 | |
1185 | /** |
1186 | * @param string $stream |
1187 | * @param string $campaignName |
1188 | * @param UserIdentity $user |
1189 | * @param array $settings |
1190 | * @param array $priorState |
1191 | * @param string $summary |
1192 | * @param string $campaignUrl |
1193 | * @return array |
1194 | */ |
1195 | public function createCentralNoticeCampaignChangeEvent( |
1196 | $stream, |
1197 | $campaignName, |
1198 | UserIdentity $user, |
1199 | array $settings, |
1200 | array $priorState, |
1201 | $summary, |
1202 | $campaignUrl |
1203 | ) { |
1204 | $attrs = $this->createCommonCentralNoticeAttrs( $campaignName, $user, $summary ); |
1205 | |
1206 | $attrs += self::createCentralNoticeCampignSettingsAttrs( $settings ); |
1207 | $attrs[ 'prior_state' ] = |
1208 | $priorState ? self::createCentralNoticeCampignSettingsAttrs( $priorState ) : []; |
1209 | |
1210 | return $this->createEvent( |
1211 | $campaignUrl, |
1212 | '/mediawiki/centralnotice/campaign/change/1.0.0', |
1213 | $stream, |
1214 | $attrs |
1215 | ); |
1216 | } |
1217 | |
1218 | /** |
1219 | * @param string $stream |
1220 | * @param string $campaignName |
1221 | * @param UserIdentity $user |
1222 | * @param array $priorState |
1223 | * @param string $summary |
1224 | * @param string $campaignUrl |
1225 | * @return array |
1226 | */ |
1227 | public function createCentralNoticeCampaignDeleteEvent( |
1228 | $stream, |
1229 | $campaignName, |
1230 | UserIdentity $user, |
1231 | array $priorState, |
1232 | $summary, |
1233 | $campaignUrl |
1234 | ) { |
1235 | $attrs = $this->createCommonCentralNoticeAttrs( $campaignName, $user, $summary ); |
1236 | // As of 2019-06-07 the $beginSettings are *never* set in \Campaign::removeCampaignByName() |
1237 | // in the CentralNotice extension where the CentralNoticeCampaignChange hook is fired! |
1238 | $attrs[ 'prior_state' ] = |
1239 | $priorState ? self::createCentralNoticeCampignSettingsAttrs( $priorState ) : []; |
1240 | |
1241 | return $this->createEvent( |
1242 | $campaignUrl, |
1243 | '/mediawiki/centralnotice/campaign/delete/1.0.0', |
1244 | $stream, |
1245 | $attrs |
1246 | ); |
1247 | } |
1248 | |
1249 | /** |
1250 | * Creates a mediawiki/revision/recommendation-create event. Called by other extensions (for |
1251 | * now, just GrowthExperiments) whenever they generate recommendations; the event will be used |
1252 | * to keep the search infrastructure informed about available recommendations. |
1253 | * @param string $stream |
1254 | * @param string $recommendationType A type, such as 'link' or 'image'. |
1255 | * @param RevisionRecord $revisionRecord The revision which the recommendation is based on. |
1256 | * @return array |
1257 | */ |
1258 | public function createRecommendationCreateEvent( |
1259 | $stream, |
1260 | $recommendationType, |
1261 | RevisionRecord $revisionRecord |
1262 | ) { |
1263 | $attrs = $this->createRevisionRecordAttrs( $revisionRecord, $revisionRecord->getUser() ); |
1264 | $attrs['recommendation_type'] = $recommendationType; |
1265 | |
1266 | return $this->createEvent( |
1267 | $this->getArticleURL( $revisionRecord->getPageAsLinkTarget() ), |
1268 | '/mediawiki/revision/recommendation-create/1.0.0', |
1269 | $stream, |
1270 | $attrs |
1271 | ); |
1272 | } |
1273 | } |