118 private $loadBalancer =
null;
121 private $databaseProvider =
null;
124 private $linkBatchFactory =
null;
139 if ( $qp ===
null ) {
141 [ SpecialAncientPages::class,
'Ancientpages' ],
142 [ SpecialBrokenRedirects::class,
'BrokenRedirects' ],
143 [ SpecialDeadendPages::class,
'Deadendpages' ],
144 [ SpecialDoubleRedirects::class,
'DoubleRedirects' ],
145 [ SpecialListDuplicatedFiles::class,
'ListDuplicatedFiles' ],
146 [ SpecialLinkSearch::class,
'LinkSearch' ],
147 [ SpecialListRedirects::class,
'Listredirects' ],
148 [ SpecialLonelyPages::class,
'Lonelypages' ],
149 [ SpecialLongPages::class,
'Longpages' ],
150 [ SpecialMediaStatistics::class,
'MediaStatistics', SpecialMediaStatistics::MAX_LIMIT ],
151 [ SpecialMIMESearch::class,
'MIMEsearch' ],
152 [ SpecialMostCategories::class,
'Mostcategories' ],
153 [ SpecialMostImages::class,
'Mostimages' ],
154 [ SpecialMostInterwikis::class,
'Mostinterwikis' ],
155 [ SpecialMostLinkedCategories::class,
'Mostlinkedcategories' ],
156 [ SpecialMostLinkedTemplates::class,
'Mostlinkedtemplates' ],
157 [ SpecialMostLinked::class,
'Mostlinked' ],
158 [ SpecialMostRevisions::class,
'Mostrevisions' ],
159 [ SpecialFewestRevisions::class,
'Fewestrevisions' ],
160 [ SpecialShortPages::class,
'Shortpages' ],
161 [ SpecialUncategorizedCategories::class,
'Uncategorizedcategories' ],
162 [ SpecialUncategorizedPages::class,
'Uncategorizedpages' ],
163 [ SpecialUncategorizedImages::class,
'Uncategorizedimages' ],
164 [ SpecialUncategorizedTemplates::class,
'Uncategorizedtemplates' ],
165 [ SpecialUnusedCategories::class,
'Unusedcategories' ],
166 [ SpecialUnusedImages::class,
'Unusedimages' ],
167 [ SpecialWantedCategories::class,
'Wantedcategories' ],
168 [ SpecialWantedFiles::class,
'Wantedfiles' ],
169 [ SpecialWantedPages::class,
'Wantedpages' ],
170 [ SpecialWantedTemplates::class,
'Wantedtemplates' ],
171 [ SpecialUnwatchedPages::class,
'Unwatchedpages' ],
172 [ SpecialUnusedTemplates::class,
'Unusedtemplates' ],
173 [ SpecialWithoutInterwiki::class,
'Withoutinterwiki' ],
186 $this->linkBatchFactory = $linkBatchFactory;
194 if ( $this->linkBatchFactory === null ) {
199 return $this->linkBatchFactory;
210 if ( !is_array( $disableQueryPageUpdate ) ) {
215 foreach ( $disableQueryPageUpdate as $name => $runMode ) {
216 if ( is_int( $name ) ) {
218 $pages[$runMode] =
'disabled';
220 $pages[$name] = $runMode;
232 $this->listoutput = $bool;
276 throw new MWException(
"Bug in a QueryPage: doesn't implement getQueryInfo() nor "
277 .
"getQuery() properly" );
403 $this->getOutput()->addWikiMsg(
'specialpage-empty' );
428 public function recache( $limit, $ignoreErrors =
true ) {
429 if ( !$this->isCacheable() ) {
433 $fname = static::class .
'::recache';
434 $dbw = $this->getDatabaseProvider()->getPrimaryDatabase();
438 $res = $this->reallyDoQuery( $limit,
false );
441 $num = $res->numRows();
444 foreach ( $res as $i => $row ) {
445 if ( isset( $row->value ) ) {
446 if ( $this->usesTimestamps() ) {
449 $value = intval( $row->value );
456 'qc_type' => $this->getName(),
457 'qc_namespace' => $row->namespace,
458 'qc_title' => $row->title,
463 $dbw->doAtomicSection(
465 function (
IDatabase $dbw, $fname ) use ( $vals ) {
468 ->deleteFrom(
'querycache' )
469 ->where( [
'qc_type' => $this->getName() ] )
470 ->caller( $fname )->execute();
473 ->insertInto(
'querycache_info' )
474 ->row( [
'qci_type' => $this->getName(),
'qci_timestamp' => $dbw->
timestamp() ] )
475 ->onDuplicateKeyUpdate()
476 ->uniqueIndexFields( [
'qci_type' ] )
477 ->set( [
'qci_timestamp' => $dbw->
timestamp() ] )
478 ->caller( $fname )->execute();
482 if ( count( $vals ) ) {
483 foreach ( array_chunk( $vals, 500 ) as $chunk ) {
485 ->insertInto(
'querycache' )
487 ->caller( $fname )->execute();
492 if ( !$ignoreErrors ) {
507 return $this->getDBLoadBalancer()
508 ->getConnectionRef( ILoadBalancer::DB_REPLICA,
'vslow' );
518 if ( $this->isCached() ) {
519 $dbw = $this->getDatabaseProvider()->getPrimaryDatabase();
520 $dbw->newDeleteQueryBuilder()
521 ->deleteFrom(
'querycache' )
523 'qc_type' => $this->getName(),
524 'qc_namespace' => $title->getNamespace(),
525 'qc_title' => $title->getDBkey(),
527 ->caller( __METHOD__ )->execute();
537 $fname = static::class .
'::' . __FUNCTION__;
538 $dbw = $this->getDatabaseProvider()->getPrimaryDatabase();
539 $dbw->newDeleteQueryBuilder()
540 ->deleteFrom(
'querycache' )
541 ->where( [
'qc_type' => $this->getName() ] )
542 ->caller( $fname )->execute();
543 $dbw->newDeleteQueryBuilder()
544 ->deleteFrom(
'querycachetwo' )
545 ->where( [
'qcc_type' => $this->getName() ] )
546 ->caller( $fname )->execute();
547 $dbw->newDeleteQueryBuilder()
548 ->deleteFrom(
'querycache_info' )
549 ->where( [
'qci_type' => $this->getName() ] )
550 ->caller( $fname )->execute();
562 $fname = static::class .
'::reallyDoQuery';
563 $dbr = $this->getRecacheDB();
564 $query = $this->getQueryInfo();
565 $order = $this->getOrderFields();
567 if ( $this->sortDescending() ) {
568 foreach ( $order as &$field ) {
573 if ( is_array( $query ) ) {
574 $tables = isset( $query[
'tables'] ) ? (array)$query[
'tables'] : [];
575 $fields = isset( $query[
'fields'] ) ? (array)$query[
'fields'] : [];
576 $conds = isset( $query[
'conds'] ) ? (array)$query[
'conds'] : [];
577 $options = isset( $query[
'options'] ) ? (array)$query[
'options'] : [];
578 $join_conds = isset( $query[
'join_conds'] ) ? (array)$query[
'join_conds'] : [];
581 $options[
'ORDER BY'] = $order;
584 if ( $limit !==
false ) {
585 $options[
'LIMIT'] = intval( $limit );
588 if ( $offset !==
false ) {
589 $options[
'OFFSET'] = intval( $offset );
592 $res = $dbr->select( $tables, $fields, $conds, $fname,
593 $options, $join_conds
597 MWDebug::detectDeprecatedOverride(
603 $sql = $this->getSQL();
604 $sql .=
' ORDER BY ' . implode(
', ', $order );
605 $sql = $dbr->limitResult( $sql, $limit, $offset );
607 $res = $dbr->query( $sql, $fname );
619 public function doQuery( $offset =
false, $limit =
false ) {
620 if ( $this->isCached() && $this->isCacheable() ) {
621 return $this->fetchFromCache( $limit, $offset );
623 return $this->reallyDoQuery( $limit, $offset );
637 $dbr = $this->getDatabaseProvider()->getReplicaDatabase();
638 $queryBuilder = $dbr->newSelectQueryBuilder()
639 ->select( [
'qc_type',
'namespace' =>
'qc_namespace',
'title' =>
'qc_title',
'value' =>
'qc_value' ] )
640 ->from(
'querycache' )
641 ->where( [
'qc_type' => $this->getName() ] );
643 if ( $limit !==
false ) {
644 $queryBuilder->limit( intval( $limit ) );
647 if ( $offset !==
false ) {
648 $queryBuilder->offset( intval( $offset ) );
651 $order = $this->getCacheOrderFields();
652 if ( $this->sortDescending() ) {
653 $queryBuilder->orderBy( $order, SelectQueryBuilder::SORT_DESC );
655 $queryBuilder->orderBy( $order );
658 return $queryBuilder->caller( __METHOD__ )->fetchResultSet();
676 if ( $this->cachedTimestamp ===
null ) {
677 $dbr = $this->getDatabaseProvider()->getReplicaDatabase();
678 $fname = static::class .
'::getCachedTimestamp';
679 $this->cachedTimestamp = $dbr->newSelectQueryBuilder()
680 ->select(
'qci_timestamp' )
681 ->from(
'querycache_info' )
682 ->where( [
'qci_type' => $this->getName() ] )
683 ->caller( $fname )->fetchField();
685 return $this->cachedTimestamp;
701 [ $limit, $offset ] = $this->getRequest()
702 ->getLimitOffsetForUser( $this->
getUser() );
704 $maxResults = $this->getMaxResults();
706 $limit = min( $limit, $maxResults );
708 $offset = min( $offset, $maxResults + 1 );
710 return [ $limit, $offset ];
722 $maxResults = $this->getMaxResults();
724 $limit = min( $uiLimit + 1, $maxResults - $uiOffset );
725 return max( $limit, 0 );
753 $this->checkPermissions();
756 $this->outputHeader();
758 $out = $this->getOutput();
760 if ( $this->isCached() && !$this->isCacheable() ) {
761 $out->addWikiMsg(
'querypage-disabled' );
765 $out->setSyndicated( $this->isSyndicated() );
767 if ( $this->limit == 0 && $this->offset == 0 ) {
768 [ $this->limit, $this->offset ] = $this->getLimitOffset();
770 $dbLimit = $this->getDBLimit( $this->limit, $this->offset );
772 if ( !$this->isCached() ) {
774 $res = $this->reallyDoQuery( $dbLimit, $this->offset );
777 $res = $this->fetchFromCache( $dbLimit, $this->offset );
778 if ( !$this->listoutput ) {
780 $ts = $this->getCachedTimestamp();
781 $lang = $this->getLanguage();
782 $maxResults = $lang->formatNum( $this->getConfig()->
get(
787 $updated = $lang->userTimeAndDate( $ts, $user );
788 $updateddate = $lang->userDate( $ts, $user );
789 $updatedtime = $lang->userTime( $ts, $user );
790 $out->addMeta(
'Data-Cache-Time', $ts );
791 $out->addJsConfigVars(
'dataCacheTime', $ts );
792 $out->addWikiMsg(
'perfcachedts', $updated, $updateddate, $updatedtime, $maxResults );
794 $out->addWikiMsg(
'perfcached', $maxResults );
799 $disabledQueryPages = self::getDisabledQueryPages( $this->getConfig() );
800 if ( isset( $disabledQueryPages[$this->getName()] ) ) {
801 $runMode = $disabledQueryPages[$this->getName()];
802 if ( $runMode ===
'disabled' ) {
804 "<div class=\"mw-querypage-no-updates\">\n$1\n</div>",
805 'querypage-no-updates'
810 "<div class=\"mw-querypage-updates-" . $runMode .
"\">\n$1\n</div>",
811 'querypage-updates-' . $runMode
818 $this->numRows = $res->numRows();
820 $dbr = $this->getRecacheDB();
821 $this->preprocessResults( $dbr, $res );
823 $out->addHTML( Xml::openElement(
'div', [
'class' =>
'mw-spcontent' ] ) );
826 if ( $this->shownavigation ) {
827 $out->addHTML( $this->getPageHeader() );
828 if ( $this->numRows > 0 ) {
829 $out->addHTML( $this->msg(
'showingresultsinrange' )->numParams(
830 min( $this->numRows, $this->limit ),
831 $this->offset + 1, ( min( $this->numRows, $this->limit ) + $this->offset ) )->parseAsBlock() );
834 && ( $this->offset + $this->limit >= $this->getMaxResults() );
835 $atEnd = ( $this->numRows <= $this->limit ) || $miserMaxResults;
836 $paging = $this->buildPrevNextNavigation( $this->offset,
837 $this->limit, $this->linkParameters(), $atEnd, $par );
838 $out->addHTML(
'<p>' . $paging .
'</p>' );
842 $this->showEmptyText();
843 $out->addHTML( Xml::closeElement(
'div' ) );
851 $this->outputResults( $out,
855 min( $this->numRows, $this->limit ),
859 if ( $this->shownavigation ) {
861 $out->addHTML(
'<p>' . $paging .
'</p>' );
864 $out->addHTML( Xml::closeElement(
'div' ) );
880 protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) {
883 if ( !$this->listoutput ) {
884 $html[] = $this->openList( $offset );
889 for ( $i = 0; $i < $num && $row = $res->fetchObject(); $i++ ) {
890 $line = $this->formatResult( $skin, $row );
892 $html[] = $this->listoutput
894 :
"<li>{$line}</li>\n";
898 if ( !$this->listoutput ) {
899 $html[] = $this->closeList();
902 $html = $this->listoutput
903 ? $this->getContentLanguage()->listToText( $html )
904 : implode(
'', $html );
906 $out->addHTML( $html );
915 return "\n<ol start='" . ( $offset + 1 ) .
"' class='special'>\n";
951 $batch = $this->getLinkBatchFactory()->newLinkBatch();
952 foreach ( $res as $row ) {
953 $batch->add( $ns ?? (
int)$row->namespace, $row->title );
965 $this->loadBalancer = $loadBalancer;
973 if ( $this->loadBalancer === null ) {
978 return $this->loadBalancer;
986 $this->databaseProvider = $databaseProvider;
994 if ( $this->databaseProvider === null ) {
995 $this->databaseProvider = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
997 return $this->databaseProvider;
1005class_alias( QueryPage::class,
'QueryPage' );
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'))
A class containing constants representing the names of configuration variables.
const DisableQueryPages
Name constant for the DisableQueryPages setting, for use with Config::get()
const QueryCacheLimit
Name constant for the QueryCacheLimit setting, for use with Config::get()
const MiserMode
Name constant for the MiserMode setting, for use with Config::get()
const DisableQueryPageUpdate
Name constant for the DisableQueryPageUpdate setting, for use with Config::get()
This is one of the Core classes and should be read at least once by any new developers.
This is a class for doing query pages; since they're almost all the same, we factor out some of the f...
getPageHeader()
The content returned by this function will be output before any result.
linkParameters()
If using extra form wheely-dealies, return a set of parameters here as an associative array.
bool $shownavigation
Whether to show prev/next links.
bool $listoutput
Whether or not we want plain listoutput rather than an ordered list.
getOrderFields()
Subclasses return an array of fields to order by here.
preprocessResults( $db, $res)
Do any necessary preprocessing of the result object.
setDatabaseProvider(IConnectionProvider $databaseProvider)
getLimitOffset()
Returns limit and offset, as returned by $this->getRequest()->getLimitOffsetForUser().
int $offset
The offset and limit in use, as passed to the query() function.
getDBLimit( $uiLimit, $uiOffset)
What is limit to fetch from DB.
isCached()
Whether or not the output of the page in question is retrieved from the database cache.
execute( $par)
This is the actual workhorse.
setListoutput( $bool)
A mutator for $this->listoutput;.
executeLBFromResultWrapper(IResultWrapper $res, $ns=null)
Creates a new LinkBatch object, adds all pages from the passed result wrapper (MUST include title and...
getMaxResults()
Get max number of results we can return in miser mode.
isExpensive()
Should this query page only be updated offline on large wikis?
static getPages()
Get a list of query page classes and their associated special pages, for periodic updates.
fetchFromCache( $limit, $offset=false)
Fetch the query results from the query cache.
setDBLoadBalancer(ILoadBalancer $loadBalancer)
string null false $cachedTimestamp
deleteAllCachedData()
Remove all cached value This is needed when the page is no longer using the cache.
showEmptyText()
Outputs some kind of an informative message (via OutputPage) to let the user know that the query retu...
recache( $limit, $ignoreErrors=true)
Clear the cache and save new results.
doQuery( $offset=false, $limit=false)
Somewhat deprecated, you probably want to be using execute()
outputResults( $out, $skin, $dbr, $res, $num, $offset)
Format and output report results using the given information plus OutputPage.
getRecacheDB()
Get a DB connection to be used for slow recache queries.
static getDisabledQueryPages(Config $config)
Get a list of disabled query pages and their run mode.
sortDescending()
Override to sort by increasing values.
int $numRows
The number of rows returned by the query.
usesTimestamps()
Does this query return timestamps rather than integers in its 'value' field? If true,...
isSyndicated()
Sometimes we don't want to build rss / atom feeds.
getQueryInfo()
Subclasses return an SQL query here, formatted as an array with the following keys: tables => Table(s...
setLinkBatchFactory(LinkBatchFactory $linkBatchFactory)
formatResult( $skin, $result)
Formats the results of the query for display.
getCacheOrderFields()
Return the order fields for fetchFromCache.
isCacheable()
Is the output of this query cacheable? Non-cacheable expensive pages will be disabled in miser mode a...
getSQL()
For back-compat, subclasses may return a raw SQL query here, as a string.
reallyDoQuery( $limit, $offset=false)
Run the query and return the result.
Parent class for all special pages.
Implements Special:Ancientpages.
A special page that list pages that contain no link to other pages.
A special page looking for articles with no article linking to them, thus being lonely.
SpecialShortpages extends QueryPage.
A special page looking for page without any category.
A special page that displays a list of pages that are not on anyone's watchlist.
A special page that lists most linked pages that does not exist.
The base class for all skins.
Module of static functions for generating XML.