MediaWiki master
QueryPage.php
Go to the documentation of this file.
1<?php
10namespace MediaWiki\SpecialPage;
11
12use Exception;
56use RuntimeException;
57use stdClass;
66use Wikimedia\Timestamp\TimestampFormat as TS;
67
77abstract class QueryPage extends SpecialPage {
79 protected $listoutput = false;
80
82 protected $offset = 0;
83
85 protected $limit = 0;
86
94 protected $numRows;
95
99 protected $cachedTimestamp = null;
100
104 protected $shownavigation = true;
105
107 private $loadBalancer = null;
108
110 private $databaseProvider = null;
111
113 private $linkBatchFactory = null;
114
116 private $httpRequestFactory = null;
117
128 public static function getPages() {
129 static $qp = null;
130
131 if ( $qp === null ) {
132 $qp = [
133 [ SpecialAncientPages::class, 'Ancientpages' ],
134 [ SpecialBrokenRedirects::class, 'BrokenRedirects' ],
135 [ SpecialDeadendPages::class, 'Deadendpages' ],
136 [ SpecialDoubleRedirects::class, 'DoubleRedirects' ],
137 [ SpecialListDuplicatedFiles::class, 'ListDuplicatedFiles' ],
138 [ SpecialLinkSearch::class, 'LinkSearch' ],
139 [ SpecialListRedirects::class, 'Listredirects' ],
140 [ SpecialLonelyPages::class, 'Lonelypages' ],
141 [ SpecialLongPages::class, 'Longpages' ],
142 [ SpecialMediaStatistics::class, 'MediaStatistics', SpecialMediaStatistics::MAX_LIMIT ],
143 [ SpecialMIMESearch::class, 'MIMEsearch' ],
144 [ SpecialMostCategories::class, 'Mostcategories' ],
145 [ SpecialMostImages::class, 'Mostimages' ],
146 [ SpecialMostInterwikis::class, 'Mostinterwikis' ],
147 [ SpecialMostLinkedCategories::class, 'Mostlinkedcategories' ],
148 [ SpecialMostLinkedTemplates::class, 'Mostlinkedtemplates' ],
149 [ SpecialMostLinked::class, 'Mostlinked' ],
150 [ SpecialMostRevisions::class, 'Mostrevisions' ],
151 [ SpecialFewestRevisions::class, 'Fewestrevisions' ],
152 [ SpecialShortPages::class, 'Shortpages' ],
153 [ SpecialUncategorizedCategories::class, 'Uncategorizedcategories' ],
154 [ SpecialUncategorizedPages::class, 'Uncategorizedpages' ],
155 [ SpecialUncategorizedImages::class, 'Uncategorizedimages' ],
156 [ SpecialUncategorizedTemplates::class, 'Uncategorizedtemplates' ],
157 [ SpecialUnusedCategories::class, 'Unusedcategories' ],
158 [ SpecialUnusedImages::class, 'Unusedimages' ],
159 [ SpecialWantedCategories::class, 'Wantedcategories' ],
160 [ SpecialWantedFiles::class, 'Wantedfiles' ],
161 [ SpecialWantedPages::class, 'Wantedpages' ],
162 [ SpecialWantedTemplates::class, 'Wantedtemplates' ],
163 [ SpecialUnwatchedPages::class, 'Unwatchedpages' ],
164 [ SpecialUnusedTemplates::class, 'Unusedtemplates' ],
165 [ SpecialWithoutInterwiki::class, 'Withoutinterwiki' ],
166 ];
167 ( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )->onWgQueryPages( $qp );
168 }
169
170 return $qp;
171 }
172
177 final protected function setLinkBatchFactory( LinkBatchFactory $linkBatchFactory ) {
178 $this->linkBatchFactory = $linkBatchFactory;
179 }
180
185 final protected function getLinkBatchFactory(): LinkBatchFactory {
186 if ( $this->linkBatchFactory === null ) {
187 // Fallback if not provided
188 // TODO Change to wfWarn in a future release
189 $this->linkBatchFactory = MediaWikiServices::getInstance()->getLinkBatchFactory();
190 }
191 return $this->linkBatchFactory;
192 }
193
197 private function getHttpRequestFactory(): HttpRequestFactory {
198 if ( $this->httpRequestFactory === null ) {
199 // Fallback if not provided
200 // TODO Do not rely on global state
201 $this->httpRequestFactory = MediaWikiServices::getInstance()->getHttpRequestFactory();
202 }
203 return $this->httpRequestFactory;
204 }
205
211 public static function getDisabledQueryPages( Config $config ) {
212 $disableQueryPageUpdate = $config->get( MainConfigNames::DisableQueryPageUpdate );
213
214 if ( !is_array( $disableQueryPageUpdate ) ) {
215 return [];
216 }
217
218 $pages = [];
219 foreach ( $disableQueryPageUpdate as $name => $runMode ) {
220 if ( is_int( $name ) ) {
221 // The run mode may be omitted
222 $pages[$runMode] = 'disabled';
223 } else {
224 $pages[$name] = $runMode;
225 }
226 }
227 return $pages;
228 }
229
235 protected function setListoutput( $bool ) {
236 $this->listoutput = $bool;
237 }
238
262 abstract public function getQueryInfo();
263
272 protected function getOrderFields() {
273 return [ 'value' ];
274 }
275
287 public function usesTimestamps() {
288 return false;
289 }
290
297 protected function sortDescending() {
298 return true;
299 }
300
321 public function isExpensive() {
322 return $this->getConfig()->get( MainConfigNames::DisableQueryPages );
323 }
324
333 public function isCacheable() {
334 return true;
335 }
336
344 public function isCached() {
345 return $this->isExpensive() && $this->getConfig()->get( MainConfigNames::MiserMode );
346 }
347
354 public function isSyndicated() {
355 return true;
356 }
357
365 public function usesExternalSource(): bool {
366 $config = $this->getConfig();
367 $externalSources = $config->get( MainConfigNames::ExternalQuerySources );
368 $pageName = $this->getName();
369
370 return !empty( $externalSources[$pageName]['enabled'] ) &&
371 !empty( $externalSources[$pageName]['url'] );
372 }
373
383 abstract protected function formatResult( $skin, $result );
384
391 protected function getPageHeader() {
392 return '';
393 }
394
402 protected function showEmptyText() {
403 $this->getOutput()->addWikiMsg( 'specialpage-empty' );
404 }
405
414 protected function linkParameters() {
415 return [];
416 }
417
428 public function recache( $limit, $unused = true ) {
429 if ( !$this->isCacheable() ) {
430 return 0;
431 }
432
433 $fname = static::class . '::recache';
434 $dbw = $this->getDatabaseProvider()->getPrimaryDatabase();
435
436 // Do query
437 $res = $this->reallyDoQuery( $limit, false );
438 $num = false;
439 if ( $res ) {
440 $num = $res->numRows();
441 // Fetch results
442 $vals = [];
443 foreach ( $res as $i => $row ) {
444 if ( isset( $row->value ) ) {
445 if ( $this->usesTimestamps() ) {
446 $value = (int)wfTimestamp( TS::UNIX, $row->value );
447 } else {
448 $value = intval( $row->value ); // T16414
449 }
450 } else {
451 $value = $i;
452 }
453
454 $vals[] = [
455 'qc_type' => $this->getName(),
456 'qc_namespace' => $row->namespace,
457 'qc_title' => $row->title,
458 'qc_value' => $value
459 ];
460 }
461
462 $dbw->doAtomicSection(
463 $fname,
464 function ( IDatabase $dbw, $fname ) {
465 // Clear out any old cached data
467 ->deleteFrom( 'querycache' )
468 ->where( [ 'qc_type' => $this->getName() ] )
469 ->caller( $fname )->execute();
470 // Update the querycache_info record for the page
472 ->insertInto( 'querycache_info' )
473 ->row( [ 'qci_type' => $this->getName(), 'qci_timestamp' => $dbw->timestamp() ] )
474 ->onDuplicateKeyUpdate()
475 ->uniqueIndexFields( [ 'qci_type' ] )
476 ->set( [ 'qci_timestamp' => $dbw->timestamp() ] )
477 ->caller( $fname )->execute();
478 }
479 );
480 // Save results into the querycache table on the primary DB
481 if ( count( $vals ) ) {
482 foreach ( array_chunk( $vals, 500 ) as $chunk ) {
484 ->insertInto( 'querycache' )
485 ->rows( $chunk )
486 ->caller( $fname )->execute();
487 }
488 }
489 }
490
491 return $num;
492 }
493
499 protected function getRecacheDB() {
500 return $this->getDatabaseProvider()->getReplicaDatabase( false, 'vslow' );
501 }
502
509 public function delete( LinkTarget $title ) {
510 if ( $this->isCached() ) {
511 $dbw = $this->getDatabaseProvider()->getPrimaryDatabase();
512 $dbw->newDeleteQueryBuilder()
513 ->deleteFrom( 'querycache' )
514 ->where( [
515 'qc_type' => $this->getName(),
516 'qc_namespace' => $title->getNamespace(),
517 'qc_title' => $title->getDBkey(),
518 ] )
519 ->caller( __METHOD__ )->execute();
520 }
521 }
522
528 public function deleteAllCachedData() {
529 $fname = static::class . '::' . __FUNCTION__;
530 $dbw = $this->getDatabaseProvider()->getPrimaryDatabase();
531 $dbw->newDeleteQueryBuilder()
532 ->deleteFrom( 'querycache' )
533 ->where( [ 'qc_type' => $this->getName() ] )
534 ->caller( $fname )->execute();
535 $dbw->newDeleteQueryBuilder()
536 ->deleteFrom( 'querycachetwo' )
537 ->where( [ 'qcc_type' => $this->getName() ] )
538 ->caller( $fname )->execute();
539 $dbw->newDeleteQueryBuilder()
540 ->deleteFrom( 'querycache_info' )
541 ->where( [ 'qci_type' => $this->getName() ] )
542 ->caller( $fname )->execute();
543 }
544
553 public function reallyDoQuery( $limit, $offset = false ) {
554 if ( $this->usesExternalSource() ) {
555 return $this->reallyDoQueryExternal();
556 }
557
558 return $this->reallyDoQueryInternal( $limit, $offset );
559 }
560
568 private function reallyDoQueryInternal( $limit, $offset = false ) {
569 $fname = static::class . '::reallyDoQueryInternal';
570 $dbr = $this->getRecacheDB();
571 $query = $this->getQueryInfo();
572 $order = $this->getOrderFields();
573
574 if ( $this->sortDescending() ) {
575 foreach ( $order as &$field ) {
576 $field .= ' DESC';
577 }
578 }
579
580 $tables = isset( $query['tables'] ) ? (array)$query['tables'] : [];
581 $fields = isset( $query['fields'] ) ? (array)$query['fields'] : [];
582 $conds = isset( $query['conds'] ) ? (array)$query['conds'] : [];
583 $options = isset( $query['options'] ) ? (array)$query['options'] : [];
584 $join_conds = isset( $query['join_conds'] ) ? (array)$query['join_conds'] : [];
585
586 $queryBuilder = $dbr->newSelectQueryBuilder()
587 ->tables( $tables )
588 ->fields( $fields )
589 ->conds( $conds )
590 ->caller( $fname )
591 ->options( $options )
592 ->joinConds( $join_conds );
593 if ( $order ) {
594 $queryBuilder->orderBy( $order );
595 }
596
597 if ( $limit !== false ) {
598 $queryBuilder->limit( intval( $limit ) );
599 }
600
601 if ( $offset !== false ) {
602 $queryBuilder->offset( intval( $offset ) );
603 }
604
605 return $queryBuilder->fetchResultSet();
606 }
607
613 private function reallyDoQueryExternal() {
614 $fname = static::class . '::reallyDoQueryExternal';
615 $httpRequestFactory = $this->getHttpRequestFactory();
616 $externalSources = $this->getConfig()->get( MainConfigNames::ExternalQuerySources );
617 $pageName = $this->getName();
618 $config = $externalSources[$pageName];
619
620 $options = [];
621 if ( isset( $config['timeout'] ) ) {
622 $options['timeout'] = (int)$config['timeout'];
623 }
624
625 $request = $httpRequestFactory->create( $config['url'], $options, $fname );
626
627 $status = $request->execute();
628 if ( !$status->isOK() ) {
629 throw new RuntimeException( "Failed to fetch data from external source '{$pageName}': " .
630 $status->getMessage()->text() );
631 }
632
633 $content = $request->getContent();
634 if ( $content === null || $content === '' ) {
635 throw new RuntimeException( "Empty response received from external source '{$pageName}'" );
636 }
637
638 $decoded = json_decode( $content, true );
639 if ( $decoded === null ) {
640 throw new RuntimeException( "Invalid JSON response from external source '{$pageName}': " .
641 json_last_error_msg() );
642 }
643
644 if ( !is_array( $decoded ) ) {
645 throw new RuntimeException( "Expected array data from external source '{$pageName}', got " .
646 gettype( $decoded ) );
647 }
648
649 $result = [];
650 foreach ( $decoded as $i => $row ) {
651 if ( !is_array( $row ) ) {
652 throw new RuntimeException( "Invalid row data at index {$i} from external source '{$pageName}': " .
653 'expected array, got ' . gettype( $row ) );
654 }
655
656 $requiredFields = [ 'qc_namespace', 'qc_title', 'qc_value' ];
657 foreach ( $requiredFields as $field ) {
658 if ( !array_key_exists( $field, $row ) ) {
659 throw new RuntimeException(
660 "Missing required field '{$field}' in row {$i} from external source '{$pageName}'" );
661 }
662 }
663
664 $result[] = (object)[
665 'namespace' => $row['qc_namespace'],
666 'title' => $row['qc_title'],
667 'value' => $row['qc_value']
668 ];
669 }
670
671 return new FakeResultWrapper( $result );
672 }
673
680 public function doQuery( $offset = false, $limit = false ) {
681 if ( $this->isCached() && $this->isCacheable() ) {
682 return $this->fetchFromCache( $limit, $offset );
683 } else {
684 return $this->reallyDoQuery( $limit, $offset );
685 }
686 }
687
697 public function fetchFromCache( $limit, $offset = false ) {
698 $dbr = $this->getDatabaseProvider()->getReplicaDatabase();
699 $queryBuilder = $dbr->newSelectQueryBuilder()
700 ->select( [ 'qc_type', 'namespace' => 'qc_namespace', 'title' => 'qc_title', 'value' => 'qc_value' ] )
701 ->from( 'querycache' )
702 ->where( [ 'qc_type' => $this->getName() ] );
703
704 if ( $limit !== false ) {
705 $queryBuilder->limit( intval( $limit ) );
706 }
707
708 if ( $offset !== false ) {
709 $queryBuilder->offset( intval( $offset ) );
710 }
711
712 $order = $this->getCacheOrderFields();
713 if ( $this->sortDescending() ) {
714 $queryBuilder->orderBy( $order, SelectQueryBuilder::SORT_DESC );
715 } else {
716 $queryBuilder->orderBy( $order );
717 }
718
719 return $queryBuilder->caller( __METHOD__ )->fetchResultSet();
720 }
721
729 protected function getCacheOrderFields() {
730 return [ 'value' ];
731 }
732
736 public function getCachedTimestamp() {
737 if ( $this->cachedTimestamp === null ) {
738 $dbr = $this->getDatabaseProvider()->getReplicaDatabase();
739 $fname = static::class . '::getCachedTimestamp';
740 $this->cachedTimestamp = $dbr->newSelectQueryBuilder()
741 ->select( 'qci_timestamp' )
742 ->from( 'querycache_info' )
743 ->where( [ 'qci_type' => $this->getName() ] )
744 ->caller( $fname )->fetchField();
745 }
746 return $this->cachedTimestamp;
747 }
748
761 protected function getLimitOffset() {
762 [ $limit, $offset ] = $this->getRequest()
763 ->getLimitOffsetForUser( $this->getUser() );
764 if ( $this->getConfig()->get( MainConfigNames::MiserMode ) ) {
765 $maxResults = $this->getMaxResults();
766 // Can't display more than max results on a page
767 $limit = min( $limit, $maxResults );
768 // Can't skip over more than the end of $maxResults
769 $offset = min( $offset, $maxResults + 1 );
770 }
771 return [ $limit, $offset ];
772 }
773
782 protected function getDBLimit( $uiLimit, $uiOffset ) {
783 $maxResults = $this->getMaxResults();
784 if ( $this->getConfig()->get( MainConfigNames::MiserMode ) ) {
785 $limit = min( $uiLimit + 1, $maxResults - $uiOffset );
786 return max( $limit, 0 );
787 } else {
788 return $uiLimit + 1;
789 }
790 }
791
802 protected function getMaxResults() {
803 // Max of 10000, unless we store more than 10000 in query cache.
804 return max( $this->getConfig()->get( MainConfigNames::QueryCacheLimit ), 10000 );
805 }
806
813 public function execute( $par ) {
814 $this->checkPermissions();
815
816 $this->setHeaders();
817 $this->outputHeader();
818
819 $out = $this->getOutput();
820
821 if ( $this->isCached() && !$this->isCacheable() ) {
822 $out->addWikiMsg( 'querypage-disabled' );
823 return;
824 }
825
826 $out->setSyndicated( $this->isSyndicated() );
827
828 if ( $this->limit == 0 && $this->offset == 0 ) {
829 [ $this->limit, $this->offset ] = $this->getLimitOffset();
830 }
831 $dbLimit = $this->getDBLimit( $this->limit, $this->offset );
832 // @todo Use doQuery()
833 if ( !$this->isCached() ) {
834 // select one extra row for navigation
835 $res = $this->reallyDoQuery( $dbLimit, $this->offset );
836 } else {
837 // Get the cached result, select one extra row for navigation
838 $res = $this->fetchFromCache( $dbLimit, $this->offset );
839 if ( !$this->listoutput ) {
840 // Fetch the timestamp of this update
841 $ts = $this->getCachedTimestamp();
842 $lang = $this->getLanguage();
843 $maxResults = $lang->formatNum( $this->getConfig()->get(
844 MainConfigNames::QueryCacheLimit ) );
845
846 if ( $ts ) {
847 $user = $this->getUser();
848 $updated = $lang->userTimeAndDate( $ts, $user );
849 $updateddate = $lang->userDate( $ts, $user );
850 $updatedtime = $lang->userTime( $ts, $user );
851 $out->addMeta( 'Data-Cache-Time', $ts );
852 $out->addJsConfigVars( 'dataCacheTime', $ts );
853 $out->addWikiMsg( 'perfcachedts', $updated, $updateddate, $updatedtime, $maxResults );
854 } else {
855 $out->addWikiMsg( 'perfcached', $maxResults );
856 }
857
858 // If updates on this page have been disabled, let the user know
859 // that the data set won't be refreshed for now
860 $disabledQueryPages = self::getDisabledQueryPages( $this->getConfig() );
861 if ( isset( $disabledQueryPages[$this->getName()] ) ) {
862 $runMode = $disabledQueryPages[$this->getName()];
863 if ( $runMode === 'disabled' ) {
864 $out->wrapWikiMsg(
865 "<div class=\"mw-querypage-no-updates\">\n$1\n</div>",
866 'querypage-no-updates'
867 );
868 } else {
869 // Messages used here: querypage-updates-periodical
870 $out->wrapWikiMsg(
871 "<div class=\"mw-querypage-updates-" . $runMode . "\">\n$1\n</div>",
872 'querypage-updates-' . $runMode
873 );
874 }
875 }
876 }
877 }
878
879 $this->numRows = $res->numRows();
880
881 $dbr = $this->getRecacheDB();
882 $this->preprocessResults( $dbr, $res );
883
884 $out->addHTML( Html::openElement( 'div', [ 'class' => 'mw-spcontent' ] ) );
885
886 // Top header and navigation
887 if ( $this->shownavigation ) {
888 $out->addHTML( $this->getPageHeader() );
889 if ( $this->numRows > 0 ) {
890 $out->addHTML( $this->msg( 'showingresultsinrange' )->numParams(
891 min( $this->numRows, $this->limit ), // do not show the one extra row, if exist
892 $this->offset + 1, ( min( $this->numRows, $this->limit ) + $this->offset ) )->parseAsBlock() );
893 // Disable the "next" link when we reach the end
894 $miserMaxResults = $this->getConfig()->get( MainConfigNames::MiserMode )
895 && ( $this->offset + $this->limit >= $this->getMaxResults() );
896 $atEnd = ( $this->numRows <= $this->limit ) || $miserMaxResults;
897 $paging = $this->buildPrevNextNavigation( $this->offset,
898 $this->limit, $this->linkParameters(), $atEnd, $par );
899 $out->addHTML( '<p>' . $paging . '</p>' );
900 } else {
901 // No results to show, so don't bother with "showing X of Y" etc.
902 // -- just let the user know and give up now
903 $this->showEmptyText();
904 $out->addHTML( Html::closeElement( 'div' ) );
905 return;
906 }
907 }
908
909 // The actual results; specialist subclasses will want to handle this
910 // with more than a straight list, so we hand them the info, plus
911 // an OutputPage, and let them get on with it
912 $this->outputResults( $out,
913 $this->getSkin(),
914 $dbr, // Should use IResultWrapper for this
915 $res,
916 min( $this->numRows, $this->limit ), // do not format the one extra row, if exist
917 $this->offset );
918
919 // Repeat the paging links at the bottom
920 if ( $this->shownavigation ) {
921 // @phan-suppress-next-line PhanPossiblyUndeclaredVariable paging is set when used here
922 $out->addHTML( '<p>' . $paging . '</p>' );
923 }
924
925 $out->addHTML( Html::closeElement( 'div' ) );
926 }
927
941 protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) {
942 if ( $num > 0 ) {
943 $html = [];
944 if ( !$this->listoutput ) {
945 $html[] = $this->openList( $offset );
946 }
947
948 // $res might contain the whole 1,000 rows, so we read up to
949 // $num [should update this to use a Pager]
950 // phpcs:ignore Generic.CodeAnalysis.AssignmentInCondition.Found
951 for ( $i = 0; $i < $num && $row = $res->fetchObject(); $i++ ) {
952 $line = $this->formatResult( $skin, $row );
953 if ( $line ) {
954 $html[] = $this->listoutput
955 ? $line
956 : "<li>{$line}</li>\n";
957 }
958 }
959
960 if ( !$this->listoutput ) {
961 $html[] = $this->closeList();
962 }
963
964 $html = $this->listoutput
965 ? $this->getContentLanguage()->listToText( $html )
966 : implode( '', $html );
967
968 $out->addHTML( $html );
969 }
970 }
971
976 protected function openList( $offset ) {
977 return "\n<ol start='" . ( $offset + 1 ) . "' class='special'>\n";
978 }
979
983 protected function closeList() {
984 return "</ol>\n";
985 }
986
993 protected function preprocessResults( $db, $res ) {
994 }
995
1008 protected function executeLBFromResultWrapper( IResultWrapper $res, $ns = null ) {
1009 if ( !$res->numRows() ) {
1010 return;
1011 }
1012
1013 $batch = $this->getLinkBatchFactory()->newLinkBatch()->setCaller( __METHOD__ );
1014 foreach ( $res as $row ) {
1015 $batch->add( $ns ?? (int)$row->namespace, $row->title );
1016 }
1017 $batch->execute();
1018
1019 $res->seek( 0 );
1020 }
1021
1027 final protected function setDBLoadBalancer( ILoadBalancer $loadBalancer ) {
1028 $this->loadBalancer = $loadBalancer;
1029 }
1030
1036 final protected function getDBLoadBalancer(): ILoadBalancer {
1037 if ( $this->loadBalancer === null ) {
1038 // Fallback if not provided
1039 // TODO Change to wfWarn in a future release
1040 $this->loadBalancer = MediaWikiServices::getInstance()->getDBLoadBalancer();
1041 }
1042 return $this->loadBalancer;
1043 }
1044
1049 final protected function setDatabaseProvider( IConnectionProvider $databaseProvider ) {
1050 $this->databaseProvider = $databaseProvider;
1051 }
1052
1057 final protected function getDatabaseProvider(): IConnectionProvider {
1058 if ( $this->databaseProvider === null ) {
1059 $this->databaseProvider = MediaWikiServices::getInstance()->getConnectionProvider();
1060 }
1061 return $this->databaseProvider;
1062 }
1063}
1064
1066class_alias( QueryPage::class, 'QueryPage' );
wfTimestamp( $outputtype=TS::UNIX, $ts=0)
Get a timestamp string in one of various formats.
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:69
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
This class is a collection of static functions that serve two purposes:
Definition Html.php:44
Factory creating MWHttpRequest objects.
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
This is one of the Core classes and should be read at least once by any new developers.
Factory for LinkBatch objects to batch query page metadata.
The base class for all skins.
Definition Skin.php:53
This is a class for doing query pages; since they're almost all the same, we factor out some of the f...
Definition QueryPage.php:77
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.
usesExternalSource()
Check if this query page is configured to fetch data from an external source via HTTP.
bool $shownavigation
Whether to show prev/next links.
bool $listoutput
Whether or not we want plain listoutput rather than an ordered list.
Definition QueryPage.php:79
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.
Definition QueryPage.php:82
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
Definition QueryPage.php:99
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...
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.
Definition QueryPage.php:94
recache( $limit, $unused=true)
Clear the cache and save new results.
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...
reallyDoQuery( $limit, $offset=false)
Run the query and return the result.
Parent class for all special pages.
Implements Special:Ancientpages.
List of redirects to non-existent pages.
List of pages that contain no links to other pages.
List of redirects to another redirecting page.
List articles with the fewest revisions.
Special:LinkSearch to search the external-links table.
List all files where the current version is a duplicate of the current version of another file.
Lists all the redirecting pages on the wiki.
List of articles with no article linking to them, thus being lonely.
Implements Special:Longpages.
Search the database for files of the requested MIME type, comparing this with the 'img_major_mime' an...
List of pages that have the highest category count.
List of the most used images.
List of pages that have the highest interwiki count.
List of categories with the most pages in them.
List of templates with a large number of transclusion links, i.e.
List of pages ordered by the number of pages linking to them.
Implements Special:Mostrevisions.
List of the shortest pages in the database.
List of file pages which haven't been categorised.
List of all uncategorised pages in the Template namespace.
List of pages that are not on anyone's watchlist.
List of the most linked non-existent files.
List of the most-linked pages that do not exist.
List of pages without any language links.
Database error base class.
Definition DBError.php:22
Overloads the relevant methods of the real ResultWrapper so it doesn't go anywhere near an actual dat...
Build SELECT queries with a fluent interface.
Interface for configuration instances.
Definition Config.php:18
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
Represents the target of a wiki link.
Provide primary and replica IDatabase connections.
Interface to a relational database.
Definition IDatabase.php:31
newDeleteQueryBuilder()
Get an DeleteQueryBuilder bound to this connection.
newInsertQueryBuilder()
Get an InsertQueryBuilder bound to this connection.
This class is a delegate to ILBFactory for a given database cluster.
A database connection without write operations.
Result wrapper for grabbing data queried from an IDatabase object.
numRows()
Get the number of rows in a result object.
seek( $pos)
Change the position of the cursor in a result object.
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by ConvertibleTimestamp to the format used for ins...