119 parent::__construct( $queryModule, $moduleName,
'in' );
133 $pageSet->requestField(
'page_restrictions' );
137 $pageSet->requestField(
'page_is_redirect' );
138 $pageSet->requestField(
'page_is_new' );
140 $pageSet->requestField(
'page_touched' );
141 $pageSet->requestField(
'page_latest' );
142 $pageSet->requestField(
'page_len' );
143 $pageSet->requestField(
'page_content_model' );
144 if ( $config->get(
'PageLanguageUseDB' ) ) {
145 $pageSet->requestField(
'page_lang' );
151 if ( $this->params[
'prop'] !==
null ) {
152 $prop = array_fill_keys( $this->params[
'prop'],
true );
153 $this->fld_protection = isset( $prop[
'protection'] );
154 $this->fld_watched = isset( $prop[
'watched'] );
155 $this->fld_watchers = isset( $prop[
'watchers'] );
156 $this->fld_visitingwatchers = isset( $prop[
'visitingwatchers'] );
157 $this->fld_notificationtimestamp = isset( $prop[
'notificationtimestamp'] );
158 $this->fld_talkid = isset( $prop[
'talkid'] );
159 $this->fld_subjectid = isset( $prop[
'subjectid'] );
160 $this->fld_url = isset( $prop[
'url'] );
161 $this->fld_readable = isset( $prop[
'readable'] );
162 $this->fld_preload = isset( $prop[
'preload'] );
163 $this->fld_displaytitle = isset( $prop[
'displaytitle'] );
164 $this->fld_varianttitles = isset( $prop[
'varianttitles'] );
165 $this->fld_linkclasses = isset( $prop[
'linkclasses'] );
166 $this->fld_associatedpage = isset( $prop[
'associatedpage'] );
170 $this->titles = $pageSet->getGoodTitles();
171 $this->missing = $pageSet->getMissingTitles();
175 uasort( $this->everything, [ Title::class,
'compare' ] );
176 if ( $this->params[
'continue'] !==
null ) {
179 $cont = explode(
'|', $this->params[
'continue'] );
181 $conttitle = $this->titleFactory->makeTitleSafe( $cont[0], $cont[1] );
182 foreach ( $this->everything as $pageid =>
$title ) {
183 if ( Title::compare(
$title, $conttitle ) >= 0 ) {
186 unset( $this->titles[$pageid] );
187 unset( $this->missing[$pageid] );
188 unset( $this->everything[$pageid] );
192 $this->pageRestrictions = $pageSet->getCustomField(
'page_restrictions' );
194 $this->pageIsRedir = !$pageSet->isResolvingRedirects()
195 ? $pageSet->getCustomField(
'page_is_redirect' )
197 $this->pageIsNew = $pageSet->getCustomField(
'page_is_new' );
199 $this->pageTouched = $pageSet->getCustomField(
'page_touched' );
200 $this->pageLatest = $pageSet->getCustomField(
'page_latest' );
201 $this->pageLength = $pageSet->getCustomField(
'page_len' );
204 if ( $this->fld_protection ) {
208 if ( $this->fld_watched || $this->fld_notificationtimestamp ) {
212 if ( $this->fld_watchers ) {
216 if ( $this->fld_visitingwatchers ) {
221 if ( $this->fld_talkid || $this->fld_subjectid ) {
225 if ( $this->fld_displaytitle ) {
229 if ( $this->fld_varianttitles ) {
233 if ( $this->fld_linkclasses ) {
238 foreach ( $this->everything as $pageid =>
$title ) {
240 $fit = $pageInfo !==
null && $result->addValue( [
243 ], $pageid, $pageInfo );
246 $title->getNamespace() .
'|' .
262 $titleExists = $pageid > 0;
263 $ns =
$title->getNamespace();
264 $dbkey =
$title->getDBkey();
266 $pageInfo[
'contentmodel'] =
$title->getContentModel();
268 $pageLanguage =
$title->getPageLanguage();
269 $pageInfo[
'pagelanguage'] = $pageLanguage->getCode();
270 $pageInfo[
'pagelanguagehtmlcode'] = $pageLanguage->getHtmlCode();
271 $pageInfo[
'pagelanguagedir'] = $pageLanguage->getDir();
273 if ( $titleExists ) {
274 $pageInfo[
'touched'] =
wfTimestamp( TS_ISO_8601, $this->pageTouched[$pageid] );
275 $pageInfo[
'lastrevid'] = (int)$this->pageLatest[$pageid];
276 $pageInfo[
'length'] = (int)$this->pageLength[$pageid];
278 if ( isset( $this->pageIsRedir[$pageid] ) && $this->pageIsRedir[$pageid] ) {
279 $pageInfo[
'redirect'] =
true;
281 if ( $this->pageIsNew[$pageid] ) {
282 $pageInfo[
'new'] =
true;
286 if ( $this->fld_protection ) {
287 $pageInfo[
'protection'] = [];
288 if ( isset( $this->protections[$ns][$dbkey] ) ) {
289 $pageInfo[
'protection'] =
290 $this->protections[$ns][$dbkey];
292 ApiResult::setIndexedTagName( $pageInfo[
'protection'],
'pr' );
294 $pageInfo[
'restrictiontypes'] = [];
295 if ( isset( $this->restrictionTypes[$ns][$dbkey] ) ) {
296 $pageInfo[
'restrictiontypes'] =
297 $this->restrictionTypes[$ns][$dbkey];
299 ApiResult::setIndexedTagName( $pageInfo[
'restrictiontypes'],
'rt' );
302 if ( $this->fld_watched ) {
303 $pageInfo[
'watched'] =
false;
305 if ( isset( $this->watched[$ns][$dbkey] ) ) {
306 $pageInfo[
'watched'] = $this->watched[$ns][$dbkey];
309 if ( isset( $this->watchlistExpiries[$ns][$dbkey] ) ) {
310 $pageInfo[
'watchlistexpiry'] = $this->watchlistExpiries[$ns][$dbkey];
314 if ( $this->fld_watchers ) {
315 if ( $this->watchers !==
null && $this->watchers[$ns][$dbkey] !== 0 ) {
316 $pageInfo[
'watchers'] = $this->watchers[$ns][$dbkey];
317 } elseif ( $this->showZeroWatchers ) {
318 $pageInfo[
'watchers'] = 0;
322 if ( $this->fld_visitingwatchers ) {
323 if ( $this->visitingwatchers !==
null && $this->visitingwatchers[$ns][$dbkey] !== 0 ) {
324 $pageInfo[
'visitingwatchers'] = $this->visitingwatchers[$ns][$dbkey];
325 } elseif ( $this->showZeroWatchers ) {
326 $pageInfo[
'visitingwatchers'] = 0;
330 if ( $this->fld_notificationtimestamp ) {
331 $pageInfo[
'notificationtimestamp'] =
'';
332 if ( isset( $this->notificationtimestamps[$ns][$dbkey] ) ) {
333 $pageInfo[
'notificationtimestamp'] =
334 wfTimestamp( TS_ISO_8601, $this->notificationtimestamps[$ns][$dbkey] );
338 if ( $this->fld_talkid && isset( $this->talkids[$ns][$dbkey] ) ) {
339 $pageInfo[
'talkid'] = $this->talkids[$ns][$dbkey];
342 if ( $this->fld_subjectid && isset( $this->subjectids[$ns][$dbkey] ) ) {
343 $pageInfo[
'subjectid'] = $this->subjectids[$ns][$dbkey];
346 if ( $this->fld_associatedpage && $ns >=
NS_MAIN ) {
347 $pageInfo[
'associatedpage'] = $this->titleFormatter->getPrefixedText(
348 $this->namespaceInfo->getAssociatedPage(
$title )
352 if ( $this->fld_url ) {
357 if ( $this->fld_readable ) {
361 if ( $this->fld_preload ) {
362 if ( $titleExists ) {
363 $pageInfo[
'preload'] =
'';
368 $pageInfo[
'preload'] = $text;
372 if ( $this->fld_displaytitle ) {
373 if ( isset( $this->displaytitles[$pageid] ) ) {
374 $pageInfo[
'displaytitle'] = $this->displaytitles[$pageid];
376 $pageInfo[
'displaytitle'] =
$title->getPrefixedText();
380 if ( $this->fld_varianttitles && isset( $this->variantTitles[$pageid] ) ) {
381 $pageInfo[
'varianttitles'] = $this->variantTitles[$pageid];
384 if ( $this->fld_linkclasses && isset( $this->linkClasses[$pageid] ) ) {
385 $pageInfo[
'linkclasses'] = $this->linkClasses[$pageid];
388 if ( $this->params[
'testactions'] ) {
390 if ( $this->countTestedActions >= $limit ) {
394 $detailLevel = $this->params[
'testactionsdetail'];
396 if ( $errorFormatter->getFormat() ===
'bc' ) {
398 $errorFormatter = $errorFormatter->newWithFormat(
'plaintext' );
401 $pageInfo[
'actions'] = [];
402 foreach ( $this->params[
'testactions'] as $action ) {
403 $this->countTestedActions++;
405 if ( $detailLevel ===
'boolean' ) {
406 $pageInfo[
'actions'][$action] = $this->
getAuthority()->authorizeRead( $action,
$title );
409 if ( $detailLevel ===
'quick' ) {
415 $pageInfo[
'actions'][$action] = $errorFormatter->arrayFromStatus( $status );
427 $this->protections = [];
428 $db = $this->
getDB();
431 if ( count( $this->titles ) ) {
434 $this->
addFields( [
'pr_page',
'pr_type',
'pr_level',
435 'pr_expiry',
'pr_cascade' ] );
436 $this->
addWhereFld(
'pr_page', array_keys( $this->titles ) );
439 foreach (
$res as $row ) {
441 $title = $this->titles[$row->pr_page];
443 'type' => $row->pr_type,
444 'level' => $row->pr_level,
445 'expiry' => ApiResult::formatExpiry( $row->pr_expiry )
447 if ( $row->pr_cascade ) {
448 $a[
'cascade'] =
true;
450 $this->protections[
$title->getNamespace()][
$title->getDBkey()][] = $a;
453 foreach ( $this->titles as $pageId =>
$title ) {
454 if ( $this->pageRestrictions[$pageId] ) {
455 $namespace =
$title->getNamespace();
456 $dbKey =
$title->getDBkey();
457 $restrictions = explode(
':', trim( $this->pageRestrictions[$pageId] ) );
458 foreach ( $restrictions as $restrict ) {
459 $temp = explode(
'=', trim( $restrict ) );
460 if ( count( $temp ) == 1 ) {
462 $restriction = trim( $temp[0] );
464 if ( $restriction ==
'' ) {
467 $this->protections[$namespace][$dbKey][] = [
469 'level' => $restriction,
470 'expiry' =>
'infinity',
472 $this->protections[$namespace][$dbKey][] = [
474 'level' => $restriction,
475 'expiry' =>
'infinity',
478 $restriction = trim( $temp[1] );
479 if ( $restriction ==
'' ) {
482 $this->protections[$namespace][$dbKey][] = [
484 'level' => $restriction,
485 'expiry' =>
'infinity',
494 if ( count( $this->missing ) ) {
496 $lb = $this->linkBatchFactory->newLinkBatch( $this->missing );
498 $this->
addFields( [
'pt_title',
'pt_namespace',
'pt_create_perm',
'pt_expiry' ] );
499 $this->
addWhere( $lb->constructSet(
'pt', $db ) );
501 foreach (
$res as $row ) {
502 $this->protections[$row->pt_namespace][$row->pt_title][] = [
504 'level' => $row->pt_create_perm,
505 'expiry' => ApiResult::formatExpiry( $row->pt_expiry )
512 $images = $others = [];
513 foreach ( $this->everything as
$title ) {
515 $images[] =
$title->getDBkey();
520 $this->restrictionTypes[
$title->getNamespace()][
$title->getDBkey()] =
521 array_values(
$title->getRestrictionTypes() );
524 if ( count( $others ) ) {
526 $lb = $this->linkBatchFactory->newLinkBatch( $others );
528 $this->
addTables( [
'page_restrictions',
'page',
'templatelinks' ] );
529 $this->
addFields( [
'pr_type',
'pr_level',
'pr_expiry',
530 'page_title',
'page_namespace',
531 'tl_title',
'tl_namespace' ] );
532 $this->
addWhere( $lb->constructSet(
'tl', $db ) );
533 $this->
addWhere(
'pr_page = page_id' );
534 $this->
addWhere(
'pr_page = tl_from' );
538 foreach (
$res as $row ) {
539 $source = $this->titleFactory->makeTitle( $row->page_namespace, $row->page_title );
540 $this->protections[$row->tl_namespace][$row->tl_title][] = [
541 'type' => $row->pr_type,
542 'level' => $row->pr_level,
543 'expiry' => ApiResult::formatExpiry( $row->pr_expiry ),
544 'source' =>
$source->getPrefixedText()
549 if ( count( $images ) ) {
552 $this->
addTables( [
'page_restrictions',
'page',
'imagelinks' ] );
553 $this->
addFields( [
'pr_type',
'pr_level',
'pr_expiry',
554 'page_title',
'page_namespace',
'il_to' ] );
555 $this->
addWhere(
'pr_page = page_id' );
556 $this->
addWhere(
'pr_page = il_from' );
561 foreach (
$res as $row ) {
562 $source = $this->titleFactory->makeTitle( $row->page_namespace, $row->page_title );
563 $this->protections[
NS_FILE][$row->il_to][] = [
564 'type' => $row->pr_type,
565 'level' => $row->pr_level,
566 'expiry' => ApiResult::formatExpiry( $row->pr_expiry ),
567 'source' =>
$source->getPrefixedText()
578 $getTitles = $this->talkids = $this->subjectids = [];
582 foreach ( $this->everything as
$t ) {
583 if ( $nsInfo->isTalk(
$t->getNamespace() ) ) {
584 if ( $this->fld_subjectid ) {
587 } elseif ( $this->fld_talkid ) {
588 $getTitles[] =
$t->getTalkPage();
591 if ( $getTitles === [] ) {
595 $db = $this->
getDB();
599 $lb = $this->linkBatchFactory->newLinkBatch( $getTitles );
602 $this->
addFields( [
'page_title',
'page_namespace',
'page_id' ] );
603 $this->
addWhere( $lb->constructSet(
'page', $db ) );
605 foreach (
$res as $row ) {
606 if ( $nsInfo->isTalk( $row->page_namespace ) ) {
607 $this->talkids[$nsInfo->getSubject( $row->page_namespace )][$row->page_title] =
608 (int)( $row->page_id );
610 $this->subjectids[$nsInfo->getTalk( $row->page_namespace )][$row->page_title] =
611 (int)( $row->page_id );
617 $this->displaytitles = [];
619 $pageIds = array_keys( $this->titles );
621 if ( $pageIds === [] ) {
627 $this->
addFields( [
'pp_page',
'pp_value' ] );
629 $this->
addWhereFld(
'pp_propname',
'displaytitle' );
632 foreach (
$res as $row ) {
633 $this->displaytitles[$row->pp_page] = $row->pp_value;
645 if ( $this->titles === [] ) {
656 foreach ( $this->titles as $pageId =>
$title ) {
657 $pdbk =
$title->getPrefixedDBkey();
658 $pagemap[$pageId] = $pdbk;
659 $classes[$pdbk] =
$title->isRedirect() ?
'mw-redirect' :
'';
662 $context_title = $this->titleFactory->newFromLinkTarget(
663 $context_title ?? $this->titleFactory->newMainPage()
666 $pagemap, $classes, $context_title
673 $this->linkClasses = [];
674 foreach ( $this->titles as $pageId =>
$title ) {
675 $pdbk =
$title->getPrefixedDBkey();
676 $this->linkClasses[$pageId] = preg_split(
677 '/\s+/', $classes[$pdbk] ??
'', -1, PREG_SPLIT_NO_EMPTY
683 if ( $this->titles === [] ) {
686 $this->variantTitles = [];
687 foreach ( $this->titles as $pageId =>
$t ) {
688 $this->variantTitles[$pageId] = isset( $this->displaytitles[$pageId] )
696 foreach ( $this->languageConverter->getVariants() as $variant ) {
697 $convertTitle = $this->languageConverter->autoConvert( $text, $variant );
699 $convertNs = $this->languageConverter->convertNamespace( $ns, $variant );
700 $convertTitle = $convertNs .
':' . $convertTitle;
702 $result[$variant] = $convertTitle;
714 if ( !$user->isRegistered() || count( $this->everything ) == 0
715 || !$this->
getAuthority()->isAllowed(
'viewmywatchlist' )
721 $this->watchlistExpiries = [];
722 $this->notificationtimestamps = [];
725 $items = $this->watchedItemStore->loadWatchedItemsBatch( $user, $this->everything );
727 foreach ( $items as $item ) {
728 $nsId = $item->getTarget()->getNamespace();
729 $dbKey = $item->getTarget()->getDBkey();
731 if ( $this->fld_watched ) {
732 $this->watched[$nsId][$dbKey] =
true;
734 $expiry = $item->getExpiry( TS_ISO_8601 );
736 $this->watchlistExpiries[$nsId][$dbKey] = $expiry;
740 if ( $this->fld_notificationtimestamp ) {
741 $this->notificationtimestamps[$nsId][$dbKey] = $item->getNotificationTimestamp();
750 if ( count( $this->everything ) == 0 ) {
754 $canUnwatchedpages = $this->
getAuthority()->isAllowed(
'unwatchedpages' );
755 $unwatchedPageThreshold = $this->
getConfig()->get(
'UnwatchedPageThreshold' );
756 if ( !$canUnwatchedpages && !is_int( $unwatchedPageThreshold ) ) {
760 $this->showZeroWatchers = $canUnwatchedpages;
763 if ( !$canUnwatchedpages ) {
764 $countOptions[
'minimumWatchers'] = $unwatchedPageThreshold;
767 $this->watchers = $this->watchedItemStore->countWatchersMultiple(
781 $db = $this->
getDB();
783 $canUnwatchedpages = $this->
getAuthority()->isAllowed(
'unwatchedpages' );
784 $unwatchedPageThreshold = $config->get(
'UnwatchedPageThreshold' );
785 if ( !$canUnwatchedpages && !is_int( $unwatchedPageThreshold ) ) {
789 $this->showZeroWatchers = $canUnwatchedpages;
791 $titlesWithThresholds = [];
792 if ( $this->titles ) {
793 $lb = $this->linkBatchFactory->newLinkBatch( $this->titles );
797 $this->
addTables( [
'page',
'revision' ] );
798 $this->
addFields( [
'page_namespace',
'page_title',
'rev_timestamp' ] );
800 'page_latest = rev_id',
801 $lb->constructSet(
'page', $db ),
803 $this->
addOption(
'GROUP BY', [
'page_namespace',
'page_title' ] );
804 $timestampRes = $this->
select( __METHOD__ );
806 $age = $config->get(
'WatchersMaxAge' );
808 foreach ( $timestampRes as $row ) {
809 $revTimestamp =
wfTimestamp( TS_UNIX, (
int)$row->rev_timestamp );
810 $timestamps[$row->page_namespace][$row->page_title] = (int)$revTimestamp - $age;
812 $titlesWithThresholds = array_map(
813 static function (
LinkTarget $target ) use ( $timestamps ) {
822 if ( $this->missing ) {
823 $titlesWithThresholds = array_merge(
824 $titlesWithThresholds,
827 return [ $target, null ];
833 $this->visitingwatchers = $this->watchedItemStore->countVisitingWatchersMultiple(
834 $titlesWithThresholds,
835 !$canUnwatchedpages ? $unwatchedPageThreshold : null
851 if ( array_diff( (array)
$params[
'prop'], $publicProps ) ) {
856 if (
$params[
'testactions'] ) {
871 'watchers', #
private
872 'visitingwatchers', #
private
873 'notificationtimestamp', #
private
877 'readable', #
private
881 'linkclasses', #
private: stub length (and possibly hook colors)
893 TitleDef::PARAM_RETURN_OBJECT =>
true,
899 'testactionsdetail' => [
912 'action=query&prop=info&titles=Main%20Page'
913 =>
'apihelp-query+info-example-simple',
914 'action=query&prop=info&inprop=protection&titles=Main%20Page'
915 =>
'apihelp-query+info-example-protection',
920 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.
addBlockInfoToStatus(StatusValue $status, Authority $user=null)
Add block info to block messages in a Status.
dieContinueUsageIf( $condition)
Die with the 'badcontinue' error.
const PARAM_DEPRECATED_VALUES
getMain()
Get the main module.
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.
getHookRunner()
Get an ApiHookRunner for running core API hooks.
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)
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.
addWhere( $value)
Add a set of WHERE clauses to the internal array.
A query module to show basic page information.
$fld_notificationtimestamp
array< int, array< string, string > > $watchlistExpiries
Watchlist expiries that corresponds with the $watched property.
getVisitingWatcherInfo()
Get the count of watchers who have visited recent edits and put it in $this->visitingwatchers.
bool $fld_associatedpage
Whether to include the name of the associated page.
getExamplesMessages()
Returns usage examples for this module.
getLinkClasses(?LinkTarget $context_title=null)
Fetch the set of extra link classes associated with links to the set of titles ("link colours"),...
__construct(ApiQuery $queryModule, $moduleName, Language $contentLanguage, LinkBatchFactory $linkBatchFactory, NamespaceInfo $namespaceInfo, TitleFactory $titleFactory, TitleFormatter $titleFormatter, WatchedItemStore $watchedItemStore, LanguageConverterFactory $languageConverterFactory)
getAllVariants( $text, $ns=NS_MAIN)
LinkBatchFactory $linkBatchFactory
WatchedItemStore $watchedItemStore
ILanguageConverter $languageConverter
TitleFormatter $titleFormatter
array< int, string[]> $linkClasses
Mapping of page id to list of 'extra link classes' for the given page.
getWatchedInfo()
Get information about watched status and put it in $this->watched and $this->notificationtimestamps.
TitleFactory $titleFactory
execute()
Evaluates the parameters, performs the requested query, and sets up the result.
getProtectionInfo()
Get information about protections and put it in $protections.
bool $fld_linkclasses
Whether to include link class information for the given page titles.
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.
NamespaceInfo $namespaceInfo
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.
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
This is a utility class for dealing with namespaces that encodes all the "magic" behaviors of them ba...
getSubjectPage(LinkTarget $target)
Represents a title within MediaWiki.
Storage layer class for WatchedItems.
The shared interface for all language converters.