MediaWiki master
ApiQueryRecentChanges.php
Go to the documentation of this file.
1<?php
9namespace MediaWiki\Api;
10
29use stdClass;
32use Wikimedia\Timestamp\TimestampFormat as TS;
33
44
46 private $formattedComments = [];
47
48 public function __construct(
49 ApiQuery $query,
50 string $moduleName,
51 private readonly CommentStore $commentStore,
52 private readonly RowCommentFormatter $commentFormatter,
53 private readonly SlotRoleRegistry $slotRoleRegistry,
54 private readonly UserNameUtils $userNameUtils,
55 private readonly LogFormatterFactory $logFormatterFactory,
56 private readonly ChangesListQueryFactory $changesListQueryFactory,
57 private readonly RecentChangeLookup $recentChangeLookup,
58 ) {
59 parent::__construct( $query, $moduleName, 'rc' );
60 }
61
62 private bool $fld_comment = false;
63 private bool $fld_parsedcomment = false;
64 private bool $fld_user = false;
65 private bool $fld_userid = false;
66 private bool $fld_flags = false;
67 private bool $fld_timestamp = false;
68 private bool $fld_title = false;
69 private bool $fld_ids = false;
70 private bool $fld_sizes = false;
71 private bool $fld_redirect = false;
72 private bool $fld_patrolled = false;
73 private bool $fld_loginfo = false;
74 private bool $fld_tags = false;
75 private bool $fld_sha1 = false;
76
81 public function initProperties( $prop ) {
82 $this->fld_comment = isset( $prop['comment'] );
83 $this->fld_parsedcomment = isset( $prop['parsedcomment'] );
84 $this->fld_user = isset( $prop['user'] );
85 $this->fld_userid = isset( $prop['userid'] );
86 $this->fld_flags = isset( $prop['flags'] );
87 $this->fld_timestamp = isset( $prop['timestamp'] );
88 $this->fld_title = isset( $prop['title'] );
89 $this->fld_ids = isset( $prop['ids'] );
90 $this->fld_sizes = isset( $prop['sizes'] );
91 $this->fld_redirect = isset( $prop['redirect'] );
92 $this->fld_patrolled = isset( $prop['patrolled'] );
93 $this->fld_loginfo = isset( $prop['loginfo'] );
94 $this->fld_tags = isset( $prop['tags'] );
95 $this->fld_sha1 = isset( $prop['sha1'] );
96 }
97
98 public function execute() {
99 $this->run();
100 }
101
103 public function executeGenerator( $resultPageSet ) {
104 $this->run( $resultPageSet );
105 }
106
112 public function run( $resultPageSet = null ) {
113 $user = $this->getUser();
114 /* Get the parameters of the request. */
115 $params = $this->extractRequestParams();
116
117 $query = $this->changesListQueryFactory->newQuery();
118 $query->watchlistUser( $user )
119 ->audience( $this->getAuthority() );
120
121 $sources = $this->recentChangeLookup->getAllSources();
122
123 if ( $params['dir'] === 'newer' ) {
124 $query->orderBy( ChangesListQuery::SORT_TIMESTAMP_ASC );
125 }
126 $startTimestamp = $params['start'];
127 $end = $params['end'];
128 if ( $params['continue'] !== null ) {
129 $cont = $this->parseContinueParamOrDie( $params['continue'], [ 'timestamp', 'int' ] );
130 $startTimestamp = $cont[0];
131 $startId = $cont[1];
132 } else {
133 $startId = null;
134 }
135 if ( $startTimestamp !== null ) {
136 $query->startAt( $startTimestamp, $startId );
137 }
138 if ( $end !== null ) {
139 $query->endAt( $end );
140 }
141
142 if ( $params['type'] !== null ) {
143 $sources = array_intersect( $sources,
144 $this->recentChangeLookup->convertTypeToSources( $params['type'] ) );
145 }
146
147 $title = $params['title'];
148 if ( $title !== null ) {
149 $titleObj = Title::newFromText( $title );
150 if ( $titleObj === null || $titleObj->isExternal() ) {
151 $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $title ) ] );
152 } else {
153 if ( $params['namespace'] &&
154 !$titleObj->inNamespaces( $params['namespace'] )
155 ) {
156 $this->requireMaxOneParameter( $params, 'title', 'namespace' );
157 }
158 $query->requireTitle( $titleObj );
159 }
160 } elseif ( $params['namespace'] !== null ) {
161 $query->requireNamespaces( $params['namespace'] );
162 }
163
164 if ( $params['show'] !== null ) {
165 $show = array_fill_keys( $params['show'], true );
166
167 /* Check for conflicting parameters. */
168 if ( ( isset( $show['minor'] ) && isset( $show['!minor'] ) )
169 || ( isset( $show['bot'] ) && isset( $show['!bot'] ) )
170 || ( isset( $show['anon'] ) && isset( $show['!anon'] ) )
171 || ( isset( $show['redirect'] ) && isset( $show['!redirect'] ) )
172 || ( isset( $show['patrolled'] ) && isset( $show['!patrolled'] ) )
173 || ( isset( $show['patrolled'] ) && isset( $show['unpatrolled'] ) )
174 || ( isset( $show['!patrolled'] ) && isset( $show['unpatrolled'] ) )
175 || ( isset( $show['autopatrolled'] ) && isset( $show['!autopatrolled'] ) )
176 || ( isset( $show['autopatrolled'] ) && isset( $show['unpatrolled'] ) )
177 || ( isset( $show['autopatrolled'] ) && isset( $show['!patrolled'] ) )
178 ) {
179 $this->dieWithError( 'apierror-show' );
180 }
181
182 // Check permissions
183 if ( $this->includesPatrollingFlags( $show ) && !$user->useRCPatrol() && !$user->useNPPatrol() ) {
184 $this->dieWithError( 'apierror-permissiondenied-patrolflag', 'permissiondenied' );
185 }
186
187 /* Add additional conditions to query depending upon parameters. */
188 $showActions = [
189 'minor' => [ 'require', 'minor', true ],
190 '!minor' => [ 'exclude', 'minor', true ],
191 'bot' => [ 'require', 'bot', true ],
192 '!bot' => [ 'exclude', 'bot', true ],
193 'anon' => [ 'exclude', 'named' ],
194 '!anon' => [ 'require', 'named' ],
195 'patrolled' => [ 'exclude', 'patrolled', RecentChange::PRC_UNPATROLLED ],
196 '!patrolled' => [ 'require', 'patrolled', RecentChange::PRC_UNPATROLLED ],
197 'redirect' => [ 'require', 'redirect', true ],
198 '!redirect' => [ 'exclude', 'redirect', true ],
199 'autopatrolled' => [ 'require', 'patrolled', RecentChange::PRC_AUTOPATROLLED ],
200 '!autopatrolled' => [ 'exclude', 'patrolled', RecentChange::PRC_AUTOPATROLLED ],
201 ];
202 foreach ( $show as $name => $unused ) {
203 if ( isset( $showActions[$name] ) ) {
204 $query->applyAction( ...$showActions[$name] );
205 }
206 }
207
208 if ( isset( $show['unpatrolled'] ) ) {
209 // See ChangesList::isUnpatrolled
210 if ( $user->useRCPatrol() ) {
211 $query->requirePatrolled( RecentChange::PRC_UNPATROLLED );
212 } elseif ( $user->useNPPatrol() ) {
213 $query->requirePatrolled( RecentChange::PRC_UNPATROLLED );
214 $sources = array_intersect( $sources, [ RecentChange::SRC_NEW ] );
215 }
216 }
217 }
218
219 $this->requireMaxOneParameter( $params, 'user', 'excludeuser' );
220
221 if ( $params['prop'] !== null ) {
222 $prop = array_fill_keys( $params['prop'], true );
223
224 /* Set up internal members based upon params. */
225 $this->initProperties( $prop );
226 }
227
228 if ( $this->fld_user || $this->fld_userid ) {
229 $query->rcUserFields();
230 }
231
232 if ( $params['user'] !== null ) {
233 $query->requireUser( $params['user'] );
234 }
235
236 if ( $params['excludeuser'] !== null ) {
237 $query->excludeUser( $params['excludeuser'] );
238 }
239
240 /* Add the fields we're concerned with to our query. */
241 $query->fields( [
242 'rc_id',
243 'rc_timestamp',
244 'rc_namespace',
245 'rc_title',
246 'rc_cur_id',
247 'rc_source',
248 'rc_deleted'
249 ] );
250
251 /* Determine what properties we need to display. */
252 if ( $params['prop'] !== null ) {
253 if ( $this->fld_patrolled && !$user->useRCPatrol() && !$user->useNPPatrol() ) {
254 $this->dieWithError( 'apierror-permissiondenied-patrolflag', 'permissiondenied' );
255 }
256
257 /* Add fields to our query if they are specified as a needed parameter. */
258 if ( $this->fld_ids ) {
259 $query->fields( [ 'rc_this_oldid', 'rc_last_oldid' ] );
260 }
261 if ( $this->fld_flags ) {
262 $query->fields( [ 'rc_minor', 'rc_bot' ] );
263 }
264 if ( $this->fld_sizes ) {
265 $query->fields( [ 'rc_old_len', 'rc_new_len' ] );
266 }
267 if ( $this->fld_patrolled ) {
268 $query->fields( [ 'rc_patrolled', 'rc_log_type' ] );
269 }
270 if ( $this->fld_loginfo ) {
271 $query->fields( [ 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ] );
272 }
273 if ( $this->fld_redirect ) {
274 $query->addRedirectField();
275 }
276 }
277 if ( $resultPageSet && $params['generaterevisions'] ) {
278 $query->fields( [ 'rc_this_oldid' ] );
279 }
280 if ( $this->fld_tags ) {
281 $query->addChangeTagSummaryField();
282 }
283 if ( $this->fld_sha1 ) {
284 $query->sha1Fields();
285 }
286 if ( $params['toponly'] ) {
287 $query->requireLatest();
288 }
289 if ( $params['tag'] !== null ) {
290 $query->requireChangeTags( [ $params['tag'] ] );
291 }
292
293 // Paranoia: avoid brute force searches (T19342)
294 if ( $params['user'] !== null || $params['excludeuser'] !== null ) {
295 $query->excludeDeletedUser();
296 }
297 if ( $this->getRequest()->getCheck( 'namespace' ) ) {
298 $query->excludeDeletedLogAction();
299 }
300
301 if ( $this->fld_comment || $this->fld_parsedcomment ) {
302 $query->commentFields();
303 }
304
305 if ( $params['slot'] !== null ) {
306 // Only include changes that touch page content (i.e. RC_NEW, RC_EDIT)
307 $sources = array_intersect( $sources,
308 [ RecentChange::SRC_NEW, RecentChange::SRC_EDIT ] );
309 $query->requireSlotChanged( $params['slot'] );
310 }
311
312 $query->requireSources( $sources )
313 ->limit( $params['limit'] + 1 )
314 ->maxExecutionTime( $this->getConfig()->get(
316 ->caller( __METHOD__ );
317
318 $hookData = [];
319 if ( $this->getHookContainer()->isRegistered( 'ApiQueryBaseBeforeQuery' ) ) {
320 $query->legacyMutator(
321 function ( &$tables, &$fields, &$conds, &$options, &$join_conds ) use ( &$hookData ) {
322 $this->getHookRunner()->onApiQueryBaseBeforeQuery(
323 $this, $tables, $fields, $conds,
324 $options, $join_conds, $hookData );
325 }
326 );
327 }
328
329 $count = 0;
330 /* Perform the actual query. */
331 $res = $query->fetchResult()->getResultWrapper();
332
333 $this->getHookRunner()->onApiQueryBaseAfterQuery( $this, $res, $hookData );
334
335 // Do batch queries
336 if ( $this->fld_title && $resultPageSet === null ) {
337 $this->executeGenderCacheFromResultWrapper( $res, __METHOD__, 'rc' );
338 }
339 if ( $this->fld_parsedcomment ) {
340 $this->formattedComments = $this->commentFormatter->formatItems(
341 $this->commentFormatter->rows( $res )
342 ->indexField( 'rc_id' )
343 ->commentKey( 'rc_comment' )
344 ->namespaceField( 'rc_namespace' )
345 ->titleField( 'rc_title' )
346 );
347 }
348
349 $revids = [];
350 $titles = [];
351
352 $result = $this->getResult();
353
354 /* Iterate through the rows, adding data extracted from them to our query result. */
355 foreach ( $res as $row ) {
356 if ( $count === 0 && $resultPageSet !== null ) {
357 // Set the non-continue since the list of recentchanges is
358 // prone to having entries added at the start frequently.
359 $this->getContinuationManager()->addGeneratorNonContinueParam(
360 $this, 'continue', "$row->rc_timestamp|$row->rc_id"
361 );
362 }
363 if ( ++$count > $params['limit'] ) {
364 // We've reached the one extra which shows that there are
365 // additional pages to be had. Stop here...
366 $this->setContinueEnumParameter( 'continue', "$row->rc_timestamp|$row->rc_id" );
367 break;
368 }
369
370 if ( $resultPageSet === null ) {
371 /* Extract the data from a single row. */
372 $vals = $this->extractRowInfo( $row );
373
374 /* Add that row's data to our final output. */
375 $fit = $this->processRow( $row, $vals, $hookData ) &&
376 $result->addValue( [ 'query', $this->getModuleName() ], null, $vals );
377 if ( !$fit ) {
378 $this->setContinueEnumParameter( 'continue', "$row->rc_timestamp|$row->rc_id" );
379 break;
380 }
381 } elseif ( $params['generaterevisions'] ) {
382 $revid = (int)$row->rc_this_oldid;
383 if ( $revid > 0 ) {
384 $revids[] = $revid;
385 }
386 } else {
387 $titles[] = Title::makeTitle( $row->rc_namespace, $row->rc_title );
388 }
389 }
390
391 if ( $resultPageSet === null ) {
392 /* Format the result */
393 $result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'rc' );
394 } elseif ( $params['generaterevisions'] ) {
395 $resultPageSet->populateFromRevisionIDs( $revids );
396 } else {
397 $resultPageSet->populateFromTitles( $titles );
398 }
399 }
400
407 public function extractRowInfo( $row ) {
408 /* Determine the title of the page that has been changed. */
409 $title = Title::makeTitle( $row->rc_namespace, $row->rc_title );
410 $user = $this->getUser();
411
412 /* Our output data. */
413 $vals = [];
414
415 $vals['type'] = $this->recentChangeLookup->convertSourceToType( $row->rc_source );
416 $isLog = $row->rc_source === RecentChange::SRC_LOG;
417
418 $anyHidden = false;
419
420 /* Create a new entry in the result for the title. */
421 if ( $this->fld_title || $this->fld_ids ) {
422 if ( $isLog && ( $row->rc_deleted & LogPage::DELETED_ACTION ) ) {
423 $vals['actionhidden'] = true;
424 $anyHidden = true;
425 }
426 if ( !$isLog ||
427 LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user )
428 ) {
429 if ( $this->fld_title ) {
430 ApiQueryBase::addTitleInfo( $vals, $title );
431 }
432 if ( $this->fld_ids ) {
433 $vals['pageid'] = (int)$row->rc_cur_id;
434 $vals['revid'] = (int)$row->rc_this_oldid;
435 $vals['old_revid'] = (int)$row->rc_last_oldid;
436 }
437 }
438 }
439
440 if ( $this->fld_ids ) {
441 $vals['rcid'] = (int)$row->rc_id;
442 }
443
444 /* Add user data and 'anon' flag, if user is anonymous. */
445 if ( $this->fld_user || $this->fld_userid ) {
446 if ( $row->rc_deleted & RevisionRecord::DELETED_USER ) {
447 $vals['userhidden'] = true;
448 $anyHidden = true;
449 }
450 if ( RevisionRecord::userCanBitfield( $row->rc_deleted, RevisionRecord::DELETED_USER, $user ) ) {
451 if ( $this->fld_user ) {
452 $vals['user'] = $row->rc_user_text;
453 }
454
455 if ( $this->fld_userid ) {
456 $vals['userid'] = (int)$row->rc_user;
457 }
458
459 if ( isset( $row->rc_user_text ) && $this->userNameUtils->isTemp( $row->rc_user_text ) ) {
460 $vals['temp'] = true;
461 }
462
463 if ( !$row->rc_user ) {
464 $vals['anon'] = true;
465 }
466 }
467 }
468
469 /* Add flags, such as new, minor, bot. */
470 if ( $this->fld_flags ) {
471 $vals['bot'] = (bool)$row->rc_bot;
472 $vals['new'] = $row->rc_source == RecentChange::SRC_NEW;
473 $vals['minor'] = (bool)$row->rc_minor;
474 }
475
476 /* Add sizes of each revision. (Only available on 1.10+) */
477 if ( $this->fld_sizes ) {
478 $vals['oldlen'] = (int)$row->rc_old_len;
479 $vals['newlen'] = (int)$row->rc_new_len;
480 }
481
482 /* Add the timestamp. */
483 if ( $this->fld_timestamp ) {
484 $vals['timestamp'] = wfTimestamp( TS::ISO_8601, $row->rc_timestamp );
485 }
486
487 /* Add edit summary / log summary. */
488 if ( $this->fld_comment || $this->fld_parsedcomment ) {
489 if ( $row->rc_deleted & RevisionRecord::DELETED_COMMENT ) {
490 $vals['commenthidden'] = true;
491 $anyHidden = true;
492 }
493 if ( RevisionRecord::userCanBitfield(
494 $row->rc_deleted, RevisionRecord::DELETED_COMMENT, $user
495 ) ) {
496 if ( $this->fld_comment ) {
497 $vals['comment'] = $this->commentStore->getComment( 'rc_comment', $row )->text;
498 }
499
500 if ( $this->fld_parsedcomment ) {
501 $vals['parsedcomment'] = $this->formattedComments[$row->rc_id];
502 }
503 }
504 }
505
506 if ( $this->fld_redirect ) {
507 $vals['redirect'] = (bool)$row->page_is_redirect;
508 }
509
510 /* Add the patrolled flag */
511 if ( $this->fld_patrolled ) {
512 $vals['patrolled'] = $row->rc_patrolled != RecentChange::PRC_UNPATROLLED;
513 $vals['unpatrolled'] = ChangesList::isUnpatrolled( $row, $user );
514 $vals['autopatrolled'] = $row->rc_patrolled == RecentChange::PRC_AUTOPATROLLED;
515 }
516
517 if ( $this->fld_loginfo && $row->rc_source == RecentChange::SRC_LOG ) {
518 if ( $row->rc_deleted & LogPage::DELETED_ACTION ) {
519 $vals['actionhidden'] = true;
520 $anyHidden = true;
521 }
522 if ( LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user ) ) {
523 $vals['logid'] = (int)$row->rc_logid;
524 $vals['logtype'] = $row->rc_log_type;
525 $vals['logaction'] = $row->rc_log_action;
526 $vals['logparams'] = $this->logFormatterFactory->newFromRow( $row )->formatParametersForApi();
527 }
528 }
529
530 if ( $this->fld_tags ) {
531 if ( $row->ts_tags ) {
532 $tags = explode( ',', $row->ts_tags );
533 ApiResult::setIndexedTagName( $tags, 'tag' );
534 $vals['tags'] = $tags;
535 } else {
536 $vals['tags'] = [];
537 }
538 }
539
540 if ( $this->fld_sha1 && $row->rev_slot_pairs !== null ) {
541 if ( $row->rev_deleted & RevisionRecord::DELETED_TEXT ) {
542 $vals['sha1hidden'] = true;
543 $anyHidden = true;
544 }
545 if ( RevisionRecord::userCanBitfield(
546 $row->rev_deleted, RevisionRecord::DELETED_TEXT, $user
547 ) ) {
548 $combinedBase36 = '';
549 if ( $row->rev_slot_pairs !== '' ) {
550 $items = explode( ',', $row->rev_slot_pairs );
551 $slotHashes = [];
552 foreach ( $items as $item ) {
553 $parts = explode( ':', $item );
554 $slotHashes[$parts[0]] = $parts[1];
555 }
556 ksort( $slotHashes );
557
558 $accu = null;
559 foreach ( $slotHashes as $slotHash ) {
560 $accu = $accu === null
561 ? $slotHash
562 : SlotRecord::base36Sha1( $accu . $slotHash );
563 }
564 $combinedBase36 = $accu ?? SlotRecord::base36Sha1( '' );
565 }
566
567 $vals['sha1'] = $combinedBase36 !== ''
568 ? \Wikimedia\base_convert( $combinedBase36, 36, 16, 40 )
569 : '';
570 }
571 }
572
573 if ( $anyHidden && ( $row->rc_deleted & RevisionRecord::DELETED_RESTRICTED ) ) {
574 $vals['suppressed'] = true;
575 }
576
577 return $vals;
578 }
579
584 private function includesPatrollingFlags( array $flagsArray ) {
585 return isset( $flagsArray['patrolled'] ) ||
586 isset( $flagsArray['!patrolled'] ) ||
587 isset( $flagsArray['unpatrolled'] ) ||
588 isset( $flagsArray['autopatrolled'] ) ||
589 isset( $flagsArray['!autopatrolled'] );
590 }
591
593 public function getCacheMode( $params ) {
594 if ( isset( $params['show'] ) &&
595 $this->includesPatrollingFlags( array_fill_keys( $params['show'], true ) )
596 ) {
597 return 'private';
598 }
599 if ( $this->userCanSeeRevDel() ) {
600 return 'private';
601 }
602 if ( $params['prop'] !== null && in_array( 'parsedcomment', $params['prop'] ) ) {
603 // MediaWiki\CommentFormatter\CommentFormatter::formatItems() calls wfMessage() among other things
604 return 'anon-public-user-private';
605 }
606
607 return 'public';
608 }
609
611 public function getAllowedParams() {
612 $slotRoles = $this->slotRoleRegistry->getKnownRoles();
613 sort( $slotRoles, SORT_STRING );
614
615 return [
616 'start' => [
617 ParamValidator::PARAM_TYPE => 'timestamp'
618 ],
619 'end' => [
620 ParamValidator::PARAM_TYPE => 'timestamp'
621 ],
622 'dir' => [
623 ParamValidator::PARAM_DEFAULT => 'older',
624 ParamValidator::PARAM_TYPE => [
625 'newer',
626 'older'
627 ],
628 ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
630 'newer' => 'api-help-paramvalue-direction-newer',
631 'older' => 'api-help-paramvalue-direction-older',
632 ],
633 ],
634 'namespace' => [
635 ParamValidator::PARAM_ISMULTI => true,
636 ParamValidator::PARAM_TYPE => 'namespace',
637 NamespaceDef::PARAM_EXTRA_NAMESPACES => [ NS_MEDIA, NS_SPECIAL ],
638 ],
639 'user' => [
640 ParamValidator::PARAM_TYPE => 'user',
641 UserDef::PARAM_RETURN_OBJECT => true,
642 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'id', 'interwiki' ],
643 ],
644 'excludeuser' => [
645 ParamValidator::PARAM_TYPE => 'user',
646 UserDef::PARAM_RETURN_OBJECT => true,
647 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'id', 'interwiki' ],
648 ],
649 'tag' => null,
650 'prop' => [
651 ParamValidator::PARAM_ISMULTI => true,
652 ParamValidator::PARAM_DEFAULT => 'title|timestamp|ids',
653 ParamValidator::PARAM_TYPE => [
654 'user',
655 'userid',
656 'comment',
657 'parsedcomment',
658 'flags',
659 'timestamp',
660 'title',
661 'ids',
662 'sizes',
663 'redirect',
664 'patrolled',
665 'loginfo',
666 'tags',
667 'sha1',
668 ],
670 ],
671 'show' => [
672 ParamValidator::PARAM_ISMULTI => true,
673 ParamValidator::PARAM_TYPE => [
674 'minor',
675 '!minor',
676 'bot',
677 '!bot',
678 'anon',
679 '!anon',
680 'redirect',
681 '!redirect',
682 'patrolled',
683 '!patrolled',
684 'unpatrolled',
685 'autopatrolled',
686 '!autopatrolled',
687 ]
688 ],
689 'limit' => [
690 ParamValidator::PARAM_DEFAULT => 10,
691 ParamValidator::PARAM_TYPE => 'limit',
692 IntegerDef::PARAM_MIN => 1,
693 IntegerDef::PARAM_MAX => ApiBase::LIMIT_BIG1,
694 IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_BIG2
695 ],
696 // TODO: deprecate and use rc_source directly, here and in the result
697 'type' => [
698 ParamValidator::PARAM_DEFAULT => 'edit|new|log|categorize',
699 ParamValidator::PARAM_ISMULTI => true,
700 ParamValidator::PARAM_TYPE => RecentChange::getChangeTypes()
701 ],
702 'toponly' => false,
703 'title' => null,
704 'continue' => [
705 ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
706 ],
707 'generaterevisions' => false,
708 'slot' => [
709 ParamValidator::PARAM_TYPE => $slotRoles
710 ],
711 ];
712 }
713
715 protected function getExamplesMessages() {
716 return [
717 'action=query&list=recentchanges'
718 => 'apihelp-query+recentchanges-example-simple',
719 'action=query&generator=recentchanges&grcshow=!patrolled&prop=info'
720 => 'apihelp-query+recentchanges-example-generator',
721 ];
722 }
723
725 public function getHelpUrls() {
726 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Recentchanges';
727 }
728}
729
731class_alias( ApiQueryRecentChanges::class, 'ApiQueryRecentChanges' );
const NS_SPECIAL
Definition Defines.php:40
const NS_MEDIA
Definition Defines.php:39
wfEscapeWikiText( $input)
Escapes the given text so that it may be output using addWikiText() without any linking,...
wfTimestamp( $outputtype=TS::UNIX, $ts=0)
Get a timestamp string in one of various formats.
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1506
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:542
getHookRunner()
Get an ApiHookRunner for running core API hooks.
Definition ApiBase.php:766
parseContinueParamOrDie(string $continue, array $types)
Parse the 'continue' parameter in the usual format and validate the types of each part,...
Definition ApiBase.php:1691
getResult()
Get the result object.
Definition ApiBase.php:681
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, or 'string' with PARAM_ISMULTI,...
Definition ApiBase.php:206
requireMaxOneParameter( $params,... $required)
Dies if more than one parameter from a certain set of parameters are set and not false.
Definition ApiBase.php:997
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:166
const LIMIT_BIG2
Fast query, apihighlimits limit.
Definition ApiBase.php:233
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:822
getHookContainer()
Get a HookContainer, for running extension hooks or for hook metadata.
Definition ApiBase.php:751
const LIMIT_BIG1
Fast query, standard limit.
Definition ApiBase.php:231
static addTitleInfo(&$arr, $title, $prefix='')
Add information (title and namespace) about a Title object to a result array.
executeGenderCacheFromResultWrapper(IResultWrapper $res, $fname=__METHOD__, $fieldPrefix='page')
Preprocess the result set to fill the GenderCache with the necessary information before using self::a...
userCanSeeRevDel()
Check whether the current user has permission to view revision-deleted fields.
processRow( $row, array &$data, array &$hookData)
Call the ApiQueryBaseProcessRow hook.
setContinueEnumParameter( $paramName, $paramValue)
Overridden to set the generator param if in generator mode.
A query action to enumerate the recent changes that were done to the wiki.
getExamplesMessages()
Returns usage examples for this module.Return value has query strings as keys, with values being eith...
executeGenerator( $resultPageSet)
Execute this module as a generator.
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
extractRowInfo( $row)
Extracts from a single sql row the data needed to describe one recent change.
__construct(ApiQuery $query, string $moduleName, private readonly CommentStore $commentStore, private readonly RowCommentFormatter $commentFormatter, private readonly SlotRoleRegistry $slotRoleRegistry, private readonly UserNameUtils $userNameUtils, private readonly LogFormatterFactory $logFormatterFactory, private readonly ChangesListQueryFactory $changesListQueryFactory, private readonly RecentChangeLookup $recentChangeLookup,)
initProperties( $prop)
Sets internal state to include the desired properties in the output.
getHelpUrls()
Return links to more detailed help pages about the module.1.25, returning boolean false is deprecated...
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
getCacheMode( $params)
Get the cache mode for the data generated by this module.Override this in the module subclass....
run( $resultPageSet=null)
Generates and outputs the result of this query based upon the provided parameters.
This is the main query class.
Definition ApiQuery.php:36
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
This is basically a CommentFormatter with a CommentStore dependency, allowing it to retrieve comment ...
Handle database storage of comments such as edit summaries and log reasons.
makeTitle( $linkId)
Convert a link ID to a Title.to override Title
Class to simplify the use of log pages.
Definition LogPage.php:35
A class containing constants representing the names of configuration variables.
const MaxExecutionTimeForExpensiveQueries
Name constant for the MaxExecutionTimeForExpensiveQueries setting, for use with Config::get()
Type definition for namespace types.
Type definition for user types.
Definition UserDef.php:27
Build and execute a query on the recentchanges table, optionally with joins and conditions.
Base class for lists of recent changes shown on special pages.
Utility class for creating and reading rows in the recentchanges table.
Page revision base class.
Value object representing a content slot associated with a page revision.
A registry service for SlotRoleHandlers, used to define which slot roles are available on which page.
Represents a title within MediaWiki.
Definition Title.php:69
UserNameUtils service.
Service for formatting and validating API parameters.
Type definition for integer types.