72 private $loadBalancer =
null;
75 private $linkBatchFactory =
null;
92 [ SpecialAncientPages::class,
'Ancientpages' ],
93 [ SpecialBrokenRedirects::class,
'BrokenRedirects' ],
94 [ SpecialDeadendPages::class,
'Deadendpages' ],
95 [ SpecialDoubleRedirects::class,
'DoubleRedirects' ],
96 [ SpecialListDuplicatedFiles::class,
'ListDuplicatedFiles' ],
97 [ SpecialLinkSearch::class,
'LinkSearch' ],
98 [ SpecialListRedirects::class,
'Listredirects' ],
99 [ SpecialLonelyPages::class,
'Lonelypages' ],
100 [ SpecialLongPages::class,
'Longpages' ],
102 [ SpecialMIMESearch::class,
'MIMEsearch' ],
103 [ SpecialMostCategories::class,
'Mostcategories' ],
104 [ MostimagesPage::class,
'Mostimages' ],
105 [ SpecialMostInterwikis::class,
'Mostinterwikis' ],
106 [ SpecialMostLinkedCategories::class,
'Mostlinkedcategories' ],
107 [ SpecialMostLinkedTemplates::class,
'Mostlinkedtemplates' ],
108 [ SpecialMostLinked::class,
'Mostlinked' ],
109 [ SpecialMostRevisions::class,
'Mostrevisions' ],
110 [ SpecialFewestRevisions::class,
'Fewestrevisions' ],
111 [ SpecialShortPages::class,
'Shortpages' ],
112 [ SpecialUncategorizedCategories::class,
'Uncategorizedcategories' ],
113 [ SpecialUncategorizedPages::class,
'Uncategorizedpages' ],
114 [ SpecialUncategorizedImages::class,
'Uncategorizedimages' ],
115 [ SpecialUncategorizedTemplates::class,
'Uncategorizedtemplates' ],
116 [ SpecialUnusedCategories::class,
'Unusedcategories' ],
117 [ SpecialUnusedImages::class,
'Unusedimages' ],
118 [ SpecialWantedCategories::class,
'Wantedcategories' ],
119 [ WantedFilesPage::class,
'Wantedfiles' ],
120 [ WantedPagesPage::class,
'Wantedpages' ],
121 [ SpecialWantedTemplates::class,
'Wantedtemplates' ],
122 [ SpecialUnwatchedPages::class,
'Unwatchedpages' ],
123 [ SpecialUnusedTemplates::class,
'Unusedtemplates' ],
124 [ SpecialWithoutInterwiki::class,
'Withoutinterwiki' ],
126 Hooks::runner()->onWgQueryPages( $qp );
137 $this->linkBatchFactory = $linkBatchFactory;
145 if ( $this->linkBatchFactory === null ) {
148 $this->linkBatchFactory = MediaWikiServices::getInstance()->getLinkBatchFactory();
150 return $this->linkBatchFactory;
159 $disableQueryPageUpdate = $config->
get( MainConfigNames::DisableQueryPageUpdate );
161 if ( !is_array( $disableQueryPageUpdate ) ) {
166 foreach ( $disableQueryPageUpdate as $name => $runMode ) {
167 if ( is_int( $name ) ) {
169 $pages[$runMode] =
'disabled';
171 $pages[$name] = $runMode;
183 $this->listoutput = $bool;
227 throw new MWException(
"Bug in a QueryPage: doesn't implement getQueryInfo() nor "
228 .
"getQuery() properly" );
289 return $this->getConfig()->get( MainConfigNames::DisableQueryPages );
312 return $this->isExpensive() && $this->getConfig()->get( MainConfigNames::MiserMode );
354 $this->getOutput()->addWikiMsg(
'specialpage-empty' );
379 public function recache( $limit, $ignoreErrors =
true ) {
380 if ( !$this->isCacheable() ) {
384 $fname = static::class .
'::recache';
385 $dbw = $this->getDBLoadBalancer()->getConnectionRef( ILoadBalancer::DB_PRIMARY );
389 $res = $this->reallyDoQuery( $limit,
false );
392 $num =
$res->numRows();
395 foreach (
$res as $i => $row ) {
396 if ( isset( $row->value ) ) {
397 if ( $this->usesTimestamps() ) {
400 $value = intval( $row->value );
407 'qc_type' => $this->getName(),
408 'qc_namespace' => $row->namespace,
409 'qc_title' => $row->title,
414 $dbw->doAtomicSection(
416 function (
IDatabase $dbw, $fname ) use ( $vals ) {
418 $dbw->
delete(
'querycache',
419 [
'qc_type' => $this->getName() ],
423 if ( count( $vals ) ) {
424 $dbw->
insert(
'querycache', $vals, $fname );
430 'qci_type' => $this->getName(),
443 if ( !$ignoreErrors ) {
458 return $this->getDBLoadBalancer()
459 ->getConnectionRef( ILoadBalancer::DB_REPLICA, [ $this->getName(),
'QueryPage::recache',
'vslow' ] );
469 if ( $this->isCached() ) {
470 $dbw = $this->getDBLoadBalancer()->getConnectionRef( ILoadBalancer::DB_PRIMARY );
471 $dbw->delete(
'querycache', [
472 'qc_type' => $this->getName(),
473 'qc_namespace' =>
$title->getNamespace(),
474 'qc_title' =>
$title->getDBkey(),
485 $fname = static::class .
'::' . __FUNCTION__;
486 $dbw = $this->getDBLoadBalancer()->getConnectionRef( ILoadBalancer::DB_PRIMARY );
487 $dbw->delete(
'querycache',
488 [
'qc_type' => $this->getName() ],
491 $dbw->delete(
'querycachetwo',
492 [
'qcc_type' => $this->getName() ],
495 $dbw->delete(
'querycache_info',
496 [
'qci_type' => $this->getName() ],
510 $fname = static::class .
'::reallyDoQuery';
511 $dbr = $this->getRecacheDB();
512 $query = $this->getQueryInfo();
513 $order = $this->getOrderFields();
515 if ( $this->sortDescending() ) {
516 foreach ( $order as &$field ) {
521 if ( is_array( $query ) ) {
522 $tables = isset( $query[
'tables'] ) ? (array)$query[
'tables'] : [];
523 $fields = isset( $query[
'fields'] ) ? (array)$query[
'fields'] : [];
524 $conds = isset( $query[
'conds'] ) ? (array)$query[
'conds'] : [];
525 $options = isset( $query[
'options'] ) ? (array)$query[
'options'] : [];
526 $join_conds = isset( $query[
'join_conds'] ) ? (array)$query[
'join_conds'] : [];
529 $options[
'ORDER BY'] = $order;
532 if ( $limit !==
false ) {
533 $options[
'LIMIT'] = intval( $limit );
536 if ( $offset !==
false ) {
537 $options[
'OFFSET'] = intval( $offset );
540 $res =
$dbr->select( $tables, $fields, $conds, $fname,
541 $options, $join_conds
545 MWDebug::detectDeprecatedOverride(
551 $sql = $this->getSQL();
552 $sql .=
' ORDER BY ' . implode(
', ', $order );
553 $sql =
$dbr->limitResult( $sql, $limit, $offset );
566 public function doQuery( $offset =
false, $limit =
false ) {
567 if ( $this->isCached() && $this->isCacheable() ) {
568 return $this->fetchFromCache( $limit, $offset );
570 return $this->reallyDoQuery( $limit, $offset );
584 $dbr = $this->getDBLoadBalancer()->getConnectionRef( ILoadBalancer::DB_REPLICA );
587 if ( $limit !==
false ) {
588 $options[
'LIMIT'] = intval( $limit );
591 if ( $offset !==
false ) {
592 $options[
'OFFSET'] = intval( $offset );
595 $order = $this->getCacheOrderFields();
596 if ( $this->sortDescending() ) {
597 foreach ( $order as &$field ) {
602 $options[
'ORDER BY'] = $order;
605 return $dbr->select(
'querycache',
607 'namespace' =>
'qc_namespace',
608 'title' =>
'qc_title',
609 'value' =>
'qc_value' ],
610 [
'qc_type' => $this->getName() ],
631 if ( $this->cachedTimestamp ===
null ) {
632 $dbr = $this->getDBLoadBalancer()->getConnectionRef( ILoadBalancer::DB_REPLICA );
633 $fname = static::class .
'::getCachedTimestamp';
634 $this->cachedTimestamp =
$dbr->selectField(
'querycache_info',
'qci_timestamp',
635 [
'qci_type' => $this->getName() ], $fname );
637 return $this->cachedTimestamp;
653 list( $limit, $offset ) = $this->getRequest()
654 ->getLimitOffsetForUser( $this->
getUser() );
655 if ( $this->getConfig()->
get( MainConfigNames::MiserMode ) ) {
656 $maxResults = $this->getMaxResults();
658 $limit = min( $limit, $maxResults );
660 $offset = min( $offset, $maxResults + 1 );
662 return [ $limit, $offset ];
674 $maxResults = $this->getMaxResults();
675 if ( $this->getConfig()->
get( MainConfigNames::MiserMode ) ) {
676 $limit = min( $uiLimit + 1, $maxResults - $uiOffset );
677 return max( $limit, 0 );
695 return max( $this->getConfig()->
get( MainConfigNames::QueryCacheLimit ), 10000 );
705 $this->checkPermissions();
708 $this->outputHeader();
710 $out = $this->getOutput();
712 if ( $this->isCached() && !$this->isCacheable() ) {
713 $out->addWikiMsg(
'querypage-disabled' );
717 $out->setSyndicated( $this->isSyndicated() );
719 if ( $this->limit == 0 && $this->offset == 0 ) {
720 list( $this->limit, $this->offset ) = $this->getLimitOffset();
722 $dbLimit = $this->getDBLimit( $this->limit, $this->offset );
724 if ( !$this->isCached() ) {
726 $res = $this->reallyDoQuery( $dbLimit, $this->offset );
729 $res = $this->fetchFromCache( $dbLimit, $this->offset );
730 if ( !$this->listoutput ) {
732 $ts = $this->getCachedTimestamp();
733 $lang = $this->getLanguage();
734 $maxResults =
$lang->formatNum( $this->getConfig()->
get(
735 MainConfigNames::QueryCacheLimit ) );
739 $updated =
$lang->userTimeAndDate( $ts, $user );
740 $updateddate =
$lang->userDate( $ts, $user );
741 $updatedtime =
$lang->userTime( $ts, $user );
742 $out->addMeta(
'Data-Cache-Time', $ts );
743 $out->addJsConfigVars(
'dataCacheTime', $ts );
744 $out->addWikiMsg(
'perfcachedts', $updated, $updateddate, $updatedtime, $maxResults );
746 $out->addWikiMsg(
'perfcached', $maxResults );
751 $disabledQueryPages = self::getDisabledQueryPages( $this->getConfig() );
752 if ( isset( $disabledQueryPages[$this->getName()] ) ) {
753 $runMode = $disabledQueryPages[$this->getName()];
754 if ( $runMode ===
'disabled' ) {
756 "<div class=\"mw-querypage-no-updates\">\n$1\n</div>",
757 'querypage-no-updates'
762 "<div class=\"mw-querypage-updates-" . $runMode .
"\">\n$1\n</div>",
763 'querypage-updates-' . $runMode
770 $this->numRows =
$res->numRows();
772 $dbr = $this->getRecacheDB();
773 $this->preprocessResults(
$dbr,
$res );
775 $out->addHTML( Xml::openElement(
'div', [
'class' =>
'mw-spcontent' ] ) );
778 if ( $this->shownavigation ) {
779 $out->addHTML( $this->getPageHeader() );
780 if ( $this->numRows > 0 ) {
781 $out->addHTML( $this->msg(
'showingresultsinrange' )->numParams(
782 min( $this->numRows, $this->limit ),
783 $this->offset + 1, ( min( $this->numRows, $this->limit ) + $this->offset ) )->parseAsBlock() );
785 $miserMaxResults = $this->getConfig()->get( MainConfigNames::MiserMode )
786 && ( $this->offset + $this->limit >= $this->getMaxResults() );
787 $atEnd = ( $this->numRows <= $this->limit ) || $miserMaxResults;
788 $paging = $this->buildPrevNextNavigation( $this->offset,
789 $this->limit, $this->linkParameters(), $atEnd, $par );
790 $out->addHTML(
'<p>' . $paging .
'</p>' );
794 $this->showEmptyText();
795 $out->addHTML( Xml::closeElement(
'div' ) );
803 $this->outputResults( $out,
807 min( $this->numRows, $this->limit ),
811 if ( $this->shownavigation ) {
813 $out->addHTML(
'<p>' . $paging .
'</p>' );
816 $out->addHTML( Xml::closeElement(
'div' ) );
835 if ( !$this->listoutput ) {
836 $html[] = $this->openList( $offset );
841 for ( $i = 0; $i < $num && $row =
$res->fetchObject(); $i++ ) {
842 $line = $this->formatResult( $skin, $row );
844 $html[] = $this->listoutput
846 :
"<li>{$line}</li>\n";
850 if ( !$this->listoutput ) {
851 $html[] = $this->closeList();
854 $html = $this->listoutput
855 ? $this->getContentLanguage()->listToText( $html )
856 : implode(
'', $html );
858 $out->addHTML( $html );
867 return "\n<ol start='" . ( $offset + 1 ) .
"' class='special'>\n";
899 if ( !
$res->numRows() ) {
903 $batch = $this->getLinkBatchFactory()->newLinkBatch();
904 foreach (
$res as $row ) {
905 $batch->add( $ns ?? (
int)$row->namespace, $row->title );
917 $this->loadBalancer = $loadBalancer;
925 if ( $this->loadBalancer === null ) {
928 $this->loadBalancer = MediaWikiServices::getInstance()->getDBLoadBalancer();
930 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.
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 query pages disabled and with it's 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