MediaWiki 1.42.0
ApiQueryRecentChanges.php
Go to the documentation of this file.
1<?php
36
44
45 private CommentStore $commentStore;
46 private RowCommentFormatter $commentFormatter;
47 private NameTableStore $changeTagDefStore;
48 private NameTableStore $slotRoleStore;
49 private SlotRoleRegistry $slotRoleRegistry;
50 private UserNameUtils $userNameUtils;
51
52 private $formattedComments = [];
53
64 public function __construct(
65 ApiQuery $query,
66 $moduleName,
67 CommentStore $commentStore,
68 RowCommentFormatter $commentFormatter,
69 NameTableStore $changeTagDefStore,
70 NameTableStore $slotRoleStore,
71 SlotRoleRegistry $slotRoleRegistry,
72 UserNameUtils $userNameUtils
73 ) {
74 parent::__construct( $query, $moduleName, 'rc' );
75 $this->commentStore = $commentStore;
76 $this->commentFormatter = $commentFormatter;
77 $this->changeTagDefStore = $changeTagDefStore;
78 $this->slotRoleStore = $slotRoleStore;
79 $this->slotRoleRegistry = $slotRoleRegistry;
80 $this->userNameUtils = $userNameUtils;
81 }
82
83 private $fld_comment = false, $fld_parsedcomment = false, $fld_user = false, $fld_userid = false,
84 $fld_flags = false, $fld_timestamp = false, $fld_title = false, $fld_ids = false,
85 $fld_sizes = false, $fld_redirect = false, $fld_patrolled = false, $fld_loginfo = false,
86 $fld_tags = false, $fld_sha1 = false;
87
92 public function initProperties( $prop ) {
93 $this->fld_comment = isset( $prop['comment'] );
94 $this->fld_parsedcomment = isset( $prop['parsedcomment'] );
95 $this->fld_user = isset( $prop['user'] );
96 $this->fld_userid = isset( $prop['userid'] );
97 $this->fld_flags = isset( $prop['flags'] );
98 $this->fld_timestamp = isset( $prop['timestamp'] );
99 $this->fld_title = isset( $prop['title'] );
100 $this->fld_ids = isset( $prop['ids'] );
101 $this->fld_sizes = isset( $prop['sizes'] );
102 $this->fld_redirect = isset( $prop['redirect'] );
103 $this->fld_patrolled = isset( $prop['patrolled'] );
104 $this->fld_loginfo = isset( $prop['loginfo'] );
105 $this->fld_tags = isset( $prop['tags'] );
106 $this->fld_sha1 = isset( $prop['sha1'] );
107 }
108
109 public function execute() {
110 $this->run();
111 }
112
113 public function executeGenerator( $resultPageSet ) {
114 $this->run( $resultPageSet );
115 }
116
122 public function run( $resultPageSet = null ) {
123 $user = $this->getUser();
124 /* Get the parameters of the request. */
125 $params = $this->extractRequestParams();
126
127 /* Build our basic query. Namely, something along the lines of:
128 * SELECT * FROM recentchanges WHERE rc_timestamp > $start
129 * AND rc_timestamp < $end AND rc_namespace = $namespace
130 */
131 $this->addTables( 'recentchanges' );
132 $this->addTimestampWhereRange( 'rc_timestamp', $params['dir'], $params['start'], $params['end'] );
133
134 if ( $params['continue'] !== null ) {
135 $cont = $this->parseContinueParamOrDie( $params['continue'], [ 'timestamp', 'int' ] );
136 $db = $this->getDB();
137 $op = $params['dir'] === 'older' ? '<=' : '>=';
138 $this->addWhere( $db->buildComparison( $op, [
139 'rc_timestamp' => $db->timestamp( $cont[0] ),
140 'rc_id' => $cont[1],
141 ] ) );
142 }
143
144 $order = $params['dir'] === 'older' ? 'DESC' : 'ASC';
145 $this->addOption( 'ORDER BY', [
146 "rc_timestamp $order",
147 "rc_id $order",
148 ] );
149
150 if ( $params['type'] !== null ) {
151 $this->addWhereFld( 'rc_type', RecentChange::parseToRCType( $params['type'] ) );
152 }
153
154 $title = $params['title'];
155 if ( $title !== null ) {
156 $titleObj = Title::newFromText( $title );
157 if ( $titleObj === null || $titleObj->isExternal() ) {
158 $this->dieWithError( [ 'apierror-invalidtitle', wfEscapeWikiText( $title ) ] );
159 } elseif ( $params['namespace'] && !in_array( $titleObj->getNamespace(), $params['namespace'] ) ) {
160 $this->requireMaxOneParameter( $params, 'title', 'namespace' );
161 }
162 $this->addWhereFld( 'rc_namespace', $titleObj->getNamespace() );
163 $this->addWhereFld( 'rc_title', $titleObj->getDBkey() );
164 } else {
165 $this->addWhereFld( 'rc_namespace', $params['namespace'] );
166 }
167
168 if ( $params['show'] !== null ) {
169 $show = array_fill_keys( $params['show'], true );
170
171 /* Check for conflicting parameters. */
172 if ( ( isset( $show['minor'] ) && isset( $show['!minor'] ) )
173 || ( isset( $show['bot'] ) && isset( $show['!bot'] ) )
174 || ( isset( $show['anon'] ) && isset( $show['!anon'] ) )
175 || ( isset( $show['redirect'] ) && isset( $show['!redirect'] ) )
176 || ( isset( $show['patrolled'] ) && isset( $show['!patrolled'] ) )
177 || ( isset( $show['patrolled'] ) && isset( $show['unpatrolled'] ) )
178 || ( isset( $show['!patrolled'] ) && isset( $show['unpatrolled'] ) )
179 || ( isset( $show['autopatrolled'] ) && isset( $show['!autopatrolled'] ) )
180 || ( isset( $show['autopatrolled'] ) && isset( $show['unpatrolled'] ) )
181 || ( isset( $show['autopatrolled'] ) && isset( $show['!patrolled'] ) )
182 ) {
183 $this->dieWithError( 'apierror-show' );
184 }
185
186 // Check permissions
187 if ( $this->includesPatrollingFlags( $show ) && !$user->useRCPatrol() && !$user->useNPPatrol() ) {
188 $this->dieWithError( 'apierror-permissiondenied-patrolflag', 'permissiondenied' );
189 }
190
191 /* Add additional conditions to query depending upon parameters. */
192 $this->addWhereIf( [ 'rc_minor' => 0 ], isset( $show['!minor'] ) );
193 $this->addWhereIf( 'rc_minor != 0', isset( $show['minor'] ) );
194 $this->addWhereIf( [ 'rc_bot' => 0 ], isset( $show['!bot'] ) );
195 $this->addWhereIf( 'rc_bot != 0', isset( $show['bot'] ) );
196 if ( isset( $show['anon'] ) || isset( $show['!anon'] ) ) {
197 $this->addTables( 'actor', 'actor' );
198 $this->addJoinConds( [ 'actor' => [ 'JOIN', 'actor_id=rc_actor' ] ] );
199 $this->addWhereIf(
200 [ 'actor_user' => null ], isset( $show['anon'] )
201 );
202 $this->addWhereIf(
203 'actor_user IS NOT NULL', isset( $show['!anon'] )
204 );
205 }
206 $this->addWhereIf( [ 'rc_patrolled' => 0 ], isset( $show['!patrolled'] ) );
207 $this->addWhereIf( 'rc_patrolled != 0', isset( $show['patrolled'] ) );
208 $this->addWhereIf( [ 'page_is_redirect' => 1 ], isset( $show['redirect'] ) );
209
210 if ( isset( $show['unpatrolled'] ) ) {
211 // See ChangesList::isUnpatrolled
212 if ( $user->useRCPatrol() ) {
213 $this->addWhereFld( 'rc_patrolled', RecentChange::PRC_UNPATROLLED );
214 } elseif ( $user->useNPPatrol() ) {
215 $this->addWhereFld( 'rc_patrolled', RecentChange::PRC_UNPATROLLED );
216 $this->addWhereFld( 'rc_type', RC_NEW );
217 }
218 }
219
220 $this->addWhereIf(
221 'rc_patrolled != ' . RecentChange::PRC_AUTOPATROLLED,
222 isset( $show['!autopatrolled'] )
223 );
224 $this->addWhereIf(
225 [ 'rc_patrolled' => RecentChange::PRC_AUTOPATROLLED ],
226 isset( $show['autopatrolled'] )
227 );
228
229 // Don't throw log entries out the window here
230 $this->addWhereIf(
231 [ 'page_is_redirect' => [ 0, null ] ],
232 isset( $show['!redirect'] )
233 );
234 }
235
236 $this->requireMaxOneParameter( $params, 'user', 'excludeuser' );
237
238 if ( $params['prop'] !== null ) {
239 $prop = array_fill_keys( $params['prop'], true );
240
241 /* Set up internal members based upon params. */
242 $this->initProperties( $prop );
243 }
244
245 if ( $this->fld_user
246 || $this->fld_userid
247 || $params['user'] !== null
248 || $params['excludeuser'] !== null
249 ) {
250 $this->addTables( 'actor', 'actor' );
251 $this->addFields( [ 'actor_name', 'actor_user', 'rc_actor' ] );
252 $this->addJoinConds( [ 'actor' => [ 'JOIN', 'actor_id=rc_actor' ] ] );
253 }
254
255 if ( $params['user'] !== null ) {
256 $this->addWhereFld( 'actor_name', $params['user'] );
257 }
258
259 if ( $params['excludeuser'] !== null ) {
260 $this->addWhere( 'actor_name<>' . $this->getDB()->addQuotes( $params['excludeuser'] ) );
261 }
262
263 /* Add the fields we're concerned with to our query. */
264 $this->addFields( [
265 'rc_id',
266 'rc_timestamp',
267 'rc_namespace',
268 'rc_title',
269 'rc_cur_id',
270 'rc_type',
271 'rc_deleted'
272 ] );
273
274 $showRedirects = false;
275 /* Determine what properties we need to display. */
276 if ( $params['prop'] !== null ) {
277 if ( $this->fld_patrolled && !$user->useRCPatrol() && !$user->useNPPatrol() ) {
278 $this->dieWithError( 'apierror-permissiondenied-patrolflag', 'permissiondenied' );
279 }
280
281 /* Add fields to our query if they are specified as a needed parameter. */
282 $this->addFieldsIf( [ 'rc_this_oldid', 'rc_last_oldid' ], $this->fld_ids );
283 $this->addFieldsIf( [ 'rc_minor', 'rc_type', 'rc_bot' ], $this->fld_flags );
284 $this->addFieldsIf( [ 'rc_old_len', 'rc_new_len' ], $this->fld_sizes );
285 $this->addFieldsIf( [ 'rc_patrolled', 'rc_log_type' ], $this->fld_patrolled );
286 $this->addFieldsIf(
287 [ 'rc_logid', 'rc_log_type', 'rc_log_action', 'rc_params' ],
288 $this->fld_loginfo
289 );
290 $showRedirects = $this->fld_redirect || isset( $show['redirect'] )
291 || isset( $show['!redirect'] );
292 }
293 $this->addFieldsIf( [ 'rc_this_oldid' ],
294 $resultPageSet && $params['generaterevisions'] );
295
296 if ( $this->fld_tags ) {
297 $this->addFields( [ 'ts_tags' => ChangeTags::makeTagSummarySubquery( 'recentchanges' ) ] );
298 }
299
300 if ( $this->fld_sha1 ) {
301 $this->addTables( 'revision' );
302 $this->addJoinConds( [ 'revision' => [ 'LEFT JOIN',
303 [ 'rc_this_oldid=rev_id' ] ] ] );
304 $this->addFields( [ 'rev_sha1', 'rev_deleted' ] );
305 }
306
307 if ( $params['toponly'] || $showRedirects ) {
308 $this->addTables( 'page' );
309 $this->addJoinConds( [ 'page' => [ 'LEFT JOIN',
310 [ 'rc_namespace=page_namespace', 'rc_title=page_title' ] ] ] );
311 $this->addFields( 'page_is_redirect' );
312
313 if ( $params['toponly'] ) {
314 $this->addWhere( 'rc_this_oldid = page_latest' );
315 }
316 }
317
318 if ( $params['tag'] !== null ) {
319 $this->addTables( 'change_tag' );
320 $this->addJoinConds( [ 'change_tag' => [ 'JOIN', [ 'rc_id=ct_rc_id' ] ] ] );
321 try {
322 $this->addWhereFld( 'ct_tag_id', $this->changeTagDefStore->getId( $params['tag'] ) );
323 } catch ( NameTableAccessException $exception ) {
324 // Return nothing.
325 $this->addWhere( '1=0' );
326 }
327 }
328
329 // Paranoia: avoid brute force searches (T19342)
330 if ( $params['user'] !== null || $params['excludeuser'] !== null ) {
331 if ( !$this->getAuthority()->isAllowed( 'deletedhistory' ) ) {
332 $bitmask = RevisionRecord::DELETED_USER;
333 } elseif ( !$this->getAuthority()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
334 $bitmask = RevisionRecord::DELETED_USER | RevisionRecord::DELETED_RESTRICTED;
335 } else {
336 $bitmask = 0;
337 }
338 if ( $bitmask ) {
339 $this->addWhere( $this->getDB()->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask" );
340 }
341 }
342 if ( $this->getRequest()->getCheck( 'namespace' ) ) {
343 // LogPage::DELETED_ACTION hides the affected page, too.
344 if ( !$this->getAuthority()->isAllowed( 'deletedhistory' ) ) {
345 $bitmask = LogPage::DELETED_ACTION;
346 } elseif ( !$this->getAuthority()->isAllowedAny( 'suppressrevision', 'viewsuppressed' ) ) {
347 $bitmask = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED;
348 } else {
349 $bitmask = 0;
350 }
351 if ( $bitmask ) {
352 $this->addWhere( $this->getDB()->makeList( [
353 'rc_type != ' . RC_LOG,
354 $this->getDB()->bitAnd( 'rc_deleted', $bitmask ) . " != $bitmask",
355 ], LIST_OR ) );
356 }
357 }
358
359 if ( $this->fld_comment || $this->fld_parsedcomment ) {
360 $commentQuery = $this->commentStore->getJoin( 'rc_comment' );
361 $this->addTables( $commentQuery['tables'] );
362 $this->addFields( $commentQuery['fields'] );
363 $this->addJoinConds( $commentQuery['joins'] );
364 }
365
366 if ( $params['slot'] !== null ) {
367 try {
368 $slotId = $this->slotRoleStore->getId( $params['slot'] );
369 } catch ( Exception $e ) {
370 $slotId = null;
371 }
372
373 $this->addTables( [
374 'slot' => 'slots', 'parent_slot' => 'slots'
375 ] );
376 $this->addJoinConds( [
377 'slot' => [ 'LEFT JOIN', [
378 'rc_this_oldid = slot.slot_revision_id',
379 'slot.slot_role_id' => $slotId,
380 ] ],
381 'parent_slot' => [ 'LEFT JOIN', [
382 'rc_last_oldid = parent_slot.slot_revision_id',
383 'parent_slot.slot_role_id' => $slotId,
384 ] ]
385 ] );
386 // Detecting whether the slot has been touched as follows:
387 // 1. if slot_origin=slot_revision_id then the slot has been newly created or edited
388 // with this revision
389 // 2. otherwise if the content of a slot is different to the content of its parent slot,
390 // then the content of the slot has been changed in this revision
391 // (probably by a revert)
392 $this->addWhere(
393 'slot.slot_origin = slot.slot_revision_id OR ' .
394 'slot.slot_content_id != parent_slot.slot_content_id OR ' .
395 '(slot.slot_content_id IS NULL AND parent_slot.slot_content_id IS NOT NULL) OR ' .
396 '(slot.slot_content_id IS NOT NULL AND parent_slot.slot_content_id IS NULL)'
397 );
398 // Only include changes that touch page content (i.e. RC_NEW, RC_EDIT)
399 $changeTypes = RecentChange::parseToRCType(
400 array_intersect( $params['type'], [ 'new', 'edit' ] )
401 );
402 if ( count( $changeTypes ) ) {
403 $this->addWhereFld( 'rc_type', $changeTypes );
404 } else {
405 // Calling $this->addWhere() with an empty array does nothing, so explicitly
406 // add an unsatisfiable condition
407 $this->addWhere( [ 'rc_type' => null ] );
408 }
409 }
410
411 $this->addOption( 'LIMIT', $params['limit'] + 1 );
412 $this->addOption(
413 'MAX_EXECUTION_TIME',
414 $this->getConfig()->get( MainConfigNames::MaxExecutionTimeForExpensiveQueries )
415 );
416
417 $hookData = [];
418 $count = 0;
419 /* Perform the actual query. */
420 $res = $this->select( __METHOD__, [], $hookData );
421
422 // Do batch queries
423 if ( $this->fld_title && $resultPageSet === null ) {
424 $this->executeGenderCacheFromResultWrapper( $res, __METHOD__, 'rc' );
425 }
426 if ( $this->fld_parsedcomment ) {
427 $this->formattedComments = $this->commentFormatter->formatItems(
428 $this->commentFormatter->rows( $res )
429 ->indexField( 'rc_id' )
430 ->commentKey( 'rc_comment' )
431 ->namespaceField( 'rc_namespace' )
432 ->titleField( 'rc_title' )
433 );
434 }
435
436 $revids = [];
437 $titles = [];
438
439 $result = $this->getResult();
440
441 /* Iterate through the rows, adding data extracted from them to our query result. */
442 foreach ( $res as $row ) {
443 if ( $count === 0 && $resultPageSet !== null ) {
444 // Set the non-continue since the list of recentchanges is
445 // prone to having entries added at the start frequently.
446 $this->getContinuationManager()->addGeneratorNonContinueParam(
447 $this, 'continue', "$row->rc_timestamp|$row->rc_id"
448 );
449 }
450 if ( ++$count > $params['limit'] ) {
451 // We've reached the one extra which shows that there are
452 // additional pages to be had. Stop here...
453 $this->setContinueEnumParameter( 'continue', "$row->rc_timestamp|$row->rc_id" );
454 break;
455 }
456
457 if ( $resultPageSet === null ) {
458 /* Extract the data from a single row. */
459 $vals = $this->extractRowInfo( $row );
460
461 /* Add that row's data to our final output. */
462 $fit = $this->processRow( $row, $vals, $hookData ) &&
463 $result->addValue( [ 'query', $this->getModuleName() ], null, $vals );
464 if ( !$fit ) {
465 $this->setContinueEnumParameter( 'continue', "$row->rc_timestamp|$row->rc_id" );
466 break;
467 }
468 } elseif ( $params['generaterevisions'] ) {
469 $revid = (int)$row->rc_this_oldid;
470 if ( $revid > 0 ) {
471 $revids[] = $revid;
472 }
473 } else {
474 $titles[] = Title::makeTitle( $row->rc_namespace, $row->rc_title );
475 }
476 }
477
478 if ( $resultPageSet === null ) {
479 /* Format the result */
480 $result->addIndexedTagName( [ 'query', $this->getModuleName() ], 'rc' );
481 } elseif ( $params['generaterevisions'] ) {
482 $resultPageSet->populateFromRevisionIDs( $revids );
483 } else {
484 $resultPageSet->populateFromTitles( $titles );
485 }
486 }
487
494 public function extractRowInfo( $row ) {
495 /* Determine the title of the page that has been changed. */
496 $title = Title::makeTitle( $row->rc_namespace, $row->rc_title );
497 $user = $this->getUser();
498
499 /* Our output data. */
500 $vals = [];
501
502 $type = (int)$row->rc_type;
503 $vals['type'] = RecentChange::parseFromRCType( $type );
504
505 $anyHidden = false;
506
507 /* Create a new entry in the result for the title. */
508 if ( $this->fld_title || $this->fld_ids ) {
509 if ( $type === RC_LOG && ( $row->rc_deleted & LogPage::DELETED_ACTION ) ) {
510 $vals['actionhidden'] = true;
511 $anyHidden = true;
512 }
513 if ( $type !== RC_LOG ||
514 LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user )
515 ) {
516 if ( $this->fld_title ) {
517 ApiQueryBase::addTitleInfo( $vals, $title );
518 }
519 if ( $this->fld_ids ) {
520 $vals['pageid'] = (int)$row->rc_cur_id;
521 $vals['revid'] = (int)$row->rc_this_oldid;
522 $vals['old_revid'] = (int)$row->rc_last_oldid;
523 }
524 }
525 }
526
527 if ( $this->fld_ids ) {
528 $vals['rcid'] = (int)$row->rc_id;
529 }
530
531 /* Add user data and 'anon' flag, if user is anonymous. */
532 if ( $this->fld_user || $this->fld_userid ) {
533 if ( $row->rc_deleted & RevisionRecord::DELETED_USER ) {
534 $vals['userhidden'] = true;
535 $anyHidden = true;
536 }
537 if ( RevisionRecord::userCanBitfield( $row->rc_deleted, RevisionRecord::DELETED_USER, $user ) ) {
538 if ( $this->fld_user ) {
539 $vals['user'] = $row->actor_name;
540 }
541
542 if ( $this->fld_userid ) {
543 $vals['userid'] = (int)$row->actor_user;
544 }
545
546 if ( isset( $row->actor_name ) && $this->userNameUtils->isTemp( $row->actor_name ) ) {
547 $vals['temp'] = true;
548 }
549
550 if ( !$row->actor_user ) {
551 $vals['anon'] = true;
552 }
553 }
554 }
555
556 /* Add flags, such as new, minor, bot. */
557 if ( $this->fld_flags ) {
558 $vals['bot'] = (bool)$row->rc_bot;
559 $vals['new'] = $row->rc_type == RC_NEW;
560 $vals['minor'] = (bool)$row->rc_minor;
561 }
562
563 /* Add sizes of each revision. (Only available on 1.10+) */
564 if ( $this->fld_sizes ) {
565 $vals['oldlen'] = (int)$row->rc_old_len;
566 $vals['newlen'] = (int)$row->rc_new_len;
567 }
568
569 /* Add the timestamp. */
570 if ( $this->fld_timestamp ) {
571 $vals['timestamp'] = wfTimestamp( TS_ISO_8601, $row->rc_timestamp );
572 }
573
574 /* Add edit summary / log summary. */
575 if ( $this->fld_comment || $this->fld_parsedcomment ) {
576 if ( $row->rc_deleted & RevisionRecord::DELETED_COMMENT ) {
577 $vals['commenthidden'] = true;
578 $anyHidden = true;
579 }
580 if ( RevisionRecord::userCanBitfield(
581 $row->rc_deleted, RevisionRecord::DELETED_COMMENT, $user
582 ) ) {
583 if ( $this->fld_comment ) {
584 $vals['comment'] = $this->commentStore->getComment( 'rc_comment', $row )->text;
585 }
586
587 if ( $this->fld_parsedcomment ) {
588 $vals['parsedcomment'] = $this->formattedComments[$row->rc_id];
589 }
590 }
591 }
592
593 if ( $this->fld_redirect ) {
594 $vals['redirect'] = (bool)$row->page_is_redirect;
595 }
596
597 /* Add the patrolled flag */
598 if ( $this->fld_patrolled ) {
599 $vals['patrolled'] = $row->rc_patrolled != RecentChange::PRC_UNPATROLLED;
600 $vals['unpatrolled'] = ChangesList::isUnpatrolled( $row, $user );
601 $vals['autopatrolled'] = $row->rc_patrolled == RecentChange::PRC_AUTOPATROLLED;
602 }
603
604 if ( $this->fld_loginfo && $row->rc_type == RC_LOG ) {
605 if ( $row->rc_deleted & LogPage::DELETED_ACTION ) {
606 $vals['actionhidden'] = true;
607 $anyHidden = true;
608 }
609 if ( LogEventsList::userCanBitfield( $row->rc_deleted, LogPage::DELETED_ACTION, $user ) ) {
610 $vals['logid'] = (int)$row->rc_logid;
611 $vals['logtype'] = $row->rc_log_type;
612 $vals['logaction'] = $row->rc_log_action;
613 $vals['logparams'] = LogFormatter::newFromRow( $row )->formatParametersForApi();
614 }
615 }
616
617 if ( $this->fld_tags ) {
618 if ( $row->ts_tags ) {
619 $tags = explode( ',', $row->ts_tags );
620 ApiResult::setIndexedTagName( $tags, 'tag' );
621 $vals['tags'] = $tags;
622 } else {
623 $vals['tags'] = [];
624 }
625 }
626
627 if ( $this->fld_sha1 && $row->rev_sha1 !== null ) {
628 if ( $row->rev_deleted & RevisionRecord::DELETED_TEXT ) {
629 $vals['sha1hidden'] = true;
630 $anyHidden = true;
631 }
632 if ( RevisionRecord::userCanBitfield(
633 $row->rev_deleted, RevisionRecord::DELETED_TEXT, $user
634 ) ) {
635 if ( $row->rev_sha1 !== '' ) {
636 $vals['sha1'] = Wikimedia\base_convert( $row->rev_sha1, 36, 16, 40 );
637 } else {
638 $vals['sha1'] = '';
639 }
640 }
641 }
642
643 if ( $anyHidden && ( $row->rc_deleted & RevisionRecord::DELETED_RESTRICTED ) ) {
644 $vals['suppressed'] = true;
645 }
646
647 return $vals;
648 }
649
654 private function includesPatrollingFlags( array $flagsArray ) {
655 return isset( $flagsArray['patrolled'] ) ||
656 isset( $flagsArray['!patrolled'] ) ||
657 isset( $flagsArray['unpatrolled'] ) ||
658 isset( $flagsArray['autopatrolled'] ) ||
659 isset( $flagsArray['!autopatrolled'] );
660 }
661
662 public function getCacheMode( $params ) {
663 if ( isset( $params['show'] ) &&
664 $this->includesPatrollingFlags( array_fill_keys( $params['show'], true ) )
665 ) {
666 return 'private';
667 }
668 if ( $this->userCanSeeRevDel() ) {
669 return 'private';
670 }
671 if ( $params['prop'] !== null && in_array( 'parsedcomment', $params['prop'] ) ) {
672 // MediaWiki\CommentFormatter\CommentFormatter::formatItems() calls wfMessage() among other things
673 return 'anon-public-user-private';
674 }
675
676 return 'public';
677 }
678
679 public function getAllowedParams() {
680 $slotRoles = $this->slotRoleRegistry->getKnownRoles();
681 sort( $slotRoles, SORT_STRING );
682
683 return [
684 'start' => [
685 ParamValidator::PARAM_TYPE => 'timestamp'
686 ],
687 'end' => [
688 ParamValidator::PARAM_TYPE => 'timestamp'
689 ],
690 'dir' => [
691 ParamValidator::PARAM_DEFAULT => 'older',
692 ParamValidator::PARAM_TYPE => [
693 'newer',
694 'older'
695 ],
696 ApiBase::PARAM_HELP_MSG => 'api-help-param-direction',
698 'newer' => 'api-help-paramvalue-direction-newer',
699 'older' => 'api-help-paramvalue-direction-older',
700 ],
701 ],
702 'namespace' => [
703 ParamValidator::PARAM_ISMULTI => true,
704 ParamValidator::PARAM_TYPE => 'namespace',
705 NamespaceDef::PARAM_EXTRA_NAMESPACES => [ NS_MEDIA, NS_SPECIAL ],
706 ],
707 'user' => [
708 ParamValidator::PARAM_TYPE => 'user',
709 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'id', 'interwiki' ],
710 ],
711 'excludeuser' => [
712 ParamValidator::PARAM_TYPE => 'user',
713 UserDef::PARAM_ALLOWED_USER_TYPES => [ 'name', 'ip', 'temp', 'id', 'interwiki' ],
714 ],
715 'tag' => null,
716 'prop' => [
717 ParamValidator::PARAM_ISMULTI => true,
718 ParamValidator::PARAM_DEFAULT => 'title|timestamp|ids',
719 ParamValidator::PARAM_TYPE => [
720 'user',
721 'userid',
722 'comment',
723 'parsedcomment',
724 'flags',
725 'timestamp',
726 'title',
727 'ids',
728 'sizes',
729 'redirect',
730 'patrolled',
731 'loginfo',
732 'tags',
733 'sha1',
734 ],
736 ],
737 'show' => [
738 ParamValidator::PARAM_ISMULTI => true,
739 ParamValidator::PARAM_TYPE => [
740 'minor',
741 '!minor',
742 'bot',
743 '!bot',
744 'anon',
745 '!anon',
746 'redirect',
747 '!redirect',
748 'patrolled',
749 '!patrolled',
750 'unpatrolled',
751 'autopatrolled',
752 '!autopatrolled',
753 ]
754 ],
755 'limit' => [
756 ParamValidator::PARAM_DEFAULT => 10,
757 ParamValidator::PARAM_TYPE => 'limit',
758 IntegerDef::PARAM_MIN => 1,
759 IntegerDef::PARAM_MAX => ApiBase::LIMIT_BIG1,
760 IntegerDef::PARAM_MAX2 => ApiBase::LIMIT_BIG2
761 ],
762 'type' => [
763 ParamValidator::PARAM_DEFAULT => 'edit|new|log|categorize',
764 ParamValidator::PARAM_ISMULTI => true,
765 ParamValidator::PARAM_TYPE => RecentChange::getChangeTypes()
766 ],
767 'toponly' => false,
768 'title' => null,
769 'continue' => [
770 ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
771 ],
772 'generaterevisions' => false,
773 'slot' => [
774 ParamValidator::PARAM_TYPE => $slotRoles
775 ],
776 ];
777 }
778
779 protected function getExamplesMessages() {
780 return [
781 'action=query&list=recentchanges'
782 => 'apihelp-query+recentchanges-example-simple',
783 'action=query&generator=recentchanges&grcshow=!patrolled&prop=info'
784 => 'apihelp-query+recentchanges-example-generator',
785 ];
786 }
787
788 public function getHelpUrls() {
789 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Recentchanges';
790 }
791}
getUser()
getRequest()
getAuthority()
const RC_NEW
Definition Defines.php:117
const NS_SPECIAL
Definition Defines.php:53
const LIST_OR
Definition Defines.php:46
const RC_LOG
Definition Defines.php:118
const NS_MEDIA
Definition Defines.php:52
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.
array $params
The job parameters.
run()
Run the job.
dieWithError( $msg, $code=null, $data=null, $httpCode=0)
Abort execution with an error.
Definition ApiBase.php:1533
parseContinueParamOrDie(string $continue, array $types)
Parse the 'continue' parameter in the usual format and validate the types of each part,...
Definition ApiBase.php:1725
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, or 'string' with PARAM_ISMULTI,...
Definition ApiBase.php:211
const LIMIT_BIG1
Fast query, standard limit.
Definition ApiBase.php:236
requireMaxOneParameter( $params,... $required)
Dies if more than one parameter from a certain set of parameters are set and not false.
Definition ApiBase.php:985
getResult()
Get the result object.
Definition ApiBase.php:671
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition ApiBase.php:811
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition ApiBase.php:171
const LIMIT_BIG2
Fast query, apihighlimits limit.
Definition ApiBase.php:238
getModuleName()
Get the name of the module being executed by this instance.
Definition ApiBase.php:532
getContinuationManager()
Definition ApiBase.php:708
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.
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.
__construct(ApiQuery $query, $moduleName, CommentStore $commentStore, RowCommentFormatter $commentFormatter, NameTableStore $changeTagDefStore, NameTableStore $slotRoleStore, SlotRoleRegistry $slotRoleRegistry, UserNameUtils $userNameUtils)
This is the main query class.
Definition ApiQuery.php:43
static makeTagSummarySubquery( $tables)
Make the tag summary subquery based on the given tables and return it.
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.
A class containing constants representing the names of configuration variables.
Type definition for namespace types.
Type definition for user types.
Definition UserDef.php:27
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.
Represents a title within MediaWiki.
Definition Title.php:78
UserNameUtils service.
Service for formatting and validating API parameters.
Type definition for integer types.