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