MediaWiki  master
ApiQueryInfo.php
Go to the documentation of this file.
1 <?php
25 
31 class ApiQueryInfo extends ApiQueryBase {
32 
33  private $fld_protection = false, $fld_talkid = false,
34  $fld_subjectid = false, $fld_url = false,
35  $fld_readable = false, $fld_watched = false,
39 
40  private $params;
41 
43  private $titles;
45  private $missing;
47  private $everything;
48 
51 
54  private $showZeroWatchers = false;
55 
56  private $tokenFunctions;
57 
58  private $countTestedActions = 0;
59 
60  public function __construct( ApiQuery $query, $moduleName ) {
61  parent::__construct( $query, $moduleName, 'in' );
62  }
63 
68  public function requestExtraData( $pageSet ) {
69  $pageSet->requestField( 'page_restrictions' );
70  // If the pageset is resolving redirects we won't get page_is_redirect.
71  // But we can't know for sure until the pageset is executed (revids may
72  // turn it off), so request it unconditionally.
73  $pageSet->requestField( 'page_is_redirect' );
74  $pageSet->requestField( 'page_is_new' );
75  $config = $this->getConfig();
76  $pageSet->requestField( 'page_touched' );
77  $pageSet->requestField( 'page_latest' );
78  $pageSet->requestField( 'page_len' );
79  $pageSet->requestField( 'page_content_model' );
80  if ( $config->get( 'PageLanguageUseDB' ) ) {
81  $pageSet->requestField( 'page_lang' );
82  }
83  }
84 
92  protected function getTokenFunctions() {
93  // Don't call the hooks twice
94  if ( isset( $this->tokenFunctions ) ) {
95  return $this->tokenFunctions;
96  }
97 
98  // If we're in a mode that breaks the same-origin policy, no tokens can
99  // be obtained
100  if ( $this->lacksSameOriginSecurity() ) {
101  return [];
102  }
103 
104  $this->tokenFunctions = [
105  'edit' => [ self::class, 'getEditToken' ],
106  'delete' => [ self::class, 'getDeleteToken' ],
107  'protect' => [ self::class, 'getProtectToken' ],
108  'move' => [ self::class, 'getMoveToken' ],
109  'block' => [ self::class, 'getBlockToken' ],
110  'unblock' => [ self::class, 'getUnblockToken' ],
111  'email' => [ self::class, 'getEmailToken' ],
112  'import' => [ self::class, 'getImportToken' ],
113  'watch' => [ self::class, 'getWatchToken' ],
114  ];
115 
116  return $this->tokenFunctions;
117  }
118 
120  protected static $cachedTokens = [];
121 
125  public static function resetTokenCache() {
126  self::$cachedTokens = [];
127  }
128 
138  private static function getUserToken( User $user, string $right ) {
139  if ( !MediaWikiServices::getInstance()
141  ->userHasRight( $user, $right )
142  ) {
143  return false;
144  }
145 
146  // The token is always the same, let's exploit that
147  if ( !isset( self::$cachedTokens['edit'] ) ) {
148  self::$cachedTokens['edit'] = $user->getEditToken();
149  }
150 
151  return self::$cachedTokens['edit'];
152  }
153 
159  public static function getEditToken( User $user ) {
160  return self::getUserToken( $user, 'edit' );
161  }
162 
168  public static function getDeleteToken( User $user ) {
169  return self::getUserToken( $user, 'delete' );
170  }
171 
177  public static function getProtectToken( User $user ) {
178  return self::getUserToken( $user, 'protect' );
179  }
180 
186  public static function getMoveToken( User $user ) {
187  return self::getUserToken( $user, 'move' );
188  }
189 
195  public static function getBlockToken( User $user ) {
196  return self::getUserToken( $user, 'block' );
197  }
198 
204  public static function getUnblockToken( User $user ) {
205  // Currently, this is exactly the same as the block token
206  return self::getBlockToken( $user );
207  }
208 
214  public static function getEmailToken( User $user ) {
215  if ( !$user->canSendEmail() || $user->isBlockedFromEmailuser() ) {
216  return false;
217  }
218 
219  // The token is always the same, let's exploit that
220  if ( !isset( self::$cachedTokens['email'] ) ) {
221  self::$cachedTokens['email'] = $user->getEditToken();
222  }
223 
224  return self::$cachedTokens['email'];
225  }
226 
232  public static function getImportToken( User $user ) {
233  if ( !MediaWikiServices::getInstance()
235  ->userHasAnyRight( $user, 'import', 'importupload' ) ) {
236  return false;
237  }
238 
239  // The token is always the same, let's exploit that
240  if ( !isset( self::$cachedTokens['import'] ) ) {
241  self::$cachedTokens['import'] = $user->getEditToken();
242  }
243 
244  return self::$cachedTokens['import'];
245  }
246 
252  public static function getWatchToken( User $user ) {
253  if ( !$user->isLoggedIn() ) {
254  return false;
255  }
256 
257  // The token is always the same, let's exploit that
258  if ( !isset( self::$cachedTokens['watch'] ) ) {
259  self::$cachedTokens['watch'] = $user->getEditToken( 'watch' );
260  }
261 
262  return self::$cachedTokens['watch'];
263  }
264 
270  public static function getOptionsToken( User $user ) {
271  if ( !$user->isLoggedIn() ) {
272  return false;
273  }
274 
275  // The token is always the same, let's exploit that
276  if ( !isset( self::$cachedTokens['options'] ) ) {
277  self::$cachedTokens['options'] = $user->getEditToken();
278  }
279 
280  return self::$cachedTokens['options'];
281  }
282 
283  public function execute() {
284  $this->params = $this->extractRequestParams();
285  if ( $this->params['prop'] !== null ) {
286  $prop = array_flip( $this->params['prop'] );
287  $this->fld_protection = isset( $prop['protection'] );
288  $this->fld_watched = isset( $prop['watched'] );
289  $this->fld_watchers = isset( $prop['watchers'] );
290  $this->fld_visitingwatchers = isset( $prop['visitingwatchers'] );
291  $this->fld_notificationtimestamp = isset( $prop['notificationtimestamp'] );
292  $this->fld_talkid = isset( $prop['talkid'] );
293  $this->fld_subjectid = isset( $prop['subjectid'] );
294  $this->fld_url = isset( $prop['url'] );
295  $this->fld_readable = isset( $prop['readable'] );
296  $this->fld_preload = isset( $prop['preload'] );
297  $this->fld_displaytitle = isset( $prop['displaytitle'] );
298  $this->fld_varianttitles = isset( $prop['varianttitles'] );
299  }
300 
301  $pageSet = $this->getPageSet();
302  $this->titles = $pageSet->getGoodTitles();
303  $this->missing = $pageSet->getMissingTitles();
304  $this->everything = $this->titles + $this->missing;
305  $result = $this->getResult();
306 
307  uasort( $this->everything, [ Title::class, 'compare' ] );
308  if ( $this->params['continue'] !== null ) {
309  // Throw away any titles we're gonna skip so they don't
310  // clutter queries
311  $cont = explode( '|', $this->params['continue'] );
312  $this->dieContinueUsageIf( count( $cont ) != 2 );
313  $conttitle = Title::makeTitleSafe( $cont[0], $cont[1] );
314  foreach ( $this->everything as $pageid => $title ) {
315  if ( Title::compare( $title, $conttitle ) >= 0 ) {
316  break;
317  }
318  unset( $this->titles[$pageid] );
319  unset( $this->missing[$pageid] );
320  unset( $this->everything[$pageid] );
321  }
322  }
323 
324  $this->pageRestrictions = $pageSet->getCustomField( 'page_restrictions' );
325  // when resolving redirects, no page will have this field
326  $this->pageIsRedir = !$pageSet->isResolvingRedirects()
327  ? $pageSet->getCustomField( 'page_is_redirect' )
328  : [];
329  $this->pageIsNew = $pageSet->getCustomField( 'page_is_new' );
330 
331  $this->pageTouched = $pageSet->getCustomField( 'page_touched' );
332  $this->pageLatest = $pageSet->getCustomField( 'page_latest' );
333  $this->pageLength = $pageSet->getCustomField( 'page_len' );
334 
335  // Get protection info if requested
336  if ( $this->fld_protection ) {
337  $this->getProtectionInfo();
338  }
339 
340  if ( $this->fld_watched || $this->fld_notificationtimestamp ) {
341  $this->getWatchedInfo();
342  }
343 
344  if ( $this->fld_watchers ) {
345  $this->getWatcherInfo();
346  }
347 
348  if ( $this->fld_visitingwatchers ) {
349  $this->getVisitingWatcherInfo();
350  }
351 
352  // Run the talkid/subjectid query if requested
353  if ( $this->fld_talkid || $this->fld_subjectid ) {
354  $this->getTSIDs();
355  }
356 
357  if ( $this->fld_displaytitle ) {
358  $this->getDisplayTitle();
359  }
360 
361  if ( $this->fld_varianttitles ) {
362  $this->getVariantTitles();
363  }
364 
366  foreach ( $this->everything as $pageid => $title ) {
367  $pageInfo = $this->extractPageInfo( $pageid, $title );
368  $fit = $pageInfo !== null && $result->addValue( [
369  'query',
370  'pages'
371  ], $pageid, $pageInfo );
372  if ( !$fit ) {
373  $this->setContinueEnumParameter( 'continue',
374  $title->getNamespace() . '|' .
375  $title->getText() );
376  break;
377  }
378  }
379  }
380 
387  private function extractPageInfo( $pageid, $title ) {
388  $pageInfo = [];
389  // $title->exists() needs pageid, which is not set for all title objects
390  $titleExists = $pageid > 0;
391  $ns = $title->getNamespace();
392  $dbkey = $title->getDBkey();
393 
394  $pageInfo['contentmodel'] = $title->getContentModel();
395 
396  $pageLanguage = $title->getPageLanguage();
397  $pageInfo['pagelanguage'] = $pageLanguage->getCode();
398  $pageInfo['pagelanguagehtmlcode'] = $pageLanguage->getHtmlCode();
399  $pageInfo['pagelanguagedir'] = $pageLanguage->getDir();
400 
401  $user = $this->getUser();
402 
403  if ( $titleExists ) {
404  $pageInfo['touched'] = wfTimestamp( TS_ISO_8601, $this->pageTouched[$pageid] );
405  $pageInfo['lastrevid'] = (int)$this->pageLatest[$pageid];
406  $pageInfo['length'] = (int)$this->pageLength[$pageid];
407 
408  if ( isset( $this->pageIsRedir[$pageid] ) && $this->pageIsRedir[$pageid] ) {
409  $pageInfo['redirect'] = true;
410  }
411  if ( $this->pageIsNew[$pageid] ) {
412  $pageInfo['new'] = true;
413  }
414  }
415 
416  if ( $this->params['token'] !== null ) {
418  $pageInfo['starttimestamp'] = wfTimestamp( TS_ISO_8601, time() );
419  foreach ( $this->params['token'] as $t ) {
420  $val = call_user_func( $tokenFunctions[$t], $this->getUser() );
421  if ( $val === false ) {
422  $this->addWarning( [ 'apiwarn-tokennotallowed', $t ] );
423  } else {
424  $pageInfo[$t . 'token'] = $val;
425  }
426  }
427  }
428 
429  if ( $this->fld_protection ) {
430  $pageInfo['protection'] = [];
431  if ( isset( $this->protections[$ns][$dbkey] ) ) {
432  $pageInfo['protection'] =
433  $this->protections[$ns][$dbkey];
434  }
435  ApiResult::setIndexedTagName( $pageInfo['protection'], 'pr' );
436 
437  $pageInfo['restrictiontypes'] = [];
438  if ( isset( $this->restrictionTypes[$ns][$dbkey] ) ) {
439  $pageInfo['restrictiontypes'] =
440  $this->restrictionTypes[$ns][$dbkey];
441  }
442  ApiResult::setIndexedTagName( $pageInfo['restrictiontypes'], 'rt' );
443  }
444 
445  if ( $this->fld_watched && $this->watched !== null ) {
446  $pageInfo['watched'] = $this->watched[$ns][$dbkey];
447  }
448 
449  if ( $this->fld_watchers ) {
450  if ( $this->watchers !== null && $this->watchers[$ns][$dbkey] !== 0 ) {
451  $pageInfo['watchers'] = $this->watchers[$ns][$dbkey];
452  } elseif ( $this->showZeroWatchers ) {
453  $pageInfo['watchers'] = 0;
454  }
455  }
456 
457  if ( $this->fld_visitingwatchers ) {
458  if ( $this->visitingwatchers !== null && $this->visitingwatchers[$ns][$dbkey] !== 0 ) {
459  $pageInfo['visitingwatchers'] = $this->visitingwatchers[$ns][$dbkey];
460  } elseif ( $this->showZeroWatchers ) {
461  $pageInfo['visitingwatchers'] = 0;
462  }
463  }
464 
465  if ( $this->fld_notificationtimestamp ) {
466  $pageInfo['notificationtimestamp'] = '';
467  if ( $this->notificationtimestamps[$ns][$dbkey] ) {
468  $pageInfo['notificationtimestamp'] =
469  wfTimestamp( TS_ISO_8601, $this->notificationtimestamps[$ns][$dbkey] );
470  }
471  }
472 
473  if ( $this->fld_talkid && isset( $this->talkids[$ns][$dbkey] ) ) {
474  $pageInfo['talkid'] = $this->talkids[$ns][$dbkey];
475  }
476 
477  if ( $this->fld_subjectid && isset( $this->subjectids[$ns][$dbkey] ) ) {
478  $pageInfo['subjectid'] = $this->subjectids[$ns][$dbkey];
479  }
480 
481  if ( $this->fld_url ) {
482  $pageInfo['fullurl'] = wfExpandUrl( $title->getFullURL(), PROTO_CURRENT );
483  $pageInfo['editurl'] = wfExpandUrl( $title->getFullURL( 'action=edit' ), PROTO_CURRENT );
484  $pageInfo['canonicalurl'] = wfExpandUrl( $title->getFullURL(), PROTO_CANONICAL );
485  }
486  if ( $this->fld_readable ) {
487  $pageInfo['readable'] = $this->getPermissionManager()->userCan(
488  'read', $user, $title
489  );
490  }
491 
492  if ( $this->fld_preload ) {
493  if ( $titleExists ) {
494  $pageInfo['preload'] = '';
495  } else {
496  $text = null;
497  $this->getHookRunner()->onEditFormPreloadText( $text, $title );
498 
499  $pageInfo['preload'] = $text;
500  }
501  }
502 
503  if ( $this->fld_displaytitle ) {
504  if ( isset( $this->displaytitles[$pageid] ) ) {
505  $pageInfo['displaytitle'] = $this->displaytitles[$pageid];
506  } else {
507  $pageInfo['displaytitle'] = $title->getPrefixedText();
508  }
509  }
510 
511  if ( $this->fld_varianttitles && isset( $this->variantTitles[$pageid] ) ) {
512  $pageInfo['varianttitles'] = $this->variantTitles[$pageid];
513  }
514 
515  if ( $this->params['testactions'] ) {
516  $limit = $this->getMain()->canApiHighLimits() ? self::LIMIT_SML2 : self::LIMIT_SML1;
517  if ( $this->countTestedActions >= $limit ) {
518  return null; // force a continuation
519  }
520 
521  $detailLevel = $this->params['testactionsdetail'];
522  $rigor = $detailLevel === 'quick'
523  ? PermissionManager::RIGOR_QUICK
524  // Not using RIGOR_SECURE here, because that results in master connection
525  : PermissionManager::RIGOR_FULL;
526  $errorFormatter = $this->getErrorFormatter();
527  if ( $errorFormatter->getFormat() === 'bc' ) {
528  // Eew, no. Use a more modern format here.
529  $errorFormatter = $errorFormatter->newWithFormat( 'plaintext' );
530  }
531 
532  $user = $this->getUser();
533  $pageInfo['actions'] = [];
534  foreach ( $this->params['testactions'] as $action ) {
535  $this->countTestedActions++;
536 
537  if ( $detailLevel === 'boolean' ) {
538  $pageInfo['actions'][$action] = $this->getPermissionManager()->userCan(
539  $action, $user, $title
540  );
541  } else {
542  $pageInfo['actions'][$action] = $errorFormatter->arrayFromStatus( $this->errorArrayToStatus(
543  $this->getPermissionManager()->getPermissionErrors(
544  $action, $user, $title, $rigor
545  ),
546  $user
547  ) );
548  }
549  }
550  }
551 
552  return $pageInfo;
553  }
554 
558  private function getProtectionInfo() {
559  $this->protections = [];
560  $db = $this->getDB();
561 
562  // Get normal protections for existing titles
563  if ( count( $this->titles ) ) {
564  $this->resetQueryParams();
565  $this->addTables( 'page_restrictions' );
566  $this->addFields( [ 'pr_page', 'pr_type', 'pr_level',
567  'pr_expiry', 'pr_cascade' ] );
568  $this->addWhereFld( 'pr_page', array_keys( $this->titles ) );
569 
570  $res = $this->select( __METHOD__ );
571  foreach ( $res as $row ) {
573  $title = $this->titles[$row->pr_page];
574  $a = [
575  'type' => $row->pr_type,
576  'level' => $row->pr_level,
577  'expiry' => ApiResult::formatExpiry( $row->pr_expiry )
578  ];
579  if ( $row->pr_cascade ) {
580  $a['cascade'] = true;
581  }
582  $this->protections[$title->getNamespace()][$title->getDBkey()][] = $a;
583  }
584  // Also check old restrictions
585  foreach ( $this->titles as $pageId => $title ) {
586  if ( $this->pageRestrictions[$pageId] ) {
587  $namespace = $title->getNamespace();
588  $dbKey = $title->getDBkey();
589  $restrictions = explode( ':', trim( $this->pageRestrictions[$pageId] ) );
590  foreach ( $restrictions as $restrict ) {
591  $temp = explode( '=', trim( $restrict ) );
592  if ( count( $temp ) == 1 ) {
593  // old old format should be treated as edit/move restriction
594  $restriction = trim( $temp[0] );
595 
596  if ( $restriction == '' ) {
597  continue;
598  }
599  $this->protections[$namespace][$dbKey][] = [
600  'type' => 'edit',
601  'level' => $restriction,
602  'expiry' => 'infinity',
603  ];
604  $this->protections[$namespace][$dbKey][] = [
605  'type' => 'move',
606  'level' => $restriction,
607  'expiry' => 'infinity',
608  ];
609  } else {
610  $restriction = trim( $temp[1] );
611  if ( $restriction == '' ) {
612  continue;
613  }
614  $this->protections[$namespace][$dbKey][] = [
615  'type' => $temp[0],
616  'level' => $restriction,
617  'expiry' => 'infinity',
618  ];
619  }
620  }
621  }
622  }
623  }
624 
625  // Get protections for missing titles
626  if ( count( $this->missing ) ) {
627  $this->resetQueryParams();
628  $linkBatchFactory = MediaWikiServices::getInstance()->getLinkBatchFactory();
629  $lb = $linkBatchFactory->newLinkBatch( $this->missing );
630  $this->addTables( 'protected_titles' );
631  $this->addFields( [ 'pt_title', 'pt_namespace', 'pt_create_perm', 'pt_expiry' ] );
632  $this->addWhere( $lb->constructSet( 'pt', $db ) );
633  $res = $this->select( __METHOD__ );
634  foreach ( $res as $row ) {
635  $this->protections[$row->pt_namespace][$row->pt_title][] = [
636  'type' => 'create',
637  'level' => $row->pt_create_perm,
638  'expiry' => ApiResult::formatExpiry( $row->pt_expiry )
639  ];
640  }
641  }
642 
643  // Separate good and missing titles into files and other pages
644  // and populate $this->restrictionTypes
645  $images = $others = [];
646  foreach ( $this->everything as $title ) {
647  if ( $title->getNamespace() === NS_FILE ) {
648  $images[] = $title->getDBkey();
649  } else {
650  $others[] = $title;
651  }
652  // Applicable protection types
653  $this->restrictionTypes[$title->getNamespace()][$title->getDBkey()] =
654  array_values( $title->getRestrictionTypes() );
655  }
656 
657  if ( count( $others ) ) {
658  // Non-images: check templatelinks
659  $linkBatchFactory = MediaWikiServices::getInstance()->getLinkBatchFactory();
660  $lb = $linkBatchFactory->newLinkBatch( $others );
661  $this->resetQueryParams();
662  $this->addTables( [ 'page_restrictions', 'page', 'templatelinks' ] );
663  $this->addFields( [ 'pr_type', 'pr_level', 'pr_expiry',
664  'page_title', 'page_namespace',
665  'tl_title', 'tl_namespace' ] );
666  $this->addWhere( $lb->constructSet( 'tl', $db ) );
667  $this->addWhere( 'pr_page = page_id' );
668  $this->addWhere( 'pr_page = tl_from' );
669  $this->addWhereFld( 'pr_cascade', 1 );
670 
671  $res = $this->select( __METHOD__ );
672  foreach ( $res as $row ) {
673  $source = Title::makeTitle( $row->page_namespace, $row->page_title );
674  $this->protections[$row->tl_namespace][$row->tl_title][] = [
675  'type' => $row->pr_type,
676  'level' => $row->pr_level,
677  'expiry' => ApiResult::formatExpiry( $row->pr_expiry ),
678  'source' => $source->getPrefixedText()
679  ];
680  }
681  }
682 
683  if ( count( $images ) ) {
684  // Images: check imagelinks
685  $this->resetQueryParams();
686  $this->addTables( [ 'page_restrictions', 'page', 'imagelinks' ] );
687  $this->addFields( [ 'pr_type', 'pr_level', 'pr_expiry',
688  'page_title', 'page_namespace', 'il_to' ] );
689  $this->addWhere( 'pr_page = page_id' );
690  $this->addWhere( 'pr_page = il_from' );
691  $this->addWhereFld( 'pr_cascade', 1 );
692  $this->addWhereFld( 'il_to', $images );
693 
694  $res = $this->select( __METHOD__ );
695  foreach ( $res as $row ) {
696  $source = Title::makeTitle( $row->page_namespace, $row->page_title );
697  $this->protections[NS_FILE][$row->il_to][] = [
698  'type' => $row->pr_type,
699  'level' => $row->pr_level,
700  'expiry' => ApiResult::formatExpiry( $row->pr_expiry ),
701  'source' => $source->getPrefixedText()
702  ];
703  }
704  }
705  }
706 
711  private function getTSIDs() {
712  $getTitles = $this->talkids = $this->subjectids = [];
713  $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
714 
716  foreach ( $this->everything as $t ) {
717  if ( $nsInfo->isTalk( $t->getNamespace() ) ) {
718  if ( $this->fld_subjectid ) {
719  $getTitles[] = $t->getSubjectPage();
720  }
721  } elseif ( $this->fld_talkid ) {
722  $getTitles[] = $t->getTalkPage();
723  }
724  }
725  if ( $getTitles === [] ) {
726  return;
727  }
728 
729  $db = $this->getDB();
730 
731  // Construct a custom WHERE clause that matches
732  // all titles in $getTitles
733  $linkBatchFactory = MediaWikiServices::getInstance()->getLinkBatchFactory();
734  $lb = $linkBatchFactory->newLinkBatch( $getTitles );
735  $this->resetQueryParams();
736  $this->addTables( 'page' );
737  $this->addFields( [ 'page_title', 'page_namespace', 'page_id' ] );
738  $this->addWhere( $lb->constructSet( 'page', $db ) );
739  $res = $this->select( __METHOD__ );
740  foreach ( $res as $row ) {
741  if ( $nsInfo->isTalk( $row->page_namespace ) ) {
742  $this->talkids[$nsInfo->getSubject( $row->page_namespace )][$row->page_title] =
743  (int)( $row->page_id );
744  } else {
745  $this->subjectids[$nsInfo->getTalk( $row->page_namespace )][$row->page_title] =
746  (int)( $row->page_id );
747  }
748  }
749  }
750 
751  private function getDisplayTitle() {
752  $this->displaytitles = [];
753 
754  $pageIds = array_keys( $this->titles );
755 
756  if ( $pageIds === [] ) {
757  return;
758  }
759 
760  $this->resetQueryParams();
761  $this->addTables( 'page_props' );
762  $this->addFields( [ 'pp_page', 'pp_value' ] );
763  $this->addWhereFld( 'pp_page', $pageIds );
764  $this->addWhereFld( 'pp_propname', 'displaytitle' );
765  $res = $this->select( __METHOD__ );
766 
767  foreach ( $res as $row ) {
768  $this->displaytitles[$row->pp_page] = $row->pp_value;
769  }
770  }
771 
772  private function getVariantTitles() {
773  if ( $this->titles === [] ) {
774  return;
775  }
776  $this->variantTitles = [];
777  foreach ( $this->titles as $pageId => $t ) {
778  $this->variantTitles[$pageId] = isset( $this->displaytitles[$pageId] )
779  ? $this->getAllVariants( $this->displaytitles[$pageId] )
780  : $this->getAllVariants( $t->getText(), $t->getNamespace() );
781  }
782  }
783 
784  private function getAllVariants( $text, $ns = NS_MAIN ) {
785  $result = [];
786  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
787  foreach ( $contLang->getVariants() as $variant ) {
788  $convertTitle = $contLang->autoConvert( $text, $variant );
789  if ( $ns !== NS_MAIN ) {
790  $convertNs = $contLang->convertNamespace( $ns, $variant );
791  $convertTitle = $convertNs . ':' . $convertTitle;
792  }
793  $result[$variant] = $convertTitle;
794  }
795  return $result;
796  }
797 
802  private function getWatchedInfo() {
803  $user = $this->getUser();
804 
805  if ( $user->isAnon() || count( $this->everything ) == 0
806  || !$this->getPermissionManager()->userHasRight( $user, 'viewmywatchlist' )
807  ) {
808  return;
809  }
810 
811  $this->watched = [];
812  $this->notificationtimestamps = [];
813 
814  $store = MediaWikiServices::getInstance()->getWatchedItemStore();
815  $timestamps = $store->getNotificationTimestampsBatch( $user, $this->everything );
816 
817  if ( $this->fld_watched ) {
818  foreach ( $timestamps as $namespaceId => $dbKeys ) {
819  $this->watched[$namespaceId] = array_map(
820  function ( $x ) {
821  return $x !== false;
822  },
823  $dbKeys
824  );
825  }
826  }
827  if ( $this->fld_notificationtimestamp ) {
828  $this->notificationtimestamps = $timestamps;
829  }
830  }
831 
835  private function getWatcherInfo() {
836  if ( count( $this->everything ) == 0 ) {
837  return;
838  }
839 
840  $user = $this->getUser();
841  $canUnwatchedpages = $this->getPermissionManager()->userHasRight( $user, 'unwatchedpages' );
842  $unwatchedPageThreshold = $this->getConfig()->get( 'UnwatchedPageThreshold' );
843  if ( !$canUnwatchedpages && !is_int( $unwatchedPageThreshold ) ) {
844  return;
845  }
846 
847  $this->showZeroWatchers = $canUnwatchedpages;
848 
849  $countOptions = [];
850  if ( !$canUnwatchedpages ) {
851  $countOptions['minimumWatchers'] = $unwatchedPageThreshold;
852  }
853 
854  $this->watchers = MediaWikiServices::getInstance()->getWatchedItemStore()->countWatchersMultiple(
855  $this->everything,
856  $countOptions
857  );
858  }
859 
866  private function getVisitingWatcherInfo() {
867  $config = $this->getConfig();
868  $user = $this->getUser();
869  $db = $this->getDB();
870 
871  $canUnwatchedpages = $this->getPermissionManager()->userHasRight( $user, 'unwatchedpages' );
872  $unwatchedPageThreshold = $this->getConfig()->get( 'UnwatchedPageThreshold' );
873  if ( !$canUnwatchedpages && !is_int( $unwatchedPageThreshold ) ) {
874  return;
875  }
876 
877  $this->showZeroWatchers = $canUnwatchedpages;
878 
879  $titlesWithThresholds = [];
880  if ( $this->titles ) {
881  $linkBatchFactory = MediaWikiServices::getInstance()->getLinkBatchFactory();
882  $lb = $linkBatchFactory->newLinkBatch( $this->titles );
883 
884  // Fetch last edit timestamps for pages
885  $this->resetQueryParams();
886  $this->addTables( [ 'page', 'revision' ] );
887  $this->addFields( [ 'page_namespace', 'page_title', 'rev_timestamp' ] );
888  $this->addWhere( [
889  'page_latest = rev_id',
890  $lb->constructSet( 'page', $db ),
891  ] );
892  $this->addOption( 'GROUP BY', [ 'page_namespace', 'page_title' ] );
893  $timestampRes = $this->select( __METHOD__ );
894 
895  $age = $config->get( 'WatchersMaxAge' );
896  $timestamps = [];
897  foreach ( $timestampRes as $row ) {
898  $revTimestamp = wfTimestamp( TS_UNIX, (int)$row->rev_timestamp );
899  $timestamps[$row->page_namespace][$row->page_title] = (int)$revTimestamp - $age;
900  }
901  $titlesWithThresholds = array_map(
902  function ( LinkTarget $target ) use ( $timestamps ) {
903  return [
904  $target, $timestamps[$target->getNamespace()][$target->getDBkey()]
905  ];
906  },
908  );
909  }
910 
911  if ( $this->missing ) {
912  $titlesWithThresholds = array_merge(
913  $titlesWithThresholds,
914  array_map(
915  function ( LinkTarget $target ) {
916  return [ $target, null ];
917  },
919  )
920  );
921  }
922  $store = MediaWikiServices::getInstance()->getWatchedItemStore();
923  $this->visitingwatchers = $store->countVisitingWatchersMultiple(
924  $titlesWithThresholds,
925  !$canUnwatchedpages ? $unwatchedPageThreshold : null
926  );
927  }
928 
929  public function getCacheMode( $params ) {
930  // Other props depend on something about the current user
931  $publicProps = [
932  'protection',
933  'talkid',
934  'subjectid',
935  'url',
936  'preload',
937  'displaytitle',
938  'varianttitles',
939  ];
940  if ( array_diff( (array)$params['prop'], $publicProps ) ) {
941  return 'private';
942  }
943 
944  // testactions also depends on the current user
945  if ( $params['testactions'] ) {
946  return 'private';
947  }
948 
949  if ( $params['token'] !== null ) {
950  return 'private';
951  }
952 
953  return 'public';
954  }
955 
956  public function getAllowedParams() {
957  return [
958  'prop' => [
959  ApiBase::PARAM_ISMULTI => true,
961  'protection',
962  'talkid',
963  'watched', # private
964  'watchers', # private
965  'visitingwatchers', # private
966  'notificationtimestamp', # private
967  'subjectid',
968  'url',
969  'readable', # private
970  'preload',
971  'displaytitle',
972  'varianttitles',
973  // If you add more properties here, please consider whether they
974  // need to be added to getCacheMode()
975  ],
978  'readable' => true, // Since 1.32
979  ],
980  ],
981  'testactions' => [
982  ApiBase::PARAM_TYPE => 'string',
983  ApiBase::PARAM_ISMULTI => true,
984  ],
985  'testactionsdetail' => [
986  ApiBase::PARAM_TYPE => [ 'boolean', 'full', 'quick' ],
987  ApiBase::PARAM_DFLT => 'boolean',
989  ],
990  'token' => [
992  ApiBase::PARAM_ISMULTI => true,
993  ApiBase::PARAM_TYPE => array_keys( $this->getTokenFunctions() )
994  ],
995  'continue' => [
996  ApiBase::PARAM_HELP_MSG => 'api-help-param-continue',
997  ],
998  ];
999  }
1000 
1001  protected function getExamplesMessages() {
1002  return [
1003  'action=query&prop=info&titles=Main%20Page'
1004  => 'apihelp-query+info-example-simple',
1005  'action=query&prop=info&inprop=protection&titles=Main%20Page'
1006  => 'apihelp-query+info-example-protection',
1007  ];
1008  }
1009 
1010  public function getHelpUrls() {
1011  return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Info';
1012  }
1013 }
ApiQueryInfo\$talkids
$talkids
Definition: ApiQueryInfo.php:53
ApiQueryInfo\extractPageInfo
extractPageInfo( $pageid, $title)
Get a result array with information about a title.
Definition: ApiQueryInfo.php:387
ApiQueryInfo\getMoveToken
static getMoveToken(User $user)
Definition: ApiQueryInfo.php:186
ContextSource\getConfig
getConfig()
Definition: ContextSource.php:70
ApiQueryInfo\$everything
Title[] $everything
Definition: ApiQueryInfo.php:47
ApiQueryBase\addFields
addFields( $value)
Add a set of fields to select to the internal array.
Definition: ApiQueryBase.php:215
ApiQuery
This is the main query class.
Definition: ApiQuery.php:37
PROTO_CANONICAL
const PROTO_CANONICAL
Definition: Defines.php:212
ApiQueryInfo\getEditToken
static getEditToken(User $user)
Definition: ApiQueryInfo.php:159
ApiBase\addWarning
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
Definition: ApiBase.php:1300
ApiQueryInfo\getImportToken
static getImportToken(User $user)
Definition: ApiQueryInfo.php:232
ApiQueryInfo\$fld_url
$fld_url
Definition: ApiQueryInfo.php:34
ApiQueryInfo\$subjectids
$subjectids
Definition: ApiQueryInfo.php:53
ApiQueryInfo\$displaytitles
$displaytitles
Definition: ApiQueryInfo.php:53
ApiQueryBase\resetQueryParams
resetQueryParams()
Blank the internal arrays with query parameters.
Definition: ApiQueryBase.php:159
ApiQueryInfo\getProtectionInfo
getProtectionInfo()
Get information about protections and put it in $protections.
Definition: ApiQueryInfo.php:558
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:163
ApiQueryInfo\$pageTouched
$pageTouched
Definition: ApiQueryInfo.php:49
ApiQueryInfo\$showZeroWatchers
$showZeroWatchers
Definition: ApiQueryInfo.php:54
ApiQueryInfo\getProtectToken
static getProtectToken(User $user)
Definition: ApiQueryInfo.php:177
ApiBase\PARAM_HELP_MSG
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
Definition: ApiBase.php:106
ApiQueryInfo\resetTokenCache
static resetTokenCache()
Definition: ApiQueryInfo.php:125
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1815
ApiQueryInfo\$tokenFunctions
$tokenFunctions
Definition: ApiQueryInfo.php:56
ApiBase\PARAM_TYPE
const PARAM_TYPE
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:70
ApiBase\getResult
getResult()
Get the result object.
Definition: ApiBase.php:564
ApiQueryInfo\getVisitingWatcherInfo
getVisitingWatcherInfo()
Get the count of watchers who have visited recent edits and put it in $this->visitingwatchers.
Definition: ApiQueryInfo.php:866
NS_FILE
const NS_FILE
Definition: Defines.php:75
ApiQueryBase\addOption
addOption( $name, $value=null)
Add an option such as LIMIT or USE INDEX.
Definition: ApiQueryBase.php:381
$res
$res
Definition: testCompression.php:57
ContextSource\getUser
getUser()
Stable to override.
Definition: ContextSource.php:134
ApiQueryInfo
A query module to show basic page information.
Definition: ApiQueryInfo.php:31
ApiBase\PARAM_DEPRECATED_VALUES
const PARAM_DEPRECATED_VALUES
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:82
ApiBase\lacksSameOriginSecurity
lacksSameOriginSecurity()
Returns true if the current request breaks the same-origin policy.
Definition: ApiBase.php:492
ApiQueryInfo\$countTestedActions
$countTestedActions
Definition: ApiQueryInfo.php:58
ApiQueryInfo\getAllVariants
getAllVariants( $text, $ns=NS_MAIN)
Definition: ApiQueryInfo.php:784
NS_MAIN
const NS_MAIN
Definition: Defines.php:69
ApiQueryInfo\$fld_varianttitles
$fld_varianttitles
Definition: ApiQueryInfo.php:38
ApiBase\PARAM_DEPRECATED
const PARAM_DEPRECATED
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:75
MediaWiki\Linker\LinkTarget\getNamespace
getNamespace()
Get the namespace index.
ApiQueryInfo\$visitingwatchers
$visitingwatchers
Definition: ApiQueryInfo.php:52
ApiQueryInfo\getUnblockToken
static getUnblockToken(User $user)
Definition: ApiQueryInfo.php:204
ApiQueryBase
This is a base class for all Query modules.
Definition: ApiQueryBase.php:37
ApiQueryInfo\$pageRestrictions
$pageRestrictions
Definition: ApiQueryInfo.php:49
ApiQueryBase\getDB
getDB()
Get the Query database connection (read-only) Stable to override.
Definition: ApiQueryBase.php:119
PROTO_CURRENT
const PROTO_CURRENT
Definition: Defines.php:211
User\isBlockedFromEmailuser
isBlockedFromEmailuser()
Get whether the user is blocked from using Special:Emailuser.
Definition: User.php:3648
ApiQueryBase\addTables
addTables( $tables, $alias=null)
Add a set of tables to the internal array.
Definition: ApiQueryBase.php:185
ApiQueryBase\select
select( $method, $extraQuery=[], array &$hookData=null)
Execute a SELECT query based on the values in the internal arrays.
Definition: ApiQueryBase.php:402
ApiQueryInfo\$titles
Title[] $titles
Definition: ApiQueryInfo.php:43
ApiBase\extractRequestParams
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
Definition: ApiBase.php:716
ApiQueryInfo\$fld_talkid
$fld_talkid
Definition: ApiQueryInfo.php:33
$title
$title
Definition: testCompression.php:38
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:591
ApiQueryInfo\$fld_displaytitle
$fld_displaytitle
Definition: ApiQueryInfo.php:38
ApiQueryInfo\$notificationtimestamps
$notificationtimestamps
Definition: ApiQueryInfo.php:53
ApiQueryInfo\getDeleteToken
static getDeleteToken(User $user)
Definition: ApiQueryInfo.php:168
ApiQueryInfo\getTSIDs
getTSIDs()
Get talk page IDs (if requested) and subject page IDs (if requested) and put them in $talkids and $su...
Definition: ApiQueryInfo.php:711
ApiQueryInfo\getWatchedInfo
getWatchedInfo()
Get information about watched status and put it in $this->watched and $this->notificationtimestamps.
Definition: ApiQueryInfo.php:802
ApiQueryInfo\$watched
$watched
Definition: ApiQueryInfo.php:52
ApiQueryInfo\$params
$params
Definition: ApiQueryInfo.php:40
ApiQueryInfo\getHelpUrls
getHelpUrls()
Return links to more detailed help pages about the module.
Definition: ApiQueryInfo.php:1010
Title\makeTitleSafe
static makeTitleSafe( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:617
MediaWiki\Permissions\PermissionManager
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
Definition: PermissionManager.php:50
ApiResult\setIndexedTagName
static setIndexedTagName(array &$arr, $tag)
Set the tag name for numeric-keyed values in XML format.
Definition: ApiResult.php:604
ApiQueryInfo\$pageIsNew
$pageIsNew
Definition: ApiQueryInfo.php:49
ApiQueryInfo\getExamplesMessages
getExamplesMessages()
Returns usage examples for this module.
Definition: ApiQueryInfo.php:1001
ApiBase\dieContinueUsageIf
dieContinueUsageIf( $condition)
Die with the 'badcontinue' error.
Definition: ApiBase.php:1561
ApiQueryInfo\getTokenFunctions
getTokenFunctions()
Get an array mapping token names to their handler functions.
Definition: ApiQueryInfo.php:92
ApiBase\LIMIT_SML2
const LIMIT_SML2
Slow query, apihighlimits limit.
Definition: ApiBase.php:170
ApiQueryInfo\$fld_preload
$fld_preload
Definition: ApiQueryInfo.php:38
ApiQueryInfo\getEmailToken
static getEmailToken(User $user)
Definition: ApiQueryInfo.php:214
ApiBase\getPermissionManager
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
Definition: ApiBase.php:636
MediaWiki\Linker\LinkTarget\getDBkey
getDBkey()
Get the main part with underscores.
ApiQueryInfo\getWatchToken
static getWatchToken(User $user)
Definition: ApiQueryInfo.php:252
ApiQueryBase\addWhereFld
addWhereFld( $field, $value)
Equivalent to addWhere( [ $field => $value ] )
Definition: ApiQueryBase.php:285
ApiQueryBase\getPageSet
getPageSet()
Get the PageSet object to work on Stable to override.
Definition: ApiQueryBase.php:145
ApiQueryInfo\$fld_watchers
$fld_watchers
Definition: ApiQueryInfo.php:36
ApiQueryInfo\getOptionsToken
static getOptionsToken(User $user)
Definition: ApiQueryInfo.php:270
ApiQueryInfo\getCacheMode
getCacheMode( $params)
Get the cache mode for the data generated by this module.
Definition: ApiQueryInfo.php:929
ApiQueryInfo\$fld_subjectid
$fld_subjectid
Definition: ApiQueryInfo.php:34
Title
Represents a title within MediaWiki.
Definition: Title.php:41
ApiQueryInfo\$fld_watched
$fld_watched
Definition: ApiQueryInfo.php:35
ApiQueryInfo\getDisplayTitle
getDisplayTitle()
Definition: ApiQueryInfo.php:751
User\isLoggedIn
isLoggedIn()
Get whether the user is logged in.
Definition: User.php:2955
ApiQueryInfo\$fld_protection
$fld_protection
Definition: ApiQueryInfo.php:33
ApiQueryInfo\$watchers
$watchers
Definition: ApiQueryInfo.php:52
ApiQueryInfo\getUserToken
static getUserToken(User $user, string $right)
Temporary method until the token methods are removed entirely.
Definition: ApiQueryInfo.php:138
ApiQueryInfo\$pageLatest
$pageLatest
Definition: ApiQueryInfo.php:50
ApiBase\PARAM_DFLT
const PARAM_DFLT
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:68
ApiQueryInfo\$restrictionTypes
$restrictionTypes
Definition: ApiQueryInfo.php:52
ApiQueryInfo\getAllowedParams
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
Definition: ApiQueryInfo.php:956
ApiBase\PARAM_ISMULTI
const PARAM_ISMULTI
(boolean) Inverse of IntegerDef::PARAM_IGNORE_RANGE
Definition: ApiBase.php:69
ApiQueryInfo\$protections
$protections
Definition: ApiQueryInfo.php:52
ApiQueryInfo\getBlockToken
static getBlockToken(User $user)
Definition: ApiQueryInfo.php:195
$source
$source
Definition: mwdoc-filter.php:34
ApiResult\formatExpiry
static formatExpiry( $expiry, $infinity='infinity')
Format an expiry timestamp for API output.
Definition: ApiResult.php:1193
ApiBase\getMain
getMain()
Get the main module.
Definition: ApiBase.php:459
ApiQueryInfo\$fld_notificationtimestamp
$fld_notificationtimestamp
Definition: ApiQueryInfo.php:37
ApiQueryBase\addWhere
addWhere( $value)
Add a set of WHERE clauses to the internal array.
Definition: ApiQueryBase.php:248
Title\compare
static compare(LinkTarget $a, LinkTarget $b)
Callback for usort() to do title sorts by (namespace, title)
Definition: Title.php:849
$t
$t
Definition: testCompression.php:74
ApiQueryBase\setContinueEnumParameter
setContinueEnumParameter( $paramName, $paramValue)
Set a query-continue value.
Definition: ApiQueryBase.php:519
ApiQueryInfo\$pageIsRedir
$pageIsRedir
Definition: ApiQueryInfo.php:49
MediaWiki\Linker\LinkTarget
Definition: LinkTarget.php:26
ApiQueryInfo\$cachedTokens
static string[] $cachedTokens
Definition: ApiQueryInfo.php:120
ApiBase\PARAM_HELP_MSG_PER_VALUE
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:139
ApiQueryInfo\getVariantTitles
getVariantTitles()
Definition: ApiQueryInfo.php:772
User\getEditToken
getEditToken( $salt='', $request=null)
Initialize (if necessary) and return a session token value which can be used in edit forms to show th...
Definition: User.php:3735
ApiBase\getHookRunner
getHookRunner()
Get an ApiHookRunner for running core API hooks.
Definition: ApiBase.php:661
ApiQueryInfo\$fld_readable
$fld_readable
Definition: ApiQueryInfo.php:35
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:56
ApiQueryInfo\requestExtraData
requestExtraData( $pageSet)
Definition: ApiQueryInfo.php:68
ApiQueryInfo\$fld_visitingwatchers
$fld_visitingwatchers
Definition: ApiQueryInfo.php:36
ApiQueryInfo\execute
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
Definition: ApiQueryInfo.php:283
ApiQueryInfo\getWatcherInfo
getWatcherInfo()
Get the count of watchers and put it in $this->watchers.
Definition: ApiQueryInfo.php:835
ApiBase\errorArrayToStatus
errorArrayToStatus(array $errors, User $user=null)
Turn an array of message keys or key+param arrays into a Status.
Definition: ApiBase.php:1192
ApiQueryInfo\$pageLength
$pageLength
Definition: ApiQueryInfo.php:50
ApiBase\getErrorFormatter
getErrorFormatter()
Get the error formatter Stable to override.
Definition: ApiBase.php:579
ApiQueryInfo\$missing
Title[] $missing
Definition: ApiQueryInfo.php:45
User\canSendEmail
canSendEmail()
Is this user allowed to send e-mails within limits of current site configuration?
Definition: User.php:3959
ApiQueryInfo\$variantTitles
$variantTitles
Definition: ApiQueryInfo.php:53
ApiBase\LIMIT_SML1
const LIMIT_SML1
Slow query, standard limit.
Definition: ApiBase.php:168
wfExpandUrl
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
Definition: GlobalFunctions.php:490
ApiQueryInfo\__construct
__construct(ApiQuery $query, $moduleName)
Definition: ApiQueryInfo.php:60