91 [ SpecialAncientPages::class,
'Ancientpages' ],
92 [ SpecialBrokenRedirects::class,
'BrokenRedirects' ],
93 [ SpecialDeadendPages::class,
'Deadendpages' ],
94 [ SpecialDoubleRedirects::class,
'DoubleRedirects' ],
95 [ SpecialFileDuplicateSearch::class,
'FileDuplicateSearch' ],
96 [ SpecialListDuplicatedFiles::class,
'ListDuplicatedFiles' ],
97 [ SpecialLinkSearch::class,
'LinkSearch' ],
98 [ SpecialListRedirects::class,
'Listredirects' ],
99 [ SpecialLonelyPages::class,
'Lonelypages' ],
100 [ SpecialLongPages::class,
'Longpages' ],
101 [ SpecialMediaStatistics::class,
'MediaStatistics' ],
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' ],
145 if ( $this->linkBatchFactory ===
null ) {
148 $this->linkBatchFactory = MediaWikiServices::getInstance()->getLinkBatchFactory();
159 $disableQueryPageUpdate = $config->
get(
'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;
225 throw new MWException(
"Bug in a QueryPage: doesn't implement getQueryInfo() nor "
226 .
"getQuery() properly" );
275 return $this->
getConfig()->get(
'DisableQueryPages' );
340 $this->
getOutput()->addWikiMsg(
'specialpage-empty' );
370 $fname = static::class .
'::recache';
381 $num =
$res->numRows();
384 foreach (
$res as $i => $row ) {
385 if ( isset( $row->value ) ) {
390 $value = intval( $row->value );
398 'qc_namespace' => $row->namespace,
399 'qc_title' => $row->title,
404 $dbw->doAtomicSection(
406 function (
IDatabase $dbw, $fname ) use ( $vals ) {
407 # Clear out any old cached data
408 $dbw->
delete(
'querycache',
409 [
'qc_type' => $this->
getName() ],
412 # Save results into the querycache table on the master
413 if ( count( $vals ) ) {
414 $dbw->
insert(
'querycache', $vals, $fname );
416 # Update the querycache_info record for the page
417 $dbw->
delete(
'querycache_info',
418 [
'qci_type' => $this->
getName() ],
421 $dbw->
insert(
'querycache_info',
422 [
'qci_type' => $this->
getName(),
430 if ( !$ignoreErrors ) {
458 $dbw->delete(
'querycache', [
460 'qc_namespace' =>
$title->getNamespace(),
461 'qc_title' =>
$title->getDBkey(),
475 $fname = static::class .
'::reallyDoQuery';
481 foreach ( $order as &$field ) {
486 if ( is_array( $query ) ) {
487 $tables = isset( $query[
'tables'] ) ? (array)$query[
'tables'] : [];
488 $fields = isset( $query[
'fields'] ) ? (array)$query[
'fields'] : [];
489 $conds = isset( $query[
'conds'] ) ? (array)$query[
'conds'] : [];
490 $options = isset( $query[
'options'] ) ? (array)$query[
'options'] : [];
491 $join_conds = isset( $query[
'join_conds'] ) ? (array)$query[
'join_conds'] : [];
494 $options[
'ORDER BY'] = $order;
498 $options[
'LIMIT'] = intval(
$limit );
502 $options[
'OFFSET'] = intval(
$offset );
505 $res =
$dbr->select( $tables, $fields, $conds, $fname,
506 $options, $join_conds
511 $sql .=
' ORDER BY ' . implode(
', ', $order );
547 $options[
'LIMIT'] = intval(
$limit );
551 $options[
'OFFSET'] = intval(
$offset );
556 foreach ( $order as &$field ) {
561 $options[
'ORDER BY'] = $order;
564 return $dbr->select(
'querycache',
566 'namespace' =>
'qc_namespace',
567 'title' =>
'qc_title',
568 'value' =>
'qc_value' ],
569 [
'qc_type' => $this->
getName() ],
590 if ( $this->cachedTimestamp ===
null ) {
592 $fname = static::class .
'::getCachedTimestamp';
593 $this->cachedTimestamp =
$dbr->selectField(
'querycache_info',
'qci_timestamp',
594 [
'qci_type' => $this->
getName() ], $fname );
613 ->getLimitOffsetForUser( $this->
getUser() );
614 if ( $this->
getConfig()->
get(
'MiserMode' ) ) {
634 if ( $this->
getConfig()->
get(
'MiserMode' ) ) {
635 $limit = min( $uiLimit + 1, $maxResults - $uiOffset );
654 return max( $this->
getConfig()->
get(
'QueryCacheLimit' ), 10000 );
672 $out->addWikiMsg(
'querypage-disabled' );
678 if ( $this->limit == 0 && $this->offset == 0 ) {
681 $dbLimit = $this->
getDBLimit( $this->limit, $this->offset );
684 # select one extra row for navigation
687 # Get the cached result, select one extra row for navigation
689 if ( !$this->listoutput ) {
690 # Fetch the timestamp of this update
693 $maxResults =
$lang->formatNum( $this->
getConfig()->
get(
'QueryCacheLimit' ) );
697 $updated =
$lang->userTimeAndDate( $ts, $user );
698 $updateddate =
$lang->userDate( $ts, $user );
699 $updatedtime =
$lang->userTime( $ts, $user );
700 $out->addMeta(
'Data-Cache-Time', $ts );
701 $out->addJsConfigVars(
'dataCacheTime', $ts );
702 $out->addWikiMsg(
'perfcachedts', $updated, $updateddate, $updatedtime, $maxResults );
704 $out->addWikiMsg(
'perfcached', $maxResults );
707 # If updates on this page have been disabled, let the user know
708 # that the data set won't be refreshed for now
710 if ( isset( $disabledQueryPages[$this->
getName()] ) ) {
711 $runMode = $disabledQueryPages[$this->
getName()];
712 if ( $runMode ===
'disabled' ) {
714 "<div class=\"mw-querypage-no-updates\">\n$1\n</div>",
715 'querypage-no-updates'
720 "<div class=\"mw-querypage-updates-" . $runMode .
"\">\n$1\n</div>",
721 'querypage-updates-' . $runMode
728 $this->numRows =
$res->numRows();
735 # Top header and navigation
736 if ( $this->shownavigation ) {
738 if ( $this->numRows > 0 ) {
739 $out->addHTML( $this->
msg(
'showingresultsinrange' )->numParams(
740 min( $this->numRows, $this->limit ), #
do not show the one extra row,
if exist
741 $this->offset + 1, ( min( $this->numRows, $this->limit ) + $this->offset ) )->parseAsBlock() );
742 # Disable the "next" link when we reach the end
743 $miserMaxResults = $this->
getConfig()->get(
'MiserMode' )
744 && ( $this->offset + $this->limit >= $this->
getMaxResults() );
745 $atEnd = ( $this->numRows <=
$this->limit ) || $miserMaxResults;
748 $out->addHTML(
'<p>' . $paging .
'</p>' );
750 # No results to show, so don't bother with "showing X of Y" etc.
751 # -- just let the user know and give up now
758 # The actual results; specialist subclasses will want to handle this
759 # with more than a straight list, so we hand them the info, plus
760 # an OutputPage, and let them get on with it
765 min( $this->numRows, $this->limit ), #
do not format the one extra row,
if exist
768 # Repeat the paging links at the bottom
769 if ( $this->shownavigation ) {
770 $out->addHTML(
'<p>' . $paging .
'</p>' );
792 if ( !$this->listoutput ) {
796 # $res might contain the whole 1,000 rows, so we read up to
797 # $num [should update this to use a Pager]
798 for ( $i = 0; $i < $num && $row =
$res->fetchObject(); $i++ ) {
801 $html[] = $this->listoutput
803 :
"<li>{$line}</li>\n";
807 if ( !$this->listoutput ) {
811 $html = $this->listoutput
813 : implode(
'', $html );
815 $out->addHTML( $html );
824 return "\n<ol start='" . (
$offset + 1 ) .
"' class='special'>\n";
856 if ( !
$res->numRows() ) {
861 foreach ( $res as $row ) {
862 $batch->add( $ns ?? $row->namespace, $row->title );
882 if ( $this->loadBalancer ===
null ) {
885 $this->loadBalancer = MediaWikiServices::getInstance()->getDBLoadBalancer();