60 parent::__construct( $query, $moduleName,
'in' );
68 $pageSet->requestField(
'page_restrictions' );
72 $pageSet->requestField(
'page_is_redirect' );
73 $pageSet->requestField(
'page_is_new' );
75 $pageSet->requestField(
'page_touched' );
76 $pageSet->requestField(
'page_latest' );
77 $pageSet->requestField(
'page_len' );
78 if ( $config->get(
'ContentHandlerUseDB' ) ) {
79 $pageSet->requestField(
'page_content_model' );
81 if ( $config->get(
'PageLanguageUseDB' ) ) {
82 $pageSet->requestField(
'page_lang' );
95 if ( isset( $this->tokenFunctions ) ) {
105 $this->tokenFunctions = [
106 'edit' => [ self::class,
'getEditToken' ],
107 'delete' => [ self::class,
'getDeleteToken' ],
108 'protect' => [ self::class,
'getProtectToken' ],
109 'move' => [ self::class,
'getMoveToken' ],
110 'block' => [ self::class,
'getBlockToken' ],
111 'unblock' => [ self::class,
'getUnblockToken' ],
112 'email' => [ self::class,
'getEmailToken' ],
113 'import' => [ self::class,
'getImportToken' ],
114 'watch' => [ self::class,
'getWatchToken' ],
116 Hooks::run(
'APIQueryInfoTokens', [ &$this->tokenFunctions ] );
128 self::$cachedTokens = [];
140 ->userHasRight( $wgUser,
'edit' ) ) {
145 if ( !isset( self::$cachedTokens[
'edit'] ) ) {
146 self::$cachedTokens[
'edit'] = $wgUser->getEditToken();
149 return self::$cachedTokens[
'edit'];
158 ->userHasRight( $wgUser,
'delete' ) ) {
163 if ( !isset( self::$cachedTokens[
'delete'] ) ) {
164 self::$cachedTokens[
'delete'] = $wgUser->getEditToken();
167 return self::$cachedTokens[
'delete'];
176 ->userHasRight( $wgUser,
'protect' ) ) {
181 if ( !isset( self::$cachedTokens[
'protect'] ) ) {
182 self::$cachedTokens[
'protect'] = $wgUser->getEditToken();
185 return self::$cachedTokens[
'protect'];
194 ->userHasRight( $wgUser,
'move' ) ) {
199 if ( !isset( self::$cachedTokens[
'move'] ) ) {
200 self::$cachedTokens[
'move'] = $wgUser->getEditToken();
203 return self::$cachedTokens[
'move'];
212 ->userHasRight( $wgUser,
'block' ) ) {
217 if ( !isset( self::$cachedTokens[
'block'] ) ) {
218 self::$cachedTokens[
'block'] = $wgUser->getEditToken();
221 return self::$cachedTokens[
'block'];
237 if ( !$wgUser->canSendEmail() || $wgUser->isBlockedFromEmailuser() ) {
242 if ( !isset( self::$cachedTokens[
'email'] ) ) {
243 self::$cachedTokens[
'email'] = $wgUser->getEditToken();
246 return self::$cachedTokens[
'email'];
254 if ( !MediaWikiServices::getInstance()
256 ->userHasAnyRight( $wgUser,
'import',
'importupload' ) ) {
261 if ( !isset( self::$cachedTokens[
'import'] ) ) {
262 self::$cachedTokens[
'import'] = $wgUser->getEditToken();
265 return self::$cachedTokens[
'import'];
273 if ( !$wgUser->isLoggedIn() ) {
278 if ( !isset( self::$cachedTokens[
'watch'] ) ) {
279 self::$cachedTokens[
'watch'] = $wgUser->getEditToken(
'watch' );
282 return self::$cachedTokens[
'watch'];
290 if ( !$wgUser->isLoggedIn() ) {
295 if ( !isset( self::$cachedTokens[
'options'] ) ) {
296 self::$cachedTokens[
'options'] = $wgUser->getEditToken();
299 return self::$cachedTokens[
'options'];
304 if ( !is_null( $this->params[
'prop'] ) ) {
305 $prop = array_flip( $this->params[
'prop'] );
306 $this->fld_protection = isset( $prop[
'protection'] );
307 $this->fld_watched = isset( $prop[
'watched'] );
308 $this->fld_watchers = isset( $prop[
'watchers'] );
309 $this->fld_visitingwatchers = isset( $prop[
'visitingwatchers'] );
310 $this->fld_notificationtimestamp = isset( $prop[
'notificationtimestamp'] );
311 $this->fld_talkid = isset( $prop[
'talkid'] );
312 $this->fld_subjectid = isset( $prop[
'subjectid'] );
313 $this->fld_url = isset( $prop[
'url'] );
314 $this->fld_readable = isset( $prop[
'readable'] );
315 $this->fld_preload = isset( $prop[
'preload'] );
316 $this->fld_displaytitle = isset( $prop[
'displaytitle'] );
317 $this->fld_varianttitles = isset( $prop[
'varianttitles'] );
321 $this->titles = $pageSet->getGoodTitles();
322 $this->missing = $pageSet->getMissingTitles();
326 uasort( $this->everything, [ Title::class,
'compare' ] );
327 if ( !is_null( $this->params[
'continue'] ) ) {
330 $cont = explode(
'|', $this->params[
'continue'] );
333 foreach ( $this->everything as $pageid =>
$title ) {
337 unset( $this->titles[$pageid] );
338 unset( $this->missing[$pageid] );
339 unset( $this->everything[$pageid] );
343 $this->pageRestrictions = $pageSet->getCustomField(
'page_restrictions' );
345 $this->pageIsRedir = !$pageSet->isResolvingRedirects()
346 ? $pageSet->getCustomField(
'page_is_redirect' )
348 $this->pageIsNew = $pageSet->getCustomField(
'page_is_new' );
350 $this->pageTouched = $pageSet->getCustomField(
'page_touched' );
351 $this->pageLatest = $pageSet->getCustomField(
'page_latest' );
352 $this->pageLength = $pageSet->getCustomField(
'page_len' );
355 if ( $this->fld_protection ) {
359 if ( $this->fld_watched || $this->fld_notificationtimestamp ) {
363 if ( $this->fld_watchers ) {
367 if ( $this->fld_visitingwatchers ) {
372 if ( $this->fld_talkid || $this->fld_subjectid ) {
376 if ( $this->fld_displaytitle ) {
380 if ( $this->fld_varianttitles ) {
385 foreach ( $this->everything as $pageid =>
$title ) {
387 $fit = $pageInfo !==
null && $result->addValue( [
390 ], $pageid, $pageInfo );
393 $title->getNamespace() .
'|' .
409 $titleExists = $pageid > 0;
410 $ns =
$title->getNamespace();
411 $dbkey =
$title->getDBkey();
413 $pageInfo[
'contentmodel'] =
$title->getContentModel();
415 $pageLanguage =
$title->getPageLanguage();
416 $pageInfo[
'pagelanguage'] = $pageLanguage->getCode();
417 $pageInfo[
'pagelanguagehtmlcode'] = $pageLanguage->getHtmlCode();
418 $pageInfo[
'pagelanguagedir'] = $pageLanguage->getDir();
422 if ( $titleExists ) {
423 $pageInfo[
'touched'] =
wfTimestamp( TS_ISO_8601, $this->pageTouched[$pageid] );
424 $pageInfo[
'lastrevid'] = (int)$this->pageLatest[$pageid];
425 $pageInfo[
'length'] = (int)$this->pageLength[$pageid];
427 if ( isset( $this->pageIsRedir[$pageid] ) && $this->pageIsRedir[$pageid] ) {
428 $pageInfo[
'redirect'] =
true;
430 if ( $this->pageIsNew[$pageid] ) {
431 $pageInfo[
'new'] =
true;
435 if ( !is_null( $this->params[
'token'] ) ) {
437 $pageInfo[
'starttimestamp'] =
wfTimestamp( TS_ISO_8601, time() );
438 foreach ( $this->params[
'token'] as
$t ) {
440 if ( $val ===
false ) {
441 $this->
addWarning( [
'apiwarn-tokennotallowed', $t ] );
443 $pageInfo[
$t .
'token'] = $val;
448 if ( $this->fld_protection ) {
449 $pageInfo[
'protection'] = [];
450 if ( isset( $this->protections[$ns][$dbkey] ) ) {
451 $pageInfo[
'protection'] =
452 $this->protections[$ns][$dbkey];
456 $pageInfo[
'restrictiontypes'] = [];
457 if ( isset( $this->restrictionTypes[$ns][$dbkey] ) ) {
458 $pageInfo[
'restrictiontypes'] =
459 $this->restrictionTypes[$ns][$dbkey];
464 if ( $this->fld_watched && $this->watched !==
null ) {
465 $pageInfo[
'watched'] = $this->watched[$ns][$dbkey];
468 if ( $this->fld_watchers ) {
469 if ( $this->watchers !==
null && $this->watchers[$ns][$dbkey] !== 0 ) {
470 $pageInfo[
'watchers'] = $this->watchers[$ns][$dbkey];
471 } elseif ( $this->showZeroWatchers ) {
472 $pageInfo[
'watchers'] = 0;
476 if ( $this->fld_visitingwatchers ) {
477 if ( $this->visitingwatchers !==
null && $this->visitingwatchers[$ns][$dbkey] !== 0 ) {
478 $pageInfo[
'visitingwatchers'] = $this->visitingwatchers[$ns][$dbkey];
479 } elseif ( $this->showZeroWatchers ) {
480 $pageInfo[
'visitingwatchers'] = 0;
484 if ( $this->fld_notificationtimestamp ) {
485 $pageInfo[
'notificationtimestamp'] =
'';
486 if ( $this->notificationtimestamps[$ns][$dbkey] ) {
487 $pageInfo[
'notificationtimestamp'] =
488 wfTimestamp( TS_ISO_8601, $this->notificationtimestamps[$ns][$dbkey] );
492 if ( $this->fld_talkid && isset( $this->talkids[$ns][$dbkey] ) ) {
493 $pageInfo[
'talkid'] = $this->talkids[$ns][$dbkey];
496 if ( $this->fld_subjectid && isset( $this->subjectids[$ns][$dbkey] ) ) {
497 $pageInfo[
'subjectid'] = $this->subjectids[$ns][$dbkey];
500 if ( $this->fld_url ) {
505 if ( $this->fld_readable ) {
511 if ( $this->fld_preload ) {
512 if ( $titleExists ) {
513 $pageInfo[
'preload'] =
'';
518 $pageInfo[
'preload'] = $text;
522 if ( $this->fld_displaytitle ) {
523 if ( isset( $this->displaytitles[$pageid] ) ) {
524 $pageInfo[
'displaytitle'] = $this->displaytitles[$pageid];
526 $pageInfo[
'displaytitle'] =
$title->getPrefixedText();
530 if ( $this->fld_varianttitles && isset( $this->variantTitles[$pageid] ) ) {
531 $pageInfo[
'varianttitles'] = $this->variantTitles[$pageid];
534 if ( $this->params[
'testactions'] ) {
536 if ( $this->countTestedActions >= $limit ) {
540 $detailLevel = $this->params[
'testactionsdetail'];
541 $rigor = $detailLevel ===
'quick' ?
'quick' :
'secure';
543 if ( $errorFormatter->getFormat() ===
'bc' ) {
545 $errorFormatter = $errorFormatter->newWithFormat(
'plaintext' );
549 $pageInfo[
'actions'] = [];
550 foreach ( $this->params[
'testactions'] as $action ) {
551 $this->countTestedActions++;
553 if ( $detailLevel ===
'boolean' ) {
558 $pageInfo[
'actions'][$action] = $errorFormatter->arrayFromStatus( $this->
errorArrayToStatus(
560 $action, $user,
$title, $rigor
575 $this->protections = [];
576 $db = $this->
getDB();
579 if ( count( $this->titles ) ) {
582 $this->
addFields( [
'pr_page',
'pr_type',
'pr_level',
583 'pr_expiry',
'pr_cascade' ] );
584 $this->
addWhereFld(
'pr_page', array_keys( $this->titles ) );
587 foreach (
$res as $row ) {
589 $title = $this->titles[$row->pr_page];
591 'type' => $row->pr_type,
592 'level' => $row->pr_level,
595 if ( $row->pr_cascade ) {
596 $a[
'cascade'] =
true;
598 $this->protections[
$title->getNamespace()][
$title->getDBkey()][] = $a;
601 foreach ( $this->titles as $pageId =>
$title ) {
602 if ( $this->pageRestrictions[$pageId] ) {
603 $namespace =
$title->getNamespace();
604 $dbKey =
$title->getDBkey();
605 $restrictions = explode(
':', trim( $this->pageRestrictions[$pageId] ) );
606 foreach ( $restrictions as $restrict ) {
607 $temp = explode(
'=', trim( $restrict ) );
608 if ( count( $temp ) == 1 ) {
610 $restriction = trim( $temp[0] );
612 if ( $restriction ==
'' ) {
615 $this->protections[$namespace][$dbKey][] = [
617 'level' => $restriction,
618 'expiry' =>
'infinity',
620 $this->protections[$namespace][$dbKey][] = [
622 'level' => $restriction,
623 'expiry' =>
'infinity',
626 $restriction = trim( $temp[1] );
627 if ( $restriction ==
'' ) {
630 $this->protections[$namespace][$dbKey][] = [
632 'level' => $restriction,
633 'expiry' =>
'infinity',
642 if ( count( $this->missing ) ) {
646 $this->
addFields( [
'pt_title',
'pt_namespace',
'pt_create_perm',
'pt_expiry' ] );
647 $this->
addWhere( $lb->constructSet(
'pt', $db ) );
649 foreach (
$res as $row ) {
650 $this->protections[$row->pt_namespace][$row->pt_title][] = [
652 'level' => $row->pt_create_perm,
660 $images = $others = [];
661 foreach ( $this->everything as
$title ) {
663 $images[] =
$title->getDBkey();
668 $this->restrictionTypes[
$title->getNamespace()][
$title->getDBkey()] =
669 array_values(
$title->getRestrictionTypes() );
672 if ( count( $others ) ) {
676 $this->
addTables( [
'page_restrictions',
'page',
'templatelinks' ] );
677 $this->
addFields( [
'pr_type',
'pr_level',
'pr_expiry',
678 'page_title',
'page_namespace',
679 'tl_title',
'tl_namespace' ] );
680 $this->
addWhere( $lb->constructSet(
'tl', $db ) );
681 $this->
addWhere(
'pr_page = page_id' );
682 $this->
addWhere(
'pr_page = tl_from' );
686 foreach (
$res as $row ) {
688 $this->protections[$row->tl_namespace][$row->tl_title][] = [
689 'type' => $row->pr_type,
690 'level' => $row->pr_level,
692 'source' =>
$source->getPrefixedText()
697 if ( count( $images ) ) {
700 $this->
addTables( [
'page_restrictions',
'page',
'imagelinks' ] );
701 $this->
addFields( [
'pr_type',
'pr_level',
'pr_expiry',
702 'page_title',
'page_namespace',
'il_to' ] );
703 $this->
addWhere(
'pr_page = page_id' );
704 $this->
addWhere(
'pr_page = il_from' );
709 foreach (
$res as $row ) {
711 $this->protections[
NS_FILE][$row->il_to][] = [
712 'type' => $row->pr_type,
713 'level' => $row->pr_level,
715 'source' =>
$source->getPrefixedText()
726 $getTitles = $this->talkids = $this->subjectids = [];
727 $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
730 foreach ( $this->everything as
$t ) {
731 if ( $nsInfo->isTalk(
$t->getNamespace() ) ) {
732 if ( $this->fld_subjectid ) {
733 $getTitles[] =
$t->getSubjectPage();
735 } elseif ( $this->fld_talkid ) {
736 $getTitles[] =
$t->getTalkPage();
739 if ( $getTitles === [] ) {
743 $db = $this->
getDB();
750 $this->
addFields( [
'page_title',
'page_namespace',
'page_id' ] );
751 $this->
addWhere( $lb->constructSet(
'page', $db ) );
753 foreach (
$res as $row ) {
754 if ( $nsInfo->isTalk( $row->page_namespace ) ) {
755 $this->talkids[$nsInfo->getSubject( $row->page_namespace )][$row->page_title] =
756 (int)( $row->page_id );
758 $this->subjectids[$nsInfo->getTalk( $row->page_namespace )][$row->page_title] =
759 (int)( $row->page_id );
765 $this->displaytitles = [];
767 $pageIds = array_keys( $this->titles );
769 if ( $pageIds === [] ) {
775 $this->
addFields( [
'pp_page',
'pp_value' ] );
777 $this->
addWhereFld(
'pp_propname',
'displaytitle' );
780 foreach (
$res as $row ) {
781 $this->displaytitles[$row->pp_page] = $row->pp_value;
786 if ( $this->titles === [] ) {
789 $this->variantTitles = [];
790 foreach ( $this->titles as $pageId =>
$t ) {
791 $this->variantTitles[$pageId] = isset( $this->displaytitles[$pageId] )
799 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
800 foreach ( $contLang->getVariants() as $variant ) {
801 $convertTitle = $contLang->autoConvert( $text, $variant );
803 $convertNs = $contLang->convertNamespace( $ns, $variant );
804 $convertTitle = $convertNs .
':' . $convertTitle;
806 $result[$variant] = $convertTitle;
818 if ( $user->isAnon() || count( $this->everything ) == 0
825 $this->notificationtimestamps = [];
827 $store = MediaWikiServices::getInstance()->getWatchedItemStore();
828 $timestamps = $store->getNotificationTimestampsBatch( $user, $this->everything );
830 if ( $this->fld_watched ) {
831 foreach ( $timestamps as $namespaceId => $dbKeys ) {
832 $this->watched[$namespaceId] = array_map(
840 if ( $this->fld_notificationtimestamp ) {
841 $this->notificationtimestamps = $timestamps;
849 if ( count( $this->everything ) == 0 ) {
855 $unwatchedPageThreshold = $this->
getConfig()->get(
'UnwatchedPageThreshold' );
856 if ( !$canUnwatchedpages && !is_int( $unwatchedPageThreshold ) ) {
860 $this->showZeroWatchers = $canUnwatchedpages;
863 if ( !$canUnwatchedpages ) {
864 $countOptions[
'minimumWatchers'] = $unwatchedPageThreshold;
867 $this->watchers = MediaWikiServices::getInstance()->getWatchedItemStore()->countWatchersMultiple(
882 $db = $this->
getDB();
885 $unwatchedPageThreshold = $this->
getConfig()->get(
'UnwatchedPageThreshold' );
886 if ( !$canUnwatchedpages && !is_int( $unwatchedPageThreshold ) ) {
890 $this->showZeroWatchers = $canUnwatchedpages;
892 $titlesWithThresholds = [];
893 if ( $this->titles ) {
898 $this->
addTables( [
'page',
'revision' ] );
899 $this->
addFields( [
'page_namespace',
'page_title',
'rev_timestamp' ] );
901 'page_latest = rev_id',
902 $lb->constructSet(
'page', $db ),
904 $this->
addOption(
'GROUP BY', [
'page_namespace',
'page_title' ] );
905 $timestampRes = $this->
select( __METHOD__ );
907 $age = $config->get(
'WatchersMaxAge' );
909 foreach ( $timestampRes as $row ) {
910 $revTimestamp =
wfTimestamp( TS_UNIX, (
int)$row->rev_timestamp );
911 $timestamps[$row->page_namespace][$row->page_title] = $revTimestamp - $age;
913 $titlesWithThresholds = array_map(
914 function (
LinkTarget $target ) use ( $timestamps ) {
923 if ( $this->missing ) {
924 $titlesWithThresholds = array_merge(
925 $titlesWithThresholds,
928 return [ $target, null ];
934 $store = MediaWikiServices::getInstance()->getWatchedItemStore();
935 $this->visitingwatchers = $store->countVisitingWatchersMultiple(
936 $titlesWithThresholds,
937 !$canUnwatchedpages ? $unwatchedPageThreshold :
null
952 if ( array_diff( (array)
$params[
'prop'], $publicProps ) ) {
957 if (
$params[
'testactions'] ) {
961 if ( !is_null(
$params[
'token'] ) ) {
976 'watchers', #
private
977 'visitingwatchers', #
private
978 'notificationtimestamp', #
private
981 'readable', #
private
997 'testactionsdetail' => [
1015 'action=query&prop=info&titles=Main%20Page'
1016 =>
'apihelp-query+info-example-simple',
1017 'action=query&prop=info&inprop=protection&titles=Main%20Page'
1018 =>
'apihelp-query+info-example-protection',
1023 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Info';