MediaWiki REL1_37
ApiQueryRecentChanges.php
Go to the documentation of this file.
1<?php
28
36
39
42
45
48
57 public function __construct(
58 ApiQuery $query,
59 $moduleName,
64 ) {
65 parent::__construct( $query, $moduleName, 'rc' );
66 $this->commentStore = $commentStore;
67 $this->changeTagDefStore = $changeTagDefStore;
68 $this->slotRoleStore = $slotRoleStore;
69 $this->slotRoleRegistry = $slotRoleRegistry;
70 }
71
72 private $fld_comment = false, $fld_parsedcomment = false, $fld_user = false, $fld_userid = false,
73 $fld_flags = false, $fld_timestamp = false, $fld_title = false, $fld_ids = false,
74 $fld_sizes = false, $fld_redirect = false, $fld_patrolled = false, $fld_loginfo = false,
75 $fld_tags = false, $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
102 public function executeGenerator( $resultPageSet ) {
103 $this->run( $resultPageSet );
104 }
105
111 public function run( $resultPageSet = null ) {
112 $user = $this->getUser();
113 /* Get the parameters of the request. */
114 $params = $this->extractRequestParams();
115
116 /* Build our basic query. Namely, something along the lines of:
117 * SELECT * FROM recentchanges WHERE rc_timestamp > $start
118 * AND rc_timestamp < $end AND rc_namespace = $namespace
119 */
120 $this->addTables( 'recentchanges' );
121 $this->addTimestampWhereRange( 'rc_timestamp', $params['dir'], $params['start'], $params['end'] );
122
123 if ( $params['continue'] !== null ) {
124 $cont = explode( '|', $params['continue'] );
125 $this->dieContinueUsageIf( count( $cont ) != 2 );
126 $db = $this->getDB();
127 $timestamp = $db->addQuotes( $db->timestamp( $cont[0] ) );
128 $id = (int)$cont[1];
129 $this->dieContinueUsageIf( $id != $cont[1] );
130 $op = $params['dir'] === 'older' ? '<' : '>';
131 $this->addWhere(
132 "rc_timestamp $op $timestamp OR " .
133 "(rc_timestamp = $timestamp AND " .
134 "rc_id $op= $id)"
135 );
136 }
137
138 $order = $params['dir'] === 'older' ? 'DESC' : 'ASC';
139 $this->addOption( 'ORDER BY', [
140 "rc_timestamp $order",
141 "rc_id $order",
142 ] );
143
144 $this->addWhereFld( 'rc_namespace', $params['namespace'] );
145
146 if ( $params['type'] !== null ) {
147 try {
148 $this->addWhereFld( 'rc_type', RecentChange::parseToRCType( $params['type'] ) );
149 } catch ( Exception $e ) {
150 ApiBase::dieDebug( __METHOD__, $e->getMessage() );
151 }
152 }
153
154 $title = $params['title'];
155 if ( $title !== null ) {
156 $titleObj = Title::newFromText( $title );
157 if ( $titleObj === null ) {
158 $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $title ) ] );
159 }
160 $this->addWhereFld( 'rc_namespace', $titleObj->getNamespace() );
161 $this->addWhereFld( 'rc_title', $titleObj->getDBkey() );
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 ) ) {
184 if ( !$user->useRCPatrol() && !$user->useNPPatrol() ) {
185 $this->dieWithError( 'apierror-permissiondenied-patrolflag', 'permissiondenied' );
186 }
187 }
188
189 /* Add additional conditions to query depending upon parameters. */
190 $this->addWhereIf( 'rc_minor = 0', isset( $show['!minor'] ) );
191 $this->addWhereIf( 'rc_minor != 0', isset( $show['minor'] ) );
192 $this->addWhereIf( 'rc_bot = 0', isset( $show['!bot'] ) );
193 $this->addWhereIf( 'rc_bot != 0', isset( $show['bot'] ) );
194 if ( isset( $show['anon'] ) || isset( $show['!anon'] ) ) {
195 $this->addTables( 'actor', 'actor' );
196 $this->addJoinConds( [ 'actor' => [ 'JOIN', 'actor_id=rc_actor' ] ] );
197 $this->addWhereIf(
198 'actor_user IS NULL', isset( $show['anon'] )
199 );
200 $this->addWhereIf(
201 'actor_user IS NOT NULL', isset( $show['!anon'] )
202 );
203 }
204 $this->addWhereIf( 'rc_patrolled = 0', isset( $show['!patrolled'] ) );
205 $this->addWhereIf( 'rc_patrolled != 0', isset( $show['patrolled'] ) );
206 $this->addWhereIf( 'page_is_redirect = 1', isset( $show['redirect'] ) );
207
208 if ( isset( $show['unpatrolled'] ) ) {
209 // See ChangesList::isUnpatrolled
210 if ( $user->useRCPatrol() ) {
211 $this->addWhere( 'rc_patrolled = ' . RecentChange::PRC_UNPATROLLED );
212 } elseif ( $user->useNPPatrol() ) {
213 $this->addWhere( 'rc_patrolled = ' . RecentChange::PRC_UNPATROLLED );
214 $this->addWhereFld( 'rc_type', RC_NEW );
215 }
216 }
217
218 $this->addWhereIf(
219 'rc_patrolled != ' . RecentChange::PRC_AUTOPATROLLED,
220 isset( $show['!autopatrolled'] )
221 );
222 $this->addWhereIf(
223 'rc_patrolled = ' . RecentChange::PRC_AUTOPATROLLED,
224 isset( $show['autopatrolled'] )
225 );
226
227 // Don't throw log entries out the window here
228 $this->addWhereIf(
229 'page_is_redirect = 0 OR page_is_redirect IS NULL',
230 isset( $show['!redirect'] )
231 );
232 }
233
234 $this->requireMaxOneParameter( $params, 'user', 'excludeuser' );
235
236 if ( $params['prop'] !== null ) {
237 $prop = array_fill_keys( $params['prop'], true );
238
239 /* Set up internal members based upon params. */
240 $this->initProperties( $prop );
241 }
242
243 if ( $this->fld_user
244 || $this->fld_userid
245 || $params['user'] !== null
246 || $params['excludeuser'] !== null
247 ) {
248 $this->addTables( 'actor', 'actor' );
249 $this->addFields( [ 'actor_name', 'actor_user', 'rc_actor' ] );
250 $this->addJoinConds( [ 'actor' => [ 'JOIN', 'actor_id=rc_actor' ] ] );
251 }
252
253 if ( $params['user'] !== null ) {
254 $this->addWhereFld( 'actor_name', $params['user'] );
255 }
256
257 if ( $params['excludeuser'] !== null ) {
258 $this->addWhere( 'actor_name<>' . $this->getDB()->addQuotes( $params['excludeuser'] ) );
259 }
260
261 /* Add the fields we're concerned with to our query. */
262 $this->addFields( [
263 'rc_id',
264 'rc_timestamp',
265 'rc_namespace',
266 'rc_title',
267 'rc_cur_id',
268 'rc_type',
269 'rc_deleted'
270 ] );
271
272 $showRedirects = false;
273 /* Determine what properties we need to display. */
274 if ( $params['prop'] !== null ) {
275 if ( $this->fld_patrolled && !$user->useRCPatrol() && !$user->useNPPatrol() ) {
276 $this->dieWithError( 'apierror-permissiondenied-patrolflag', 'permissiondenied' );
277 }
278
279 /* Add fields to our query if they are specified as a needed parameter. */
280 $this->addFieldsIf( [ 'rc_this_oldid', 'rc_last_oldid' ], $this->fld_ids );
281 $this->addFieldsIf( [ 'rc_minor', 'rc_type', 'rc_bot' ], $this->fld_flags );
282 $this->addFieldsIf( [ 'rc_old_len', 'rc_new_len' ], $this->fld_sizes );
283 $this->addFieldsIf( [ 'rc_patrolled', 'rc_log_type' ], $this->fld_patrolled );
284 $this->addFieldsIf(
285 [ 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ],
286 $this->fld_loginfo
287 );
288 $showRedirects = $this->fld_redirect || isset( $show['redirect'] )
289 || isset( $show['!redirect'] );
290 }
291 $this->addFieldsIf( [ 'rc_this_oldid' ],
292 $resultPageSet && $params['generaterevisions'] );
293
294 if ( $this->fld_tags ) {
295 $this->addFields( [ 'ts_tags' => ChangeTags::makeTagSummarySubquery( 'recentchanges' ) ] );
296 }
297
298 if ( $this->fld_sha1 ) {
299 $this->addTables( 'revision' );
300 $this->addJoinConds( [ 'revision' => [ 'LEFT JOIN',
301 [ 'rc_this_oldid=rev_id' ] ] ] );
302 $this->addFields( [ 'rev_sha1', 'rev_deleted' ] );
303 }
304
305 if ( $params['toponly'] || $showRedirects ) {
306 $this->addTables( 'page' );
307 $this->addJoinConds( [ 'page' => [ 'LEFT JOIN',
308 [ 'rc_namespace=page_namespace', 'rc_title=page_title' ] ] ] );
309 $this->addFields( 'page_is_redirect' );
310
311 if ( $params['toponly'] ) {
312 $this->addWhere( 'rc_this_oldid = page_latest' );
313 }
314 }
315
316 if ( $params['tag'] !== null ) {
317 $this->addTables( 'change_tag' );
318 $this->addJoinConds( [ 'change_tag' => [ 'JOIN', [ 'rc_id=ct_rc_id' ] ] ] );
319 try {
320 $this->addWhereFld( 'ct_tag_id', $this->changeTagDefStore->getId( $params['tag'] ) );
321 } catch ( NameTableAccessException $exception ) {
322 // Return nothing.
323 $this->addWhere( '1=0' );
324 }
325 }
326
327 // Paranoia: avoid brute force searches (T19342)
328 if ( $params['user'] !== null || $params['excludeuser'] !== null ) {
329 if ( !$this->getAuthority()->isAllowed( 'deletedhistory' ) ) {
330 $bitmask = RevisionRecord::DELETED_USER;
331 } elseif ( !$this->getAuthority()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
332 $bitmask = RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED;
333 } else {
334 $bitmask = 0;
335 }
336 if ( $bitmask ) {
337 $this->addWhere( $this->getDB()->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask" );
338 }
339 }
340 if ( $this->getRequest()->getCheck( 'namespace' ) ) {
341 // LogPage::DELETED_ACTION hides the affected page, too.
342 if ( !$this->getAuthority()->isAllowed( 'deletedhistory' ) ) {
343 $bitmask = LogPage::DELETED_ACTION;
344 } elseif ( !$this->getAuthority()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
346 } else {
347 $bitmask = 0;
348 }
349 if ( $bitmask ) {
350 $this->addWhere( $this->getDB()->makeList( [
351 'rc_type != ' . RC_LOG,
352 $this->getDB()->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask",
353 ], LIST_OR ) );
354 }
355 }
356
357 if ( $this->fld_comment || $this->fld_parsedcomment ) {
358 $commentQuery = $this->commentStore->getJoin( 'rc_comment' );
359 $this->addTables( $commentQuery['tables'] );
360 $this->addFields( $commentQuery['fields'] );
361 $this->addJoinConds( $commentQuery['joins'] );
362 }
363
364 if ( $params['slot'] !== null ) {
365 try {
366 $slotId = $this->slotRoleStore->getId( $params['slot'] );
367 } catch ( \Exception $e ) {
368 $slotId = null;
369 }
370
371 $this->addTables( [
372 'slot' => 'slots', 'parent_slot' => 'slots'
373 ] );
374 $this->addJoinConds( [
375 'slot' => [ 'LEFT JOIN', [
376 'rc_this_oldid = slot.slot_revision_id',
377 'slot.slot_role_id' => $slotId,
378 ] ],
379 'parent_slot' => [ 'LEFT JOIN', [
380 'rc_last_oldid = parent_slot.slot_revision_id',
381 'parent_slot.slot_role_id' => $slotId,
382 ] ]
383 ] );
384 // Detecting whether the slot has been touched as follows:
385 // 1. if slot_origin=slot_revision_id then the slot has been newly created or edited
386 // with this revision
387 // 2. otherwise if the content of a slot is different to the content of its parent slot,
388 // then the content of the slot has been changed in this revision
389 // (probably by a revert)
390 $this->addWhere(
391 'slot.slot_origin = slot.slot_revision_id OR ' .
392 'slot.slot_content_id != parent_slot.slot_content_id OR ' .
393 '(slot.slot_content_id IS NULL AND parent_slot.slot_content_id IS NOT NULL) OR ' .
394 '(slot.slot_content_id IS NOT NULL AND parent_slot.slot_content_id IS NULL)'
395 );
396 // Only include changes that touch page content (i.e. RC_NEW, RC_EDIT)
397 $changeTypes = RecentChange::parseToRCType(
398 array_intersect( $params['type'], [ 'new', 'edit' ] )
399 );
400 if ( count( $changeTypes ) ) {
401 $this->addWhereFld( 'rc_type', $changeTypes );
402 } else {
403 // Calling $this->addWhere() with an empty array does nothing, so explicitly
404 // add an unsatisfiable condition
405 $this->addWhere( 'rc_type IS NULL' );
406 }
407 }
408
409 $this->addOption( 'LIMIT', $params['limit'] + 1 );
410 $this->addOption(
411 'MAX_EXECUTION_TIME',
412 $this->getConfig()->get( 'MaxExecutionTimeForExpensiveQueries' )
413 );
414
415 $hookData = [];
416 $count = 0;
417 /* Perform the actual query. */
418 $res = $this->select( __METHOD__, [], $hookData );
419
420 if ( $this->fld_title && $resultPageSet === null ) {
421 $this->executeGenderCacheFromResultWrapper( $res, __METHOD__, 'rc' );
422 }
423
424 $revids = [];
425 $titles = [];
426
427 $result = $this->getResult();
428
429 /* Iterate through the rows, adding data extracted from them to our query result. */
430 foreach ( $res as $row ) {
431 if ( $count === 0 && $resultPageSet !== null ) {
432 // Set the non-continue since the list of recentchanges is
433 // prone to having entries added at the start frequently.
434 $this->getContinuationManager()->addGeneratorNonContinueParam(
435 $this, 'continue', "$row->rc_timestamp|$row->rc_id"
436 );
437 }
438 if ( ++$count > $params['limit'] ) {
439 // We've reached the one extra which shows that there are
440 // additional pages to be had. Stop here...
441 $this->setContinueEnumParameter( 'continue', "$row->rc_timestamp|$row->rc_id" );
442 break;
443 }
444
445 if ( $resultPageSet === null ) {
446 /* Extract the data from a single row. */
447 $vals = $this->extractRowInfo( $row );
448
449 /* Add that row's data to our final output. */
450 $fit = $this->processRow( $row, $vals, $hookData ) &&
451 $result->addValue( [ 'query', $this->getModuleName() ], null, $vals );
452 if ( !$fit ) {
453 $this->setContinueEnumParameter( 'continue', "$row->rc_timestamp|$row->rc_id" );
454 break;
455 }
456 } elseif ( $params['generaterevisions'] ) {
457 $revid = (int)$row->rc_this_oldid;
458 if ( $revid > 0 ) {
459 $revids[] = $revid;
460 }
461 } else {
462 $titles[] = Title::makeTitle( $row->rc_namespace, $row->rc_title );
463 }
464 }
465
466 if ( $resultPageSet === null ) {
467 /* Format the result */
468 $result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'rc' );
469 } elseif ( $params['generaterevisions'] ) {
470 $resultPageSet->populateFromRevisionIDs( $revids );
471 } else {
472 $resultPageSet->populateFromTitles( $titles );
473 }
474 }
475
482 public function extractRowInfo( $row ) {
483 /* Determine the title of the page that has been changed. */
484 $title = Title::makeTitle( $row->rc_namespace, $row->rc_title );
485 $user = $this->getUser();
486
487 /* Our output data. */
488 $vals = [];
489
490 $type = (int)$row->rc_type;
491 $vals['type'] = RecentChange::parseFromRCType( $type );
492
493 $anyHidden = false;
494
495 /* Create a new entry in the result for the title. */
496 if ( $this->fld_title || $this->fld_ids ) {
497 if ( $type === RC_LOG && ( $row->rc_deleted & LogPage::DELETED_ACTION ) ) {
498 $vals['actionhidden'] = true;
499 $anyHidden = true;
500 }
501 if ( $type !== RC_LOG ||
502 LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user )
503 ) {
504 if ( $this->fld_title ) {
506 }
507 if ( $this->fld_ids ) {
508 $vals['pageid'] = (int)$row->rc_cur_id;
509 $vals['revid'] = (int)$row->rc_this_oldid;
510 $vals['old_revid'] = (int)$row->rc_last_oldid;
511 }
512 }
513 }
514
515 if ( $this->fld_ids ) {
516 $vals['rcid'] = (int)$row->rc_id;
517 }
518
519 /* Add user data and 'anon' flag, if user is anonymous. */
520 if ( $this->fld_user || $this->fld_userid ) {
521 if ( $row->rc_deleted & RevisionRecord::DELETED_USER ) {
522 $vals['userhidden'] = true;
523 $anyHidden = true;
524 }
525 if ( RevisionRecord::userCanBitfield( $row->rc_deleted, RevisionRecord::DELETED_USER, $user ) ) {
526 if ( $this->fld_user ) {
527 $vals['user'] = $row->actor_name;
528 }
529
530 if ( $this->fld_userid ) {
531 $vals['userid'] = (int)$row->actor_user;
532 }
533
534 if ( !$row->actor_user ) {
535 $vals['anon'] = true;
536 }
537 }
538 }
539
540 /* Add flags, such as new, minor, bot. */
541 if ( $this->fld_flags ) {
542 $vals['bot'] = (bool)$row->rc_bot;
543 $vals['new'] = $row->rc_type == RC_NEW;
544 $vals['minor'] = (bool)$row->rc_minor;
545 }
546
547 /* Add sizes of each revision. (Only available on 1.10+) */
548 if ( $this->fld_sizes ) {
549 $vals['oldlen'] = (int)$row->rc_old_len;
550 $vals['newlen'] = (int)$row->rc_new_len;
551 }
552
553 /* Add the timestamp. */
554 if ( $this->fld_timestamp ) {
555 $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->rc_timestamp );
556 }
557
558 /* Add edit summary / log summary. */
559 if ( $this->fld_comment || $this->fld_parsedcomment ) {
560 if ( $row->rc_deleted & RevisionRecord::DELETED_COMMENT ) {
561 $vals['commenthidden'] = true;
562 $anyHidden = true;
563 }
564 if ( RevisionRecord::userCanBitfield(
565 $row->rc_deleted, RevisionRecord::DELETED_COMMENT, $user
566 ) ) {
567 $comment = $this->commentStore->getComment( 'rc_comment', $row )->text;
568 if ( $this->fld_comment ) {
569 $vals['comment'] = $comment;
570 }
571
572 if ( $this->fld_parsedcomment ) {
573 $vals['parsedcomment'] = Linker::formatComment( $comment, $title );
574 }
575 }
576 }
577
578 if ( $this->fld_redirect ) {
579 $vals['redirect'] = (bool)$row->page_is_redirect;
580 }
581
582 /* Add the patrolled flag */
583 if ( $this->fld_patrolled ) {
584 $vals['patrolled'] = $row->rc_patrolled != RecentChange::PRC_UNPATROLLED;
585 $vals['unpatrolled'] = ChangesList::isUnpatrolled( $row, $user );
586 $vals['autopatrolled'] = $row->rc_patrolled == RecentChange::PRC_AUTOPATROLLED;
587 }
588
589 if ( $this->fld_loginfo && $row->rc_type == RC_LOG ) {
590 if ( $row->rc_deleted & LogPage::DELETED_ACTION ) {
591 $vals['actionhidden'] = true;
592 $anyHidden = true;
593 }
594 if ( LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user ) ) {
595 $vals['logid'] = (int)$row->rc_logid;
596 $vals['logtype'] = $row->rc_log_type;
597 $vals['logaction'] = $row->rc_log_action;
598 $vals['logparams'] = LogFormatter::newFromRow( $row )->formatParametersForApi();
599 }
600 }
601
602 if ( $this->fld_tags ) {
603 if ( $row->ts_tags ) {
604 $tags = explode( ',', $row->ts_tags );
605 ApiResult::setIndexedTagName( $tags, 'tag' );
606 $vals['tags'] = $tags;
607 } else {
608 $vals['tags'] = [];
609 }
610 }
611
612 if ( $this->fld_sha1 && $row->rev_sha1 !== null ) {
613 if ( $row->rev_deleted & RevisionRecord::DELETED_TEXT ) {
614 $vals['sha1hidden'] = true;
615 $anyHidden = true;
616 }
617 if ( RevisionRecord::userCanBitfield(
618 $row->rev_deleted, RevisionRecord::DELETED_TEXT, $user
619 ) ) {
620 if ( $row->rev_sha1 !== '' ) {
621 $vals['sha1'] = Wikimedia\base_convert( $row->rev_sha1, 36, 16, 40 );
622 } else {
623 $vals['sha1'] = '';
624 }
625 }
626 }
627
628 if ( $anyHidden && ( $row->rc_deleted & RevisionRecord::DELETED_RESTRICTED ) ) {
629 $vals['suppressed'] = true;
630 }
631
632 return $vals;
633 }
634
639 private function includesPatrollingFlags( array $flagsArray ) {
640 return isset( $flagsArray['patrolled'] ) ||
641 isset( $flagsArray['!patrolled'] ) ||
642 isset( $flagsArray['unpatrolled'] ) ||
643 isset( $flagsArray['autopatrolled'] ) ||
644 isset( $flagsArray['!autopatrolled'] );
645 }
646
647 public function getCacheMode( $params ) {
648 if ( isset( $params['show'] ) &&
649 $this->includesPatrollingFlags( array_fill_keys( $params['show'], true ) )
650 ) {
651 return 'private';
652 }
653 if ( $this->userCanSeeRevDel() ) {
654 return 'private';
655 }
656 if ( $params['prop'] !== null && in_array( 'parsedcomment', $params['prop'] ) ) {
657 // formatComment() calls wfMessage() among other things
658 return 'anon-public-user-private';
659 }
660
661 return 'public';
662 }
663
664 public function getAllowedParams() {
665 $slotRoles = $this->slotRoleRegistry->getKnownRoles();
666 sort( $slotRoles, SORT_STRING );
667
668 return [
669 'start' => [
670 ApiBase::PARAM_TYPE => 'timestamp'
671 ],
672 'end' => [
673 ApiBase::PARAM_TYPE => 'timestamp'
674 ],
675 'dir' => [
676 ApiBase::PARAM_DFLT => 'older',
678 'newer',
679 'older'
680 ],
681 ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
682 ],
683 'namespace' => [
685 ApiBase::PARAM_TYPE => 'namespace',
687 ],
688 'user' => [
689 ApiBase::PARAM_TYPE => 'user',
690 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'id', 'interwiki' ],
691 ],
692 'excludeuser' => [
693 ApiBase::PARAM_TYPE => 'user',
694 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'id', 'interwiki' ],
695 ],
696 'tag' => null,
697 'prop' => [
699 ApiBase::PARAM_DFLT => 'title|timestamp|ids',
701 'user',
702 'userid',
703 'comment',
704 'parsedcomment',
705 'flags',
706 'timestamp',
707 'title',
708 'ids',
709 'sizes',
710 'redirect',
711 'patrolled',
712 'loginfo',
713 'tags',
714 'sha1',
715 ],
717 ],
718 'show' => [
721 'minor',
722 '!minor',
723 'bot',
724 '!bot',
725 'anon',
726 '!anon',
727 'redirect',
728 '!redirect',
729 'patrolled',
730 '!patrolled',
731 'unpatrolled',
732 'autopatrolled',
733 '!autopatrolled',
734 ]
735 ],
736 'limit' => [
738 ApiBase::PARAM_TYPE => 'limit',
742 ],
743 'type' => [
744 ApiBase::PARAM_DFLT => 'edit|new|log|categorize',
746 ApiBase::PARAM_TYPE => RecentChange::getChangeTypes()
747 ],
748 'toponly' => false,
749 'title' => null,
750 'continue' => [
751 ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
752 ],
753 'generaterevisions' => false,
754 'slot' => [
755 ApiBase::PARAM_TYPE => $slotRoles
756 ],
757 ];
758 }
759
760 protected function getExamplesMessages() {
761 return [
762 'action=query&list=recentchanges'
763 => 'apihelp-query+recentchanges-example-simple',
764 'action=query&generator=recentchanges&grcshow=!patrolled&prop=info'
765 => 'apihelp-query+recentchanges-example-generator',
766 ];
767 }
768
769 public function getHelpUrls() {
770 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Recentchanges';
771 }
772}
getAuthority()
const RC_NEW
Definition Defines.php:116
const NS_SPECIAL
Definition Defines.php:53
const LIST_OR
Definition Defines.php:46
const RC_LOG
Definition Defines.php:117
const NS_MEDIA
Definition Defines.php:52
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1436
const PARAM_MAX2
Definition ApiBase.php:89
const PARAM_MAX
Definition ApiBase.php:85
dieContinueUsageIf( $condition)
Die with the 'badcontinue' error.
Definition ApiBase.php:1620
static dieDebug( $method, $message)
Internal code errors should be reported with this method.
Definition ApiBase.php:1633
const PARAM_TYPE
Definition ApiBase.php:81
const PARAM_DFLT
Definition ApiBase.php:73
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, this is an array mapping those values to $msg...
Definition ApiBase.php:195
const PARAM_MIN
Definition ApiBase.php:93
const LIMIT_BIG1
Fast query, standard limit.
Definition ApiBase.php:220
requireMaxOneParameter( $params,... $required)
Die if more than one of a certain set of parameters is set and not false.
Definition ApiBase.php:936
getResult()
Get the result object.
Definition ApiBase.php:628
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:764
const PARAM_EXTRA_NAMESPACES
Definition ApiBase.php:121
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:162
const LIMIT_BIG2
Fast query, apihighlimits limit.
Definition ApiBase.php:222
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:497
getContinuationManager()
Definition ApiBase.php:662
const PARAM_ISMULTI
Definition ApiBase.php:77
static addTitleInfo(&$arr, $title, $prefix='')
Add information (title and namespace) about a Title object to a result array.
processRow( $row, array &$data, array &$hookData)
Call the ApiQueryBaseProcessRow hook.
addWhereIf( $value, $condition)
Same as addWhere(), but add the WHERE clauses only if a condition is met.
addFields( $value)
Add a set of fields to select to the internal array.
addOption( $name, $value=null)
Add an option such as LIMIT or USE INDEX.
addTables( $tables, $alias=null)
Add a set of tables to the internal array.
addTimestampWhereRange( $field, $dir, $start, $end, $sort=true)
Add a WHERE clause corresponding to a range, similar to addWhereRange, but converts $start and $end t...
getDB()
Get the Query database connection (read-only)
executeGenderCacheFromResultWrapper(IResultWrapper $res, $fname=__METHOD__, $fieldPrefix='page')
Preprocess the result set to fill the GenderCache with the necessary information before using self::a...
select( $method, $extraQuery=[], array &$hookData=null)
Execute a SELECT query based on the values in the internal arrays.
addFieldsIf( $value, $condition)
Same as addFields(), but add the fields only if a condition is met.
addJoinConds( $join_conds)
Add a set of JOIN conditions to the internal array.
addWhereFld( $field, $value)
Equivalent to addWhere( [ $field => $value ] )
addWhere( $value)
Add a set of WHERE clauses to the internal array.
userCanSeeRevDel()
Check whether the current user has permission to view revision-deleted fields.
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.
SlotRoleRegistry $slotRoleRegistry
includesPatrollingFlags(array $flagsArray)
__construct(ApiQuery $query, $moduleName, CommentStore $commentStore, NameTableStore $changeTagDefStore, NameTableStore $slotRoleStore, SlotRoleRegistry $slotRoleRegistry)
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
initProperties( $prop)
Sets internal state to include the desired properties in the output.
getExamplesMessages()
Returns usage examples for this module.
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
executeGenerator( $resultPageSet)
Execute this module as a generator.
run( $resultPageSet=null)
Generates and outputs the result of this query based upon the provided parameters.
getHelpUrls()
Return links to more detailed help pages about the module.
getCacheMode( $params)
Get the cache mode for the data generated by this module.
extractRowInfo( $row)
Extracts from a single sql row the data needed to describe one recent change.
This is the main query class.
Definition ApiQuery.php:37
static makeTagSummarySubquery( $tables)
Make the tag summary subquery based on the given tables and return it.
Handle database storage of comments such as edit summaries and log reasons.
static formatComment( $comment, $title=null, $local=false, $wikiId=null)
This function is called by all recent changes variants, by the page history, and by the user contribu...
Definition Linker.php:1372
static newFromRow( $row)
Handy shortcut for constructing a formatter directly from database row.
const DELETED_RESTRICTED
Definition LogPage.php:42
const DELETED_ACTION
Definition LogPage.php:39
Type definition for user types.
Definition UserDef.php:25
Page revision base class.
A registry service for SlotRoleHandlers, used to define which slot roles are available on which page.
Exception representing a failure to look up a row from a name table.