110 parent::__construct( $queryModule, $moduleName,
'in' );
123 $pageSet->requestField(
'page_restrictions' );
127 $pageSet->requestField(
'page_is_redirect' );
128 $pageSet->requestField(
'page_is_new' );
130 $pageSet->requestField(
'page_touched' );
131 $pageSet->requestField(
'page_latest' );
132 $pageSet->requestField(
'page_len' );
133 $pageSet->requestField(
'page_content_model' );
134 if ( $config->get(
'PageLanguageUseDB' ) ) {
135 $pageSet->requestField(
'page_lang' );
148 if ( isset( $this->tokenFunctions ) ) {
158 $this->tokenFunctions = [
159 'edit' => [ self::class,
'getEditToken' ],
160 'delete' => [ self::class,
'getDeleteToken' ],
161 'protect' => [ self::class,
'getProtectToken' ],
162 'move' => [ self::class,
'getMoveToken' ],
163 'block' => [ self::class,
'getBlockToken' ],
164 'unblock' => [ self::class,
'getUnblockToken' ],
165 'email' => [ self::class,
'getEmailToken' ],
166 'import' => [ self::class,
'getImportToken' ],
167 'watch' => [ self::class,
'getWatchToken' ],
180 self::$cachedTokens = [];
193 if ( !MediaWikiServices::getInstance()
195 ->userHasRight( $user, $right )
201 if ( !isset( self::$cachedTokens[
'edit'] ) ) {
205 return self::$cachedTokens[
'edit'];
274 if ( !isset( self::$cachedTokens[
'email'] ) ) {
278 return self::$cachedTokens[
'email'];
287 if ( !MediaWikiServices::getInstance()
289 ->userHasAnyRight( $user,
'import',
'importupload' ) ) {
294 if ( !isset( self::$cachedTokens[
'import'] ) ) {
298 return self::$cachedTokens[
'import'];
312 if ( !isset( self::$cachedTokens[
'watch'] ) ) {
313 self::$cachedTokens[
'watch'] = $user->
getEditToken(
'watch' );
316 return self::$cachedTokens[
'watch'];
330 if ( !isset( self::$cachedTokens[
'options'] ) ) {
334 return self::$cachedTokens[
'options'];
339 if ( $this->params[
'prop'] !==
null ) {
340 $prop = array_flip( $this->params[
'prop'] );
341 $this->fld_protection = isset( $prop[
'protection'] );
342 $this->fld_watched = isset( $prop[
'watched'] );
343 $this->fld_watchers = isset( $prop[
'watchers'] );
344 $this->fld_visitingwatchers = isset( $prop[
'visitingwatchers'] );
345 $this->fld_notificationtimestamp = isset( $prop[
'notificationtimestamp'] );
346 $this->fld_talkid = isset( $prop[
'talkid'] );
347 $this->fld_subjectid = isset( $prop[
'subjectid'] );
348 $this->fld_url = isset( $prop[
'url'] );
349 $this->fld_readable = isset( $prop[
'readable'] );
350 $this->fld_preload = isset( $prop[
'preload'] );
351 $this->fld_displaytitle = isset( $prop[
'displaytitle'] );
352 $this->fld_varianttitles = isset( $prop[
'varianttitles'] );
353 $this->fld_linkclasses = isset( $prop[
'linkclasses'] );
357 $this->titles = $pageSet->getGoodTitles();
358 $this->missing = $pageSet->getMissingTitles();
362 uasort( $this->everything, [ Title::class,
'compare' ] );
363 if ( $this->params[
'continue'] !==
null ) {
366 $cont = explode(
'|', $this->params[
'continue'] );
368 $conttitle = $this->titleFactory->makeTitleSafe( $cont[0], $cont[1] );
369 foreach ( $this->everything as $pageid =>
$title ) {
373 unset( $this->titles[$pageid] );
374 unset( $this->missing[$pageid] );
375 unset( $this->everything[$pageid] );
379 $this->pageRestrictions = $pageSet->getCustomField(
'page_restrictions' );
381 $this->pageIsRedir = !$pageSet->isResolvingRedirects()
382 ? $pageSet->getCustomField(
'page_is_redirect' )
384 $this->pageIsNew = $pageSet->getCustomField(
'page_is_new' );
386 $this->pageTouched = $pageSet->getCustomField(
'page_touched' );
387 $this->pageLatest = $pageSet->getCustomField(
'page_latest' );
388 $this->pageLength = $pageSet->getCustomField(
'page_len' );
391 if ( $this->fld_protection ) {
395 if ( $this->fld_watched || $this->fld_notificationtimestamp ) {
399 if ( $this->fld_watchers ) {
403 if ( $this->fld_visitingwatchers ) {
408 if ( $this->fld_talkid || $this->fld_subjectid ) {
412 if ( $this->fld_displaytitle ) {
416 if ( $this->fld_varianttitles ) {
420 if ( $this->fld_linkclasses ) {
425 foreach ( $this->everything as $pageid =>
$title ) {
427 $fit = $pageInfo !==
null && $result->addValue( [
430 ], $pageid, $pageInfo );
433 $title->getNamespace() .
'|' .
449 $titleExists = $pageid > 0;
450 $ns =
$title->getNamespace();
451 $dbkey =
$title->getDBkey();
453 $pageInfo[
'contentmodel'] =
$title->getContentModel();
455 $pageLanguage =
$title->getPageLanguage();
456 $pageInfo[
'pagelanguage'] = $pageLanguage->getCode();
457 $pageInfo[
'pagelanguagehtmlcode'] = $pageLanguage->getHtmlCode();
458 $pageInfo[
'pagelanguagedir'] = $pageLanguage->getDir();
462 if ( $titleExists ) {
463 $pageInfo[
'touched'] =
wfTimestamp( TS_ISO_8601, $this->pageTouched[$pageid] );
464 $pageInfo[
'lastrevid'] = (int)$this->pageLatest[$pageid];
465 $pageInfo[
'length'] = (int)$this->pageLength[$pageid];
467 if ( isset( $this->pageIsRedir[$pageid] ) && $this->pageIsRedir[$pageid] ) {
468 $pageInfo[
'redirect'] =
true;
470 if ( $this->pageIsNew[$pageid] ) {
471 $pageInfo[
'new'] =
true;
475 if ( $this->params[
'token'] !==
null ) {
477 $pageInfo[
'starttimestamp'] =
wfTimestamp( TS_ISO_8601, time() );
478 foreach ( $this->params[
'token'] as
$t ) {
480 if ( $val ===
false ) {
481 $this->
addWarning( [
'apiwarn-tokennotallowed', $t ] );
483 $pageInfo[
$t .
'token'] = $val;
488 if ( $this->fld_protection ) {
489 $pageInfo[
'protection'] = [];
490 if ( isset( $this->protections[$ns][$dbkey] ) ) {
491 $pageInfo[
'protection'] =
492 $this->protections[$ns][$dbkey];
496 $pageInfo[
'restrictiontypes'] = [];
497 if ( isset( $this->restrictionTypes[$ns][$dbkey] ) ) {
498 $pageInfo[
'restrictiontypes'] =
499 $this->restrictionTypes[$ns][$dbkey];
504 if ( $this->fld_watched ) {
505 $pageInfo[
'watched'] =
false;
507 if ( isset( $this->watched[$ns][$dbkey] ) ) {
508 $pageInfo[
'watched'] = $this->watched[$ns][$dbkey];
511 if ( isset( $this->watchlistExpiries[$ns][$dbkey] ) ) {
512 $pageInfo[
'watchlistexpiry'] = $this->watchlistExpiries[$ns][$dbkey];
516 if ( $this->fld_watchers ) {
517 if ( $this->watchers !==
null && $this->watchers[$ns][$dbkey] !== 0 ) {
518 $pageInfo[
'watchers'] = $this->watchers[$ns][$dbkey];
519 } elseif ( $this->showZeroWatchers ) {
520 $pageInfo[
'watchers'] = 0;
524 if ( $this->fld_visitingwatchers ) {
525 if ( $this->visitingwatchers !==
null && $this->visitingwatchers[$ns][$dbkey] !== 0 ) {
526 $pageInfo[
'visitingwatchers'] = $this->visitingwatchers[$ns][$dbkey];
527 } elseif ( $this->showZeroWatchers ) {
528 $pageInfo[
'visitingwatchers'] = 0;
532 if ( $this->fld_notificationtimestamp ) {
533 $pageInfo[
'notificationtimestamp'] =
'';
534 if ( isset( $this->notificationtimestamps[$ns][$dbkey] ) ) {
535 $pageInfo[
'notificationtimestamp'] =
536 wfTimestamp( TS_ISO_8601, $this->notificationtimestamps[$ns][$dbkey] );
540 if ( $this->fld_talkid && isset( $this->talkids[$ns][$dbkey] ) ) {
541 $pageInfo[
'talkid'] = $this->talkids[$ns][$dbkey];
544 if ( $this->fld_subjectid && isset( $this->subjectids[$ns][$dbkey] ) ) {
545 $pageInfo[
'subjectid'] = $this->subjectids[$ns][$dbkey];
548 if ( $this->fld_url ) {
553 if ( $this->fld_readable ) {
559 if ( $this->fld_preload ) {
560 if ( $titleExists ) {
561 $pageInfo[
'preload'] =
'';
566 $pageInfo[
'preload'] = $text;
570 if ( $this->fld_displaytitle ) {
571 if ( isset( $this->displaytitles[$pageid] ) ) {
572 $pageInfo[
'displaytitle'] = $this->displaytitles[$pageid];
574 $pageInfo[
'displaytitle'] =
$title->getPrefixedText();
578 if ( $this->fld_varianttitles && isset( $this->variantTitles[$pageid] ) ) {
579 $pageInfo[
'varianttitles'] = $this->variantTitles[$pageid];
582 if ( $this->fld_linkclasses && isset( $this->linkClasses[$pageid] ) ) {
583 $pageInfo[
'linkclasses'] = $this->linkClasses[$pageid];
586 if ( $this->params[
'testactions'] ) {
588 if ( $this->countTestedActions >= $limit ) {
592 $detailLevel = $this->params[
'testactionsdetail'];
593 $rigor = $detailLevel ===
'quick'
594 ? PermissionManager::RIGOR_QUICK
596 : PermissionManager::RIGOR_FULL;
598 if ( $errorFormatter->getFormat() ===
'bc' ) {
600 $errorFormatter = $errorFormatter->newWithFormat(
'plaintext' );
604 $pageInfo[
'actions'] = [];
605 foreach ( $this->params[
'testactions'] as $action ) {
606 $this->countTestedActions++;
608 if ( $detailLevel ===
'boolean' ) {
613 $pageInfo[
'actions'][$action] = $errorFormatter->arrayFromStatus( $this->
errorArrayToStatus(
615 $action, $user,
$title, $rigor
630 $this->protections = [];
631 $db = $this->
getDB();
634 if ( count( $this->titles ) ) {
637 $this->
addFields( [
'pr_page',
'pr_type',
'pr_level',
638 'pr_expiry',
'pr_cascade' ] );
639 $this->
addWhereFld(
'pr_page', array_keys( $this->titles ) );
642 foreach (
$res as $row ) {
644 $title = $this->titles[$row->pr_page];
646 'type' => $row->pr_type,
647 'level' => $row->pr_level,
650 if ( $row->pr_cascade ) {
651 $a[
'cascade'] =
true;
653 $this->protections[
$title->getNamespace()][
$title->getDBkey()][] = $a;
656 foreach ( $this->titles as $pageId =>
$title ) {
657 if ( $this->pageRestrictions[$pageId] ) {
658 $namespace =
$title->getNamespace();
659 $dbKey =
$title->getDBkey();
660 $restrictions = explode(
':', trim( $this->pageRestrictions[$pageId] ) );
661 foreach ( $restrictions as $restrict ) {
662 $temp = explode(
'=', trim( $restrict ) );
663 if ( count( $temp ) == 1 ) {
665 $restriction = trim( $temp[0] );
667 if ( $restriction ==
'' ) {
670 $this->protections[$namespace][$dbKey][] = [
672 'level' => $restriction,
673 'expiry' =>
'infinity',
675 $this->protections[$namespace][$dbKey][] = [
677 'level' => $restriction,
678 'expiry' =>
'infinity',
681 $restriction = trim( $temp[1] );
682 if ( $restriction ==
'' ) {
685 $this->protections[$namespace][$dbKey][] = [
687 'level' => $restriction,
688 'expiry' =>
'infinity',
697 if ( count( $this->missing ) ) {
699 $lb = $this->linkBatchFactory->newLinkBatch( $this->missing );
701 $this->
addFields( [
'pt_title',
'pt_namespace',
'pt_create_perm',
'pt_expiry' ] );
702 $this->
addWhere( $lb->constructSet(
'pt', $db ) );
704 foreach (
$res as $row ) {
705 $this->protections[$row->pt_namespace][$row->pt_title][] = [
707 'level' => $row->pt_create_perm,
715 $images = $others = [];
716 foreach ( $this->everything as
$title ) {
718 $images[] =
$title->getDBkey();
723 $this->restrictionTypes[
$title->getNamespace()][
$title->getDBkey()] =
724 array_values(
$title->getRestrictionTypes() );
727 if ( count( $others ) ) {
729 $lb = $this->linkBatchFactory->newLinkBatch( $others );
731 $this->
addTables( [
'page_restrictions',
'page',
'templatelinks' ] );
732 $this->
addFields( [
'pr_type',
'pr_level',
'pr_expiry',
733 'page_title',
'page_namespace',
734 'tl_title',
'tl_namespace' ] );
735 $this->
addWhere( $lb->constructSet(
'tl', $db ) );
736 $this->
addWhere(
'pr_page = page_id' );
737 $this->
addWhere(
'pr_page = tl_from' );
741 foreach (
$res as $row ) {
742 $source = $this->titleFactory->makeTitle( $row->page_namespace, $row->page_title );
743 $this->protections[$row->tl_namespace][$row->tl_title][] = [
744 'type' => $row->pr_type,
745 'level' => $row->pr_level,
747 'source' =>
$source->getPrefixedText()
752 if ( count( $images ) ) {
755 $this->
addTables( [
'page_restrictions',
'page',
'imagelinks' ] );
756 $this->
addFields( [
'pr_type',
'pr_level',
'pr_expiry',
757 'page_title',
'page_namespace',
'il_to' ] );
758 $this->
addWhere(
'pr_page = page_id' );
759 $this->
addWhere(
'pr_page = il_from' );
764 foreach (
$res as $row ) {
765 $source = $this->titleFactory->makeTitle( $row->page_namespace, $row->page_title );
766 $this->protections[
NS_FILE][$row->il_to][] = [
767 'type' => $row->pr_type,
768 'level' => $row->pr_level,
770 'source' =>
$source->getPrefixedText()
781 $getTitles = $this->talkids = $this->subjectids = [];
785 foreach ( $this->everything as
$t ) {
786 if ( $nsInfo->isTalk(
$t->getNamespace() ) ) {
787 if ( $this->fld_subjectid ) {
788 $getTitles[] =
$t->getSubjectPage();
790 } elseif ( $this->fld_talkid ) {
791 $getTitles[] =
$t->getTalkPage();
794 if ( $getTitles === [] ) {
798 $db = $this->
getDB();
802 $lb = $this->linkBatchFactory->newLinkBatch( $getTitles );
805 $this->
addFields( [
'page_title',
'page_namespace',
'page_id' ] );
806 $this->
addWhere( $lb->constructSet(
'page', $db ) );
808 foreach (
$res as $row ) {
809 if ( $nsInfo->isTalk( $row->page_namespace ) ) {
810 $this->talkids[$nsInfo->getSubject( $row->page_namespace )][$row->page_title] =
811 (int)( $row->page_id );
813 $this->subjectids[$nsInfo->getTalk( $row->page_namespace )][$row->page_title] =
814 (int)( $row->page_id );
820 $this->displaytitles = [];
822 $pageIds = array_keys( $this->titles );
824 if ( $pageIds === [] ) {
830 $this->
addFields( [
'pp_page',
'pp_value' ] );
832 $this->
addWhereFld(
'pp_propname',
'displaytitle' );
835 foreach (
$res as $row ) {
836 $this->displaytitles[$row->pp_page] = $row->pp_value;
848 if ( $this->titles === [] ) {
859 foreach ( $this->titles as $pageId =>
$title ) {
860 $pdbk =
$title->getPrefixedDBkey();
861 $pagemap[$pageId] = $pdbk;
862 $classes[$pdbk] =
$title->isRedirect() ?
'mw-redirect' :
'';
865 $context_title = $this->titleFactory->newFromLinkTarget(
866 $context_title ?? $this->titleFactory->newMainPage()
869 $pagemap, $classes, $context_title
876 $this->linkClasses = [];
877 foreach ( $this->titles as $pageId =>
$title ) {
878 $pdbk =
$title->getPrefixedDBkey();
879 $this->linkClasses[$pageId] = preg_split(
880 '/\s+/', $classes[$pdbk] ??
'', -1, PREG_SPLIT_NO_EMPTY
886 if ( $this->titles === [] ) {
889 $this->variantTitles = [];
890 foreach ( $this->titles as $pageId =>
$t ) {
891 $this->variantTitles[$pageId] = isset( $this->displaytitles[$pageId] )
900 foreach ( $contLang->getVariants() as $variant ) {
901 $convertTitle = $contLang->
autoConvert( $text, $variant );
903 $convertNs = $contLang->convertNamespace( $ns, $variant );
904 $convertTitle = $convertNs .
':' . $convertTitle;
906 $result[$variant] = $convertTitle;
918 if ( $user->isAnon() || count( $this->everything ) == 0
925 $this->watchlistExpiries = [];
926 $this->notificationtimestamps = [];
929 $items = $this->watchedItemStore->loadWatchedItemsBatch( $user, $this->everything );
931 foreach ( $items as $item ) {
932 $nsId = $item->getLinkTarget()->getNamespace();
933 $dbKey = $item->getLinkTarget()->getDBkey();
935 if ( $this->fld_watched ) {
936 $this->watched[$nsId][$dbKey] =
true;
938 $expiry = $item->getExpiry( TS_ISO_8601 );
940 $this->watchlistExpiries[$nsId][$dbKey] = $expiry;
944 if ( $this->fld_notificationtimestamp ) {
945 $this->notificationtimestamps[$nsId][$dbKey] = $item->getNotificationTimestamp();
954 if ( count( $this->everything ) == 0 ) {
960 $unwatchedPageThreshold = $this->
getConfig()->get(
'UnwatchedPageThreshold' );
961 if ( !$canUnwatchedpages && !is_int( $unwatchedPageThreshold ) ) {
965 $this->showZeroWatchers = $canUnwatchedpages;
968 if ( !$canUnwatchedpages ) {
969 $countOptions[
'minimumWatchers'] = $unwatchedPageThreshold;
972 $this->watchers = $this->watchedItemStore->countWatchersMultiple(
987 $db = $this->
getDB();
990 $unwatchedPageThreshold = $this->
getConfig()->get(
'UnwatchedPageThreshold' );
991 if ( !$canUnwatchedpages && !is_int( $unwatchedPageThreshold ) ) {
995 $this->showZeroWatchers = $canUnwatchedpages;
997 $titlesWithThresholds = [];
998 if ( $this->titles ) {
999 $lb = $this->linkBatchFactory->newLinkBatch( $this->titles );
1003 $this->
addTables( [
'page',
'revision' ] );
1004 $this->
addFields( [
'page_namespace',
'page_title',
'rev_timestamp' ] );
1006 'page_latest = rev_id',
1007 $lb->constructSet(
'page', $db ),
1009 $this->
addOption(
'GROUP BY', [
'page_namespace',
'page_title' ] );
1010 $timestampRes = $this->
select( __METHOD__ );
1012 $age = $config->get(
'WatchersMaxAge' );
1014 foreach ( $timestampRes as $row ) {
1015 $revTimestamp =
wfTimestamp( TS_UNIX, (
int)$row->rev_timestamp );
1016 $timestamps[$row->page_namespace][$row->page_title] = (int)$revTimestamp - $age;
1018 $titlesWithThresholds = array_map(
1019 function (
LinkTarget $target ) use ( $timestamps ) {
1028 if ( $this->missing ) {
1029 $titlesWithThresholds = array_merge(
1030 $titlesWithThresholds,
1033 return [ $target, null ];
1039 $this->visitingwatchers = $this->watchedItemStore->countVisitingWatchersMultiple(
1040 $titlesWithThresholds,
1041 !$canUnwatchedpages ? $unwatchedPageThreshold :
null
1056 if ( array_diff( (array)
$params[
'prop'], $publicProps ) ) {
1061 if (
$params[
'testactions'] ) {
1065 if (
$params[
'token'] !==
null ) {
1079 'watched', #
private
1080 'watchers', #
private
1081 'visitingwatchers', #
private
1082 'notificationtimestamp', #
private
1085 'readable', #
private
1089 'linkclasses', #
private: stub length (and possibly hook colors)
1101 TitleDef::PARAM_RETURN_OBJECT =>
true,
1107 'testactionsdetail' => [
1125 'action=query&prop=info&titles=Main%20Page'
1126 =>
'apihelp-query+info-example-simple',
1127 'action=query&prop=info&inprop=protection&titles=Main%20Page'
1128 =>
'apihelp-query+info-example-protection',
1133 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Info';