61 parent::__construct( $query, $moduleName,
'in' );
69 $pageSet->requestField(
'page_restrictions' );
73 $pageSet->requestField(
'page_is_redirect' );
74 $pageSet->requestField(
'page_is_new' );
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' );
94 if ( isset( $this->tokenFunctions ) ) {
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' ],
115 $this->
getHookRunner()->onAPIQueryInfoTokens( $this->tokenFunctions );
127 self::$cachedTokens = [];
142 if ( !MediaWikiServices::getInstance()
144 ->userHasRight( $user, $right )
150 if ( !isset( self::$cachedTokens[
'edit'] ) ) {
151 self::$cachedTokens[
'edit'] = $user->getEditToken();
154 return self::$cachedTokens[
'edit'];
207 if ( !$user->canSendEmail() || $user->isBlockedFromEmailuser() ) {
212 if ( !isset( self::$cachedTokens[
'email'] ) ) {
213 self::$cachedTokens[
'email'] = $user->getEditToken();
216 return self::$cachedTokens[
'email'];
226 if ( !MediaWikiServices::getInstance()
228 ->userHasAnyRight( $user,
'import',
'importupload' ) ) {
233 if ( !isset( self::$cachedTokens[
'import'] ) ) {
234 self::$cachedTokens[
'import'] = $user->getEditToken();
237 return self::$cachedTokens[
'import'];
247 if ( !$user->isLoggedIn() ) {
252 if ( !isset( self::$cachedTokens[
'watch'] ) ) {
253 self::$cachedTokens[
'watch'] = $user->getEditToken(
'watch' );
256 return self::$cachedTokens[
'watch'];
266 if ( !$user->isLoggedIn() ) {
271 if ( !isset( self::$cachedTokens[
'options'] ) ) {
272 self::$cachedTokens[
'options'] = $user->getEditToken();
275 return self::$cachedTokens[
'options'];
280 if ( $this->params[
'prop'] !==
null ) {
281 $prop = array_flip( $this->params[
'prop'] );
282 $this->fld_protection = isset( $prop[
'protection'] );
283 $this->fld_watched = isset( $prop[
'watched'] );
284 $this->fld_watchers = isset( $prop[
'watchers'] );
285 $this->fld_visitingwatchers = isset( $prop[
'visitingwatchers'] );
286 $this->fld_notificationtimestamp = isset( $prop[
'notificationtimestamp'] );
287 $this->fld_talkid = isset( $prop[
'talkid'] );
288 $this->fld_subjectid = isset( $prop[
'subjectid'] );
289 $this->fld_url = isset( $prop[
'url'] );
290 $this->fld_readable = isset( $prop[
'readable'] );
291 $this->fld_preload = isset( $prop[
'preload'] );
292 $this->fld_displaytitle = isset( $prop[
'displaytitle'] );
293 $this->fld_varianttitles = isset( $prop[
'varianttitles'] );
297 $this->titles = $pageSet->getGoodTitles();
298 $this->missing = $pageSet->getMissingTitles();
302 uasort( $this->everything, [ Title::class,
'compare' ] );
303 if ( $this->params[
'continue'] !==
null ) {
306 $cont = explode(
'|', $this->params[
'continue'] );
308 $conttitle = Title::makeTitleSafe( $cont[0], $cont[1] );
309 foreach ( $this->everything as $pageid =>
$title ) {
310 if ( Title::compare(
$title, $conttitle ) >= 0 ) {
313 unset( $this->titles[$pageid] );
314 unset( $this->missing[$pageid] );
315 unset( $this->everything[$pageid] );
319 $this->pageRestrictions = $pageSet->getCustomField(
'page_restrictions' );
321 $this->pageIsRedir = !$pageSet->isResolvingRedirects()
322 ? $pageSet->getCustomField(
'page_is_redirect' )
324 $this->pageIsNew = $pageSet->getCustomField(
'page_is_new' );
326 $this->pageTouched = $pageSet->getCustomField(
'page_touched' );
327 $this->pageLatest = $pageSet->getCustomField(
'page_latest' );
328 $this->pageLength = $pageSet->getCustomField(
'page_len' );
331 if ( $this->fld_protection ) {
335 if ( $this->fld_watched || $this->fld_notificationtimestamp ) {
339 if ( $this->fld_watchers ) {
343 if ( $this->fld_visitingwatchers ) {
348 if ( $this->fld_talkid || $this->fld_subjectid ) {
352 if ( $this->fld_displaytitle ) {
356 if ( $this->fld_varianttitles ) {
361 foreach ( $this->everything as $pageid =>
$title ) {
363 $fit = $pageInfo !==
null && $result->addValue( [
366 ], $pageid, $pageInfo );
369 $title->getNamespace() .
'|' .
385 $titleExists = $pageid > 0;
386 $ns =
$title->getNamespace();
387 $dbkey =
$title->getDBkey();
389 $pageInfo[
'contentmodel'] =
$title->getContentModel();
391 $pageLanguage =
$title->getPageLanguage();
392 $pageInfo[
'pagelanguage'] = $pageLanguage->getCode();
393 $pageInfo[
'pagelanguagehtmlcode'] = $pageLanguage->getHtmlCode();
394 $pageInfo[
'pagelanguagedir'] = $pageLanguage->getDir();
398 if ( $titleExists ) {
399 $pageInfo[
'touched'] =
wfTimestamp( TS_ISO_8601, $this->pageTouched[$pageid] );
400 $pageInfo[
'lastrevid'] = (int)$this->pageLatest[$pageid];
401 $pageInfo[
'length'] = (int)$this->pageLength[$pageid];
403 if ( isset( $this->pageIsRedir[$pageid] ) && $this->pageIsRedir[$pageid] ) {
404 $pageInfo[
'redirect'] =
true;
406 if ( $this->pageIsNew[$pageid] ) {
407 $pageInfo[
'new'] =
true;
411 if ( $this->params[
'token'] !==
null ) {
413 $pageInfo[
'starttimestamp'] =
wfTimestamp( TS_ISO_8601, time() );
414 foreach ( $this->params[
'token'] as
$t ) {
416 if ( $val ===
false ) {
417 $this->
addWarning( [
'apiwarn-tokennotallowed', $t ] );
419 $pageInfo[
$t .
'token'] = $val;
424 if ( $this->fld_protection ) {
425 $pageInfo[
'protection'] = [];
426 if ( isset( $this->protections[$ns][$dbkey] ) ) {
427 $pageInfo[
'protection'] =
428 $this->protections[$ns][$dbkey];
430 ApiResult::setIndexedTagName( $pageInfo[
'protection'],
'pr' );
432 $pageInfo[
'restrictiontypes'] = [];
433 if ( isset( $this->restrictionTypes[$ns][$dbkey] ) ) {
434 $pageInfo[
'restrictiontypes'] =
435 $this->restrictionTypes[$ns][$dbkey];
437 ApiResult::setIndexedTagName( $pageInfo[
'restrictiontypes'],
'rt' );
440 if ( $this->fld_watched && $this->watched !==
null ) {
441 $pageInfo[
'watched'] = $this->watched[$ns][$dbkey];
444 if ( $this->fld_watchers ) {
445 if ( $this->watchers !==
null && $this->watchers[$ns][$dbkey] !== 0 ) {
446 $pageInfo[
'watchers'] = $this->watchers[$ns][$dbkey];
447 } elseif ( $this->showZeroWatchers ) {
448 $pageInfo[
'watchers'] = 0;
452 if ( $this->fld_visitingwatchers ) {
453 if ( $this->visitingwatchers !==
null && $this->visitingwatchers[$ns][$dbkey] !== 0 ) {
454 $pageInfo[
'visitingwatchers'] = $this->visitingwatchers[$ns][$dbkey];
455 } elseif ( $this->showZeroWatchers ) {
456 $pageInfo[
'visitingwatchers'] = 0;
460 if ( $this->fld_notificationtimestamp ) {
461 $pageInfo[
'notificationtimestamp'] =
'';
462 if ( $this->notificationtimestamps[$ns][$dbkey] ) {
463 $pageInfo[
'notificationtimestamp'] =
464 wfTimestamp( TS_ISO_8601, $this->notificationtimestamps[$ns][$dbkey] );
468 if ( $this->fld_talkid && isset( $this->talkids[$ns][$dbkey] ) ) {
469 $pageInfo[
'talkid'] = $this->talkids[$ns][$dbkey];
472 if ( $this->fld_subjectid && isset( $this->subjectids[$ns][$dbkey] ) ) {
473 $pageInfo[
'subjectid'] = $this->subjectids[$ns][$dbkey];
476 if ( $this->fld_url ) {
481 if ( $this->fld_readable ) {
487 if ( $this->fld_preload ) {
488 if ( $titleExists ) {
489 $pageInfo[
'preload'] =
'';
494 $pageInfo[
'preload'] = $text;
498 if ( $this->fld_displaytitle ) {
499 if ( isset( $this->displaytitles[$pageid] ) ) {
500 $pageInfo[
'displaytitle'] = $this->displaytitles[$pageid];
502 $pageInfo[
'displaytitle'] =
$title->getPrefixedText();
506 if ( $this->fld_varianttitles && isset( $this->variantTitles[$pageid] ) ) {
507 $pageInfo[
'varianttitles'] = $this->variantTitles[$pageid];
510 if ( $this->params[
'testactions'] ) {
512 if ( $this->countTestedActions >= $limit ) {
516 $detailLevel = $this->params[
'testactionsdetail'];
517 $rigor = $detailLevel ===
'quick'
518 ? PermissionManager::RIGOR_QUICK
520 : PermissionManager::RIGOR_FULL;
522 if ( $errorFormatter->getFormat() ===
'bc' ) {
524 $errorFormatter = $errorFormatter->newWithFormat(
'plaintext' );
528 $pageInfo[
'actions'] = [];
529 foreach ( $this->params[
'testactions'] as $action ) {
530 $this->countTestedActions++;
532 if ( $detailLevel ===
'boolean' ) {
537 $pageInfo[
'actions'][$action] = $errorFormatter->arrayFromStatus( $this->
errorArrayToStatus(
539 $action, $user,
$title, $rigor
554 $this->protections = [];
555 $db = $this->
getDB();
558 if ( count( $this->titles ) ) {
561 $this->
addFields( [
'pr_page',
'pr_type',
'pr_level',
562 'pr_expiry',
'pr_cascade' ] );
563 $this->
addWhereFld(
'pr_page', array_keys( $this->titles ) );
566 foreach (
$res as $row ) {
568 $title = $this->titles[$row->pr_page];
570 'type' => $row->pr_type,
571 'level' => $row->pr_level,
572 'expiry' => ApiResult::formatExpiry( $row->pr_expiry )
574 if ( $row->pr_cascade ) {
575 $a[
'cascade'] =
true;
577 $this->protections[
$title->getNamespace()][
$title->getDBkey()][] = $a;
580 foreach ( $this->titles as $pageId =>
$title ) {
581 if ( $this->pageRestrictions[$pageId] ) {
582 $namespace =
$title->getNamespace();
583 $dbKey =
$title->getDBkey();
584 $restrictions = explode(
':', trim( $this->pageRestrictions[$pageId] ) );
585 foreach ( $restrictions as $restrict ) {
586 $temp = explode(
'=', trim( $restrict ) );
587 if ( count( $temp ) == 1 ) {
589 $restriction = trim( $temp[0] );
591 if ( $restriction ==
'' ) {
594 $this->protections[$namespace][$dbKey][] = [
596 'level' => $restriction,
597 'expiry' =>
'infinity',
599 $this->protections[$namespace][$dbKey][] = [
601 'level' => $restriction,
602 'expiry' =>
'infinity',
605 $restriction = trim( $temp[1] );
606 if ( $restriction ==
'' ) {
609 $this->protections[$namespace][$dbKey][] = [
611 'level' => $restriction,
612 'expiry' =>
'infinity',
621 if ( count( $this->missing ) ) {
625 $this->
addFields( [
'pt_title',
'pt_namespace',
'pt_create_perm',
'pt_expiry' ] );
626 $this->
addWhere( $lb->constructSet(
'pt', $db ) );
628 foreach (
$res as $row ) {
629 $this->protections[$row->pt_namespace][$row->pt_title][] = [
631 'level' => $row->pt_create_perm,
632 'expiry' => ApiResult::formatExpiry( $row->pt_expiry )
639 $images = $others = [];
640 foreach ( $this->everything as
$title ) {
642 $images[] =
$title->getDBkey();
647 $this->restrictionTypes[
$title->getNamespace()][
$title->getDBkey()] =
648 array_values(
$title->getRestrictionTypes() );
651 if ( count( $others ) ) {
655 $this->
addTables( [
'page_restrictions',
'page',
'templatelinks' ] );
656 $this->
addFields( [
'pr_type',
'pr_level',
'pr_expiry',
657 'page_title',
'page_namespace',
658 'tl_title',
'tl_namespace' ] );
659 $this->
addWhere( $lb->constructSet(
'tl', $db ) );
660 $this->
addWhere(
'pr_page = page_id' );
661 $this->
addWhere(
'pr_page = tl_from' );
665 foreach (
$res as $row ) {
666 $source = Title::makeTitle( $row->page_namespace, $row->page_title );
667 $this->protections[$row->tl_namespace][$row->tl_title][] = [
668 'type' => $row->pr_type,
669 'level' => $row->pr_level,
670 'expiry' => ApiResult::formatExpiry( $row->pr_expiry ),
671 'source' =>
$source->getPrefixedText()
676 if ( count( $images ) ) {
679 $this->
addTables( [
'page_restrictions',
'page',
'imagelinks' ] );
680 $this->
addFields( [
'pr_type',
'pr_level',
'pr_expiry',
681 'page_title',
'page_namespace',
'il_to' ] );
682 $this->
addWhere(
'pr_page = page_id' );
683 $this->
addWhere(
'pr_page = il_from' );
688 foreach (
$res as $row ) {
689 $source = Title::makeTitle( $row->page_namespace, $row->page_title );
690 $this->protections[
NS_FILE][$row->il_to][] = [
691 'type' => $row->pr_type,
692 'level' => $row->pr_level,
693 'expiry' => ApiResult::formatExpiry( $row->pr_expiry ),
694 'source' =>
$source->getPrefixedText()
705 $getTitles = $this->talkids = $this->subjectids = [];
706 $nsInfo = MediaWikiServices::getInstance()->getNamespaceInfo();
709 foreach ( $this->everything as
$t ) {
710 if ( $nsInfo->isTalk(
$t->getNamespace() ) ) {
711 if ( $this->fld_subjectid ) {
712 $getTitles[] =
$t->getSubjectPage();
714 } elseif ( $this->fld_talkid ) {
715 $getTitles[] =
$t->getTalkPage();
718 if ( $getTitles === [] ) {
722 $db = $this->
getDB();
729 $this->
addFields( [
'page_title',
'page_namespace',
'page_id' ] );
730 $this->
addWhere( $lb->constructSet(
'page', $db ) );
732 foreach (
$res as $row ) {
733 if ( $nsInfo->isTalk( $row->page_namespace ) ) {
734 $this->talkids[$nsInfo->getSubject( $row->page_namespace )][$row->page_title] =
735 (int)( $row->page_id );
737 $this->subjectids[$nsInfo->getTalk( $row->page_namespace )][$row->page_title] =
738 (int)( $row->page_id );
744 $this->displaytitles = [];
746 $pageIds = array_keys( $this->titles );
748 if ( $pageIds === [] ) {
754 $this->
addFields( [
'pp_page',
'pp_value' ] );
756 $this->
addWhereFld(
'pp_propname',
'displaytitle' );
759 foreach (
$res as $row ) {
760 $this->displaytitles[$row->pp_page] = $row->pp_value;
765 if ( $this->titles === [] ) {
768 $this->variantTitles = [];
769 foreach ( $this->titles as $pageId =>
$t ) {
770 $this->variantTitles[$pageId] = isset( $this->displaytitles[$pageId] )
778 $contLang = MediaWikiServices::getInstance()->getContentLanguage();
779 foreach ( $contLang->getVariants() as $variant ) {
780 $convertTitle = $contLang->autoConvert( $text, $variant );
782 $convertNs = $contLang->convertNamespace( $ns, $variant );
783 $convertTitle = $convertNs .
':' . $convertTitle;
785 $result[$variant] = $convertTitle;
797 if ( $user->isAnon() || count( $this->everything ) == 0
804 $this->notificationtimestamps = [];
806 $store = MediaWikiServices::getInstance()->getWatchedItemStore();
807 $timestamps = $store->getNotificationTimestampsBatch( $user, $this->everything );
809 if ( $this->fld_watched ) {
810 foreach ( $timestamps as $namespaceId => $dbKeys ) {
811 $this->watched[$namespaceId] = array_map(
819 if ( $this->fld_notificationtimestamp ) {
820 $this->notificationtimestamps = $timestamps;
828 if ( count( $this->everything ) == 0 ) {
834 $unwatchedPageThreshold = $this->
getConfig()->get(
'UnwatchedPageThreshold' );
835 if ( !$canUnwatchedpages && !is_int( $unwatchedPageThreshold ) ) {
839 $this->showZeroWatchers = $canUnwatchedpages;
842 if ( !$canUnwatchedpages ) {
843 $countOptions[
'minimumWatchers'] = $unwatchedPageThreshold;
846 $this->watchers = MediaWikiServices::getInstance()->getWatchedItemStore()->countWatchersMultiple(
861 $db = $this->
getDB();
864 $unwatchedPageThreshold = $this->
getConfig()->get(
'UnwatchedPageThreshold' );
865 if ( !$canUnwatchedpages && !is_int( $unwatchedPageThreshold ) ) {
869 $this->showZeroWatchers = $canUnwatchedpages;
871 $titlesWithThresholds = [];
872 if ( $this->titles ) {
877 $this->
addTables( [
'page',
'revision' ] );
878 $this->
addFields( [
'page_namespace',
'page_title',
'rev_timestamp' ] );
880 'page_latest = rev_id',
881 $lb->constructSet(
'page', $db ),
883 $this->
addOption(
'GROUP BY', [
'page_namespace',
'page_title' ] );
884 $timestampRes = $this->
select( __METHOD__ );
886 $age = $config->get(
'WatchersMaxAge' );
888 foreach ( $timestampRes as $row ) {
889 $revTimestamp =
wfTimestamp( TS_UNIX, (
int)$row->rev_timestamp );
890 $timestamps[$row->page_namespace][$row->page_title] = (int)$revTimestamp - $age;
892 $titlesWithThresholds = array_map(
893 function (
LinkTarget $target ) use ( $timestamps ) {
902 if ( $this->missing ) {
903 $titlesWithThresholds = array_merge(
904 $titlesWithThresholds,
907 return [ $target, null ];
913 $store = MediaWikiServices::getInstance()->getWatchedItemStore();
914 $this->visitingwatchers = $store->countVisitingWatchersMultiple(
915 $titlesWithThresholds,
916 !$canUnwatchedpages ? $unwatchedPageThreshold : null
931 if ( array_diff( (array)
$params[
'prop'], $publicProps ) ) {
936 if (
$params[
'testactions'] ) {
940 if (
$params[
'token'] !==
null ) {
955 'watchers', #
private
956 'visitingwatchers', #
private
957 'notificationtimestamp', #
private
960 'readable', #
private
976 'testactionsdetail' => [
994 'action=query&prop=info&titles=Main%20Page'
995 =>
'apihelp-query+info-example-simple',
996 'action=query&prop=info&inprop=protection&titles=Main%20Page'
997 =>
'apihelp-query+info-example-protection',
1002 return 'https://www.mediawiki.org/wiki/Special:MyLanguage/API:Info';
wfExpandUrl( $url, $defaultProto=PROTO_CURRENT)
Expand a potentially local URL to a fully-qualified URL.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
dieContinueUsageIf( $condition)
Die with the 'badcontinue' error.
const PARAM_DEPRECATED_VALUES
getMain()
Get the main module.
getErrorFormatter()
Get the error formatter Stable to override.
getPermissionManager()
Obtain a PermissionManager instance that subclasses may use in their authorization checks.
errorArrayToStatus(array $errors, User $user=null)
Turn an array of message keys or key+param arrays into a Status.
const PARAM_HELP_MSG_PER_VALUE
((string|array|Message)[]) When PARAM_TYPE is an array, this is an array mapping those values to $msg...
const LIMIT_SML2
Slow query, apihighlimits limit.
getResult()
Get the result object.
extractRequestParams( $options=[])
Using getAllowedParams(), this function makes an array of the values provided by the user,...
const LIMIT_SML1
Slow query, standard limit.
const PARAM_HELP_MSG
(string|array|Message) Specify an alternative i18n documentation message for this parameter.
addWarning( $msg, $code=null, $data=null)
Add a warning for this module.
getHookRunner()
Get an ApiHookRunner for running core API hooks.
lacksSameOriginSecurity()
Returns true if the current request breaks the same-origin policy.
This is a base class for all Query modules.
setContinueEnumParameter( $paramName, $paramValue)
Set a query-continue value.
resetQueryParams()
Blank the internal arrays with query parameters.
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.
getDB()
Get the Query database connection (read-only) Stable to override.
select( $method, $extraQuery=[], array &$hookData=null)
Execute a SELECT query based on the values in the internal arrays.
addWhereFld( $field, $value)
Equivalent to addWhere( [ $field => $value ] )
getPageSet()
Get the PageSet object to work on Stable to override.
addWhere( $value)
Add a set of WHERE clauses to the internal array.
A query module to show basic page information.
$fld_notificationtimestamp
getTokenFunctions()
Get an array mapping token names to their handler functions.
getVisitingWatcherInfo()
Get the count of watchers who have visited recent edits and put it in $this->visitingwatchers.
static getUserToken(string $right)
Temporary method until the token methods are removed entirely.
getExamplesMessages()
Returns usage examples for this module.
static getUnblockToken( $pageid, $title)
static getWatchToken( $pageid, $title)
static getDeleteToken( $pageid, $title)
getAllVariants( $text, $ns=NS_MAIN)
static getEditToken( $pageid, $title)
static string[] $cachedTokens
static getMoveToken( $pageid, $title)
static getEmailToken( $pageid, $title)
static getProtectToken( $pageid, $title)
getWatchedInfo()
Get information about watched status and put it in $this->watched and $this->notificationtimestamps.
__construct(ApiQuery $query, $moduleName)
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
static getOptionsToken( $pageid, $title)
getProtectionInfo()
Get information about protections and put it in $protections.
static getBlockToken( $pageid, $title)
requestExtraData( $pageSet)
getWatcherInfo()
Get the count of watchers and put it in $this->watchers.
extractPageInfo( $pageid, $title)
Get a result array with information about a title.
getHelpUrls()
Return links to more detailed help pages about the module.
static getImportToken( $pageid, $title)
getAllowedParams()
Returns an array of allowed parameters (parameter name) => (default value) or (parameter name) => (ar...
getTSIDs()
Get talk page IDs (if requested) and subject page IDs (if requested) and put them in $talkids and $su...
getCacheMode( $params)
Get the cache mode for the data generated by this module.
This is the main query class.
getUser()
Stable to override.
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Represents a title within MediaWiki.