75 private $loadBalancer =
null;
78 private $linkBatchFactory =
null;
95 [ SpecialAncientPages::class,
'Ancientpages' ],
96 [ SpecialBrokenRedirects::class,
'BrokenRedirects' ],
97 [ SpecialDeadendPages::class,
'Deadendpages' ],
98 [ SpecialDoubleRedirects::class,
'DoubleRedirects' ],
99 [ SpecialListDuplicatedFiles::class,
'ListDuplicatedFiles' ],
100 [ SpecialLinkSearch::class,
'LinkSearch' ],
101 [ SpecialListRedirects::class,
'Listredirects' ],
102 [ SpecialLonelyPages::class,
'Lonelypages' ],
103 [ SpecialLongPages::class,
'Longpages' ],
105 [ SpecialMIMESearch::class,
'MIMEsearch' ],
106 [ SpecialMostCategories::class,
'Mostcategories' ],
107 [ SpecialMostImages::class,
'Mostimages' ],
108 [ SpecialMostInterwikis::class,
'Mostinterwikis' ],
109 [ SpecialMostLinkedCategories::class,
'Mostlinkedcategories' ],
110 [ SpecialMostLinkedTemplates::class,
'Mostlinkedtemplates' ],
111 [ SpecialMostLinked::class,
'Mostlinked' ],
112 [ SpecialMostRevisions::class,
'Mostrevisions' ],
113 [ SpecialFewestRevisions::class,
'Fewestrevisions' ],
114 [ SpecialShortPages::class,
'Shortpages' ],
115 [ SpecialUncategorizedCategories::class,
'Uncategorizedcategories' ],
116 [ SpecialUncategorizedPages::class,
'Uncategorizedpages' ],
117 [ SpecialUncategorizedImages::class,
'Uncategorizedimages' ],
118 [ SpecialUncategorizedTemplates::class,
'Uncategorizedtemplates' ],
119 [ SpecialUnusedCategories::class,
'Unusedcategories' ],
120 [ SpecialUnusedImages::class,
'Unusedimages' ],
121 [ SpecialWantedCategories::class,
'Wantedcategories' ],
122 [ SpecialWantedFiles::class,
'Wantedfiles' ],
123 [ SpecialWantedPages::class,
'Wantedpages' ],
124 [ SpecialWantedTemplates::class,
'Wantedtemplates' ],
125 [ SpecialUnwatchedPages::class,
'Unwatchedpages' ],
126 [ SpecialUnusedTemplates::class,
'Unusedtemplates' ],
127 [ SpecialWithoutInterwiki::class,
'Withoutinterwiki' ],
129 Hooks::runner()->onWgQueryPages( $qp );
140 $this->linkBatchFactory = $linkBatchFactory;
148 if ( $this->linkBatchFactory === null ) {
151 $this->linkBatchFactory = MediaWikiServices::getInstance()->getLinkBatchFactory();
153 return $this->linkBatchFactory;
162 $disableQueryPageUpdate = $config->
get( MainConfigNames::DisableQueryPageUpdate );
164 if ( !is_array( $disableQueryPageUpdate ) ) {
169 foreach ( $disableQueryPageUpdate as $name => $runMode ) {
170 if ( is_int( $name ) ) {
172 $pages[$runMode] =
'disabled';
174 $pages[$name] = $runMode;
186 $this->listoutput = $bool;
230 throw new MWException(
"Bug in a QueryPage: doesn't implement getQueryInfo() nor "
231 .
"getQuery() properly" );
292 return $this->getConfig()->get( MainConfigNames::DisableQueryPages );
315 return $this->isExpensive() && $this->getConfig()->get( MainConfigNames::MiserMode );
357 $this->getOutput()->addWikiMsg(
'specialpage-empty' );
382 public function recache( $limit, $ignoreErrors =
true ) {
383 if ( !$this->isCacheable() ) {
387 $fname = static::class .
'::recache';
388 $dbw = $this->getDBLoadBalancer()->getConnectionRef( ILoadBalancer::DB_PRIMARY );
392 $res = $this->reallyDoQuery( $limit,
false );
395 $num =
$res->numRows();
398 foreach (
$res as $i => $row ) {
399 if ( isset( $row->value ) ) {
400 if ( $this->usesTimestamps() ) {
403 $value = intval( $row->value );
410 'qc_type' => $this->getName(),
411 'qc_namespace' => $row->namespace,
412 'qc_title' => $row->title,
417 $dbw->doAtomicSection(
419 function (
IDatabase $dbw, $fname ) use ( $vals ) {
421 $dbw->
delete(
'querycache',
422 [
'qc_type' => $this->getName() ],
426 if ( count( $vals ) ) {
427 $dbw->
insert(
'querycache', $vals, $fname );
433 'qci_type' => $this->getName(),
446 if ( !$ignoreErrors ) {
461 return $this->getDBLoadBalancer()
462 ->getConnectionRef( ILoadBalancer::DB_REPLICA,
'vslow' );
472 if ( $this->isCached() ) {
473 $dbw = $this->getDBLoadBalancer()->getConnectionRef( ILoadBalancer::DB_PRIMARY );
474 $dbw->delete(
'querycache', [
475 'qc_type' => $this->getName(),
476 'qc_namespace' =>
$title->getNamespace(),
477 'qc_title' =>
$title->getDBkey(),
488 $fname = static::class .
'::' . __FUNCTION__;
489 $dbw = $this->getDBLoadBalancer()->getConnectionRef( ILoadBalancer::DB_PRIMARY );
490 $dbw->delete(
'querycache',
491 [
'qc_type' => $this->getName() ],
494 $dbw->delete(
'querycachetwo',
495 [
'qcc_type' => $this->getName() ],
498 $dbw->delete(
'querycache_info',
499 [
'qci_type' => $this->getName() ],
513 $fname = static::class .
'::reallyDoQuery';
514 $dbr = $this->getRecacheDB();
515 $query = $this->getQueryInfo();
516 $order = $this->getOrderFields();
518 if ( $this->sortDescending() ) {
519 foreach ( $order as &$field ) {
524 if ( is_array( $query ) ) {
525 $tables = isset( $query[
'tables'] ) ? (array)$query[
'tables'] : [];
526 $fields = isset( $query[
'fields'] ) ? (array)$query[
'fields'] : [];
527 $conds = isset( $query[
'conds'] ) ? (array)$query[
'conds'] : [];
528 $options = isset( $query[
'options'] ) ? (array)$query[
'options'] : [];
529 $join_conds = isset( $query[
'join_conds'] ) ? (array)$query[
'join_conds'] : [];
532 $options[
'ORDER BY'] = $order;
535 if ( $limit !==
false ) {
536 $options[
'LIMIT'] = intval( $limit );
539 if ( $offset !==
false ) {
540 $options[
'OFFSET'] = intval( $offset );
543 $res =
$dbr->select( $tables, $fields, $conds, $fname,
544 $options, $join_conds
548 MWDebug::detectDeprecatedOverride(
554 $sql = $this->getSQL();
555 $sql .=
' ORDER BY ' . implode(
', ', $order );
556 $sql =
$dbr->limitResult( $sql, $limit, $offset );
569 public function doQuery( $offset =
false, $limit =
false ) {
570 if ( $this->isCached() && $this->isCacheable() ) {
571 return $this->fetchFromCache( $limit, $offset );
573 return $this->reallyDoQuery( $limit, $offset );
587 $dbr = $this->getDBLoadBalancer()->getConnectionRef( ILoadBalancer::DB_REPLICA );
590 if ( $limit !==
false ) {
591 $options[
'LIMIT'] = intval( $limit );
594 if ( $offset !==
false ) {
595 $options[
'OFFSET'] = intval( $offset );
598 $order = $this->getCacheOrderFields();
599 if ( $this->sortDescending() ) {
600 foreach ( $order as &$field ) {
605 $options[
'ORDER BY'] = $order;
608 return $dbr->select(
'querycache',
610 'namespace' =>
'qc_namespace',
611 'title' =>
'qc_title',
612 'value' =>
'qc_value' ],
613 [
'qc_type' => $this->getName() ],
634 if ( $this->cachedTimestamp ===
null ) {
635 $dbr = $this->getDBLoadBalancer()->getConnectionRef( ILoadBalancer::DB_REPLICA );
636 $fname = static::class .
'::getCachedTimestamp';
637 $this->cachedTimestamp =
$dbr->selectField(
'querycache_info',
'qci_timestamp',
638 [
'qci_type' => $this->getName() ], $fname );
640 return $this->cachedTimestamp;
656 [ $limit, $offset ] = $this->getRequest()
657 ->getLimitOffsetForUser( $this->
getUser() );
658 if ( $this->getConfig()->
get( MainConfigNames::MiserMode ) ) {
659 $maxResults = $this->getMaxResults();
661 $limit = min( $limit, $maxResults );
663 $offset = min( $offset, $maxResults + 1 );
665 return [ $limit, $offset ];
677 $maxResults = $this->getMaxResults();
678 if ( $this->getConfig()->
get( MainConfigNames::MiserMode ) ) {
679 $limit = min( $uiLimit + 1, $maxResults - $uiOffset );
680 return max( $limit, 0 );
698 return max( $this->getConfig()->
get( MainConfigNames::QueryCacheLimit ), 10000 );
708 $this->checkPermissions();
711 $this->outputHeader();
713 $out = $this->getOutput();
715 if ( $this->isCached() && !$this->isCacheable() ) {
716 $out->addWikiMsg(
'querypage-disabled' );
720 $out->setSyndicated( $this->isSyndicated() );
722 if ( $this->limit == 0 && $this->offset == 0 ) {
723 [ $this->limit, $this->offset ] = $this->getLimitOffset();
725 $dbLimit = $this->getDBLimit( $this->limit, $this->offset );
727 if ( !$this->isCached() ) {
729 $res = $this->reallyDoQuery( $dbLimit, $this->offset );
732 $res = $this->fetchFromCache( $dbLimit, $this->offset );
733 if ( !$this->listoutput ) {
735 $ts = $this->getCachedTimestamp();
736 $lang = $this->getLanguage();
737 $maxResults =
$lang->formatNum( $this->getConfig()->
get(
738 MainConfigNames::QueryCacheLimit ) );
742 $updated =
$lang->userTimeAndDate( $ts, $user );
743 $updateddate =
$lang->userDate( $ts, $user );
744 $updatedtime =
$lang->userTime( $ts, $user );
745 $out->addMeta(
'Data-Cache-Time', $ts );
746 $out->addJsConfigVars(
'dataCacheTime', $ts );
747 $out->addWikiMsg(
'perfcachedts', $updated, $updateddate, $updatedtime, $maxResults );
749 $out->addWikiMsg(
'perfcached', $maxResults );
754 $disabledQueryPages = self::getDisabledQueryPages( $this->getConfig() );
755 if ( isset( $disabledQueryPages[$this->getName()] ) ) {
756 $runMode = $disabledQueryPages[$this->getName()];
757 if ( $runMode ===
'disabled' ) {
759 "<div class=\"mw-querypage-no-updates\">\n$1\n</div>",
760 'querypage-no-updates'
765 "<div class=\"mw-querypage-updates-" . $runMode .
"\">\n$1\n</div>",
766 'querypage-updates-' . $runMode
773 $this->numRows =
$res->numRows();
775 $dbr = $this->getRecacheDB();
776 $this->preprocessResults(
$dbr,
$res );
778 $out->addHTML( Xml::openElement(
'div', [
'class' =>
'mw-spcontent' ] ) );
781 if ( $this->shownavigation ) {
782 $out->addHTML( $this->getPageHeader() );
783 if ( $this->numRows > 0 ) {
784 $out->addHTML( $this->msg(
'showingresultsinrange' )->numParams(
785 min( $this->numRows, $this->limit ),
786 $this->offset + 1, ( min( $this->numRows, $this->limit ) + $this->offset ) )->parseAsBlock() );
788 $miserMaxResults = $this->getConfig()->get( MainConfigNames::MiserMode )
789 && ( $this->offset + $this->limit >= $this->getMaxResults() );
790 $atEnd = ( $this->numRows <= $this->limit ) || $miserMaxResults;
791 $paging = $this->buildPrevNextNavigation( $this->offset,
792 $this->limit, $this->linkParameters(), $atEnd, $par );
793 $out->addHTML(
'<p>' . $paging .
'</p>' );
797 $this->showEmptyText();
798 $out->addHTML( Xml::closeElement(
'div' ) );
806 $this->outputResults( $out,
810 min( $this->numRows, $this->limit ),
814 if ( $this->shownavigation ) {
816 $out->addHTML(
'<p>' . $paging .
'</p>' );
819 $out->addHTML( Xml::closeElement(
'div' ) );
838 if ( !$this->listoutput ) {
839 $html[] = $this->openList( $offset );
844 for ( $i = 0; $i < $num && $row =
$res->fetchObject(); $i++ ) {
845 $line = $this->formatResult( $skin, $row );
847 $html[] = $this->listoutput
849 :
"<li>{$line}</li>\n";
853 if ( !$this->listoutput ) {
854 $html[] = $this->closeList();
857 $html = $this->listoutput
858 ? $this->getContentLanguage()->listToText( $html )
859 : implode(
'', $html );
861 $out->addHTML( $html );
870 return "\n<ol start='" . ( $offset + 1 ) .
"' class='special'>\n";
902 if ( !
$res->numRows() ) {
906 $batch = $this->getLinkBatchFactory()->newLinkBatch();
907 foreach (
$res as $row ) {
908 $batch->add( $ns ?? (
int)$row->namespace, $row->title );
920 $this->loadBalancer = $loadBalancer;
928 if ( $this->loadBalancer === null ) {
931 $this->loadBalancer = MediaWikiServices::getInstance()->getDBLoadBalancer();
933 return $this->loadBalancer;
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
if(!defined('MW_SETUP_CALLBACK'))
The persistent session ID (if any) loaded at startup.
A class containing constants representing the names of configuration variables.
A special page that lists most linked pages that does not exist.
This is a class for doing query pages; since they're almost all the same, we factor out some of the f...
isExpensive()
Should this query page only be updated offline on large wikis?
linkParameters()
If using extra form wheely-dealies, return a set of parameters here as an associative array.
getMaxResults()
Get max number of results we can return in miser mode.
doQuery( $offset=false, $limit=false)
Somewhat deprecated, you probably want to be using execute()
executeLBFromResultWrapper(IResultWrapper $res, $ns=null)
Creates a new LinkBatch object, adds all pages from the passed result wrapper (MUST include title and...
setListoutput( $bool)
A mutator for $this->listoutput;.
static getDisabledQueryPages(Config $config)
Get a list of disabled query pages and their run mode.
recache( $limit, $ignoreErrors=true)
Clear the cache and save new results.
setDBLoadBalancer(ILoadBalancer $loadBalancer)
fetchFromCache( $limit, $offset=false)
Fetch the query results from the query cache.
int $offset
The offset and limit in use, as passed to the query() function.
outputResults( $out, $skin, $dbr, $res, $num, $offset)
Format and output report results using the given information plus OutputPage.
isCached()
Whether or not the output of the page in question is retrieved from the database cache.
sortDescending()
Override to sort by increasing values.
deleteAllCachedData()
Remove all cached value This is needed when the page is no longer using the cache.
formatResult( $skin, $result)
Formats the results of the query for display.
isSyndicated()
Sometimes we don't want to build rss / atom feeds.
static getPages()
Get a list of query page classes and their associated special pages, for periodic updates.
bool $shownavigation
Whether to show prev/next links.
reallyDoQuery( $limit, $offset=false)
Run the query and return the result.
isCacheable()
Is the output of this query cacheable? Non-cacheable expensive pages will be disabled in miser mode a...
getOrderFields()
Subclasses return an array of fields to order by here.
showEmptyText()
Outputs some kind of an informative message (via OutputPage) to let the user know that the query retu...
usesTimestamps()
Does this query return timestamps rather than integers in its 'value' field? If true,...
getCacheOrderFields()
Return the order fields for fetchFromCache.
getQueryInfo()
Subclasses return an SQL query here, formatted as an array with the following keys: tables => Table(s...
getDBLimit( $uiLimit, $uiOffset)
What is limit to fetch from DB.
int $numRows
The number of rows returned by the query.
preprocessResults( $db, $res)
Do any necessary preprocessing of the result object.
setLinkBatchFactory(LinkBatchFactory $linkBatchFactory)
getSQL()
For back-compat, subclasses may return a raw SQL query here, as a string.
getPageHeader()
The content returned by this function will be output before any result.
bool $listoutput
Whether or not we want plain listoutput rather than an ordered list.
execute( $par)
This is the actual workhorse.
string null false $cachedTimestamp
getLimitOffset()
Returns limit and offset, as returned by $this->getRequest()->getLimitOffsetForUser().
getRecacheDB()
Get a DB connection to be used for slow recache queries.
Parent class for all special pages.
Interface for configuration instances.
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
if(!isset( $args[0])) $lang