MediaWiki  master
QueryPage.php
Go to the documentation of this file.
1 <?php
29 
36 abstract class QueryPage extends SpecialPage {
38  protected $listoutput = false;
39 
41  protected $offset = 0;
42 
44  protected $limit = 0;
45 
53  protected $numRows;
54 
58  protected $cachedTimestamp = null;
59 
63  protected $shownavigation = true;
64 
74  public static function getPages() {
75  static $qp = null;
76 
77  if ( $qp === null ) {
78  // QueryPage subclass, Special page name
79  $qp = [
80  [ SpecialAncientPages::class, 'Ancientpages' ],
81  [ SpecialBrokenRedirects::class, 'BrokenRedirects' ],
82  [ SpecialDeadendPages::class, 'Deadendpages' ],
83  [ SpecialDoubleRedirects::class, 'DoubleRedirects' ],
84  [ SpecialFileDuplicateSearch::class, 'FileDuplicateSearch' ],
85  [ SpecialListDuplicatedFiles::class, 'ListDuplicatedFiles' ],
86  [ SpecialLinkSearch::class, 'LinkSearch' ],
87  [ SpecialListRedirects::class, 'Listredirects' ],
88  [ SpecialLonelyPages::class, 'Lonelypages' ],
89  [ SpecialLongPages::class, 'Longpages' ],
90  [ SpecialMediaStatistics::class, 'MediaStatistics' ],
91  [ SpecialMIMESearch::class, 'MIMEsearch' ],
92  [ SpecialMostCategories::class, 'Mostcategories' ],
93  [ MostimagesPage::class, 'Mostimages' ],
94  [ SpecialMostInterwikis::class, 'Mostinterwikis' ],
95  [ SpecialMostLinkedCategories::class, 'Mostlinkedcategories' ],
96  [ SpecialMostLinkedTemplates::class, 'Mostlinkedtemplates' ],
97  [ SpecialMostLinked::class, 'Mostlinked' ],
98  [ SpecialMostRevisions::class, 'Mostrevisions' ],
99  [ SpecialFewestRevisions::class, 'Fewestrevisions' ],
100  [ SpecialShortPages::class, 'Shortpages' ],
101  [ SpecialUncategorizedCategories::class, 'Uncategorizedcategories' ],
102  [ SpecialUncategorizedPages::class, 'Uncategorizedpages' ],
103  [ SpecialUncategorizedImages::class, 'Uncategorizedimages' ],
104  [ SpecialUncategorizedTemplates::class, 'Uncategorizedtemplates' ],
105  [ SpecialUnusedCategories::class, 'Unusedcategories' ],
106  [ SpecialUnusedImages::class, 'Unusedimages' ],
107  [ SpecialWantedCategories::class, 'Wantedcategories' ],
108  [ WantedFilesPage::class, 'Wantedfiles' ],
109  [ WantedPagesPage::class, 'Wantedpages' ],
110  [ SpecialWantedTemplates::class, 'Wantedtemplates' ],
111  [ SpecialUnwatchedPages::class, 'Unwatchedpages' ],
112  [ SpecialUnusedTemplates::class, 'Unusedtemplates' ],
113  [ SpecialWithoutInterwiki::class, 'Withoutinterwiki' ],
114  ];
115  Hooks::run( 'wgQueryPages', [ &$qp ] );
116  }
117 
118  return $qp;
119  }
120 
126  public static function getDisabledQueryPages( Config $config ) {
127  $disableQueryPageUpdate = $config->get( 'DisableQueryPageUpdate' );
128 
129  if ( !is_array( $disableQueryPageUpdate ) ) {
130  return [];
131  }
132 
133  $pages = [];
134  foreach ( $disableQueryPageUpdate as $name => $runMode ) {
135  if ( is_int( $name ) ) {
136  // The run mode may be omitted
137  $pages[$runMode] = 'disabled';
138  } else {
139  $pages[$name] = $runMode;
140  }
141  }
142  return $pages;
143  }
144 
150  function setListoutput( $bool ) {
151  $this->listoutput = $bool;
152  }
153 
180  public function getQueryInfo() {
181  return null;
182  }
183 
190  function getSQL() {
191  /* Implement getQueryInfo() instead */
192  throw new MWException( "Bug in a QueryPage: doesn't implement getQueryInfo() nor "
193  . "getQuery() properly" );
194  }
195 
203  function getOrderFields() {
204  return [ 'value' ];
205  }
206 
217  public function usesTimestamps() {
218  return false;
219  }
220 
226  function sortDescending() {
227  return true;
228  }
229 
237  public function isExpensive() {
238  return $this->getConfig()->get( 'DisableQueryPages' );
239  }
240 
248  public function isCacheable() {
249  return true;
250  }
251 
258  public function isCached() {
259  return $this->isExpensive() && $this->getConfig()->get( 'MiserMode' );
260  }
261 
267  function isSyndicated() {
268  return true;
269  }
270 
280  abstract function formatResult( $skin, $result );
281 
287  function getPageHeader() {
288  return '';
289  }
290 
298  protected function showEmptyText() {
299  $this->getOutput()->addWikiMsg( 'specialpage-empty' );
300  }
301 
309  function linkParameters() {
310  return [];
311  }
312 
321  public function recache( $limit, $ignoreErrors = true ) {
322  if ( !$this->isCacheable() ) {
323  return 0;
324  }
325 
326  $fname = static::class . '::recache';
327  $dbw = wfGetDB( DB_MASTER );
328  if ( !$dbw ) {
329  return false;
330  }
331 
332  try {
333  # Do query
334  $res = $this->reallyDoQuery( $limit, false );
335  $num = false;
336  if ( $res ) {
337  $num = $res->numRows();
338  # Fetch results
339  $vals = [];
340  foreach ( $res as $i => $row ) {
341  if ( isset( $row->value ) ) {
342  if ( $this->usesTimestamps() ) {
343  $value = wfTimestamp( TS_UNIX,
344  $row->value );
345  } else {
346  $value = intval( $row->value ); // T16414
347  }
348  } else {
349  $value = $i;
350  }
351 
352  $vals[] = [
353  'qc_type' => $this->getName(),
354  'qc_namespace' => $row->namespace,
355  'qc_title' => $row->title,
356  'qc_value' => $value
357  ];
358  }
359 
360  $dbw->doAtomicSection(
361  __METHOD__,
362  function ( IDatabase $dbw, $fname ) use ( $vals ) {
363  # Clear out any old cached data
364  $dbw->delete( 'querycache',
365  [ 'qc_type' => $this->getName() ],
366  $fname
367  );
368  # Save results into the querycache table on the master
369  if ( count( $vals ) ) {
370  $dbw->insert( 'querycache', $vals, $fname );
371  }
372  # Update the querycache_info record for the page
373  $dbw->delete( 'querycache_info',
374  [ 'qci_type' => $this->getName() ],
375  $fname
376  );
377  $dbw->insert( 'querycache_info',
378  [ 'qci_type' => $this->getName(),
379  'qci_timestamp' => $dbw->timestamp() ],
380  $fname
381  );
382  }
383  );
384  }
385  } catch ( DBError $e ) {
386  if ( !$ignoreErrors ) {
387  throw $e; // report query error
388  }
389  $num = false; // set result to false to indicate error
390  }
391 
392  return $num;
393  }
394 
399  function getRecacheDB() {
400  return wfGetDB( DB_REPLICA, [ $this->getName(), 'QueryPage::recache', 'vslow' ] );
401  }
402 
409  public function delete( LinkTarget $title ) {
410  if ( $this->isCached() ) {
411  $dbw = wfGetDB( DB_MASTER );
412  $dbw->delete( 'querycache', [
413  'qc_type' => $this->getName(),
414  'qc_namespace' => $title->getNamespace(),
415  'qc_title' => $title->getDBkey(),
416  ], __METHOD__ );
417  }
418  }
419 
427  public function reallyDoQuery( $limit, $offset = false ) {
428  $fname = static::class . '::reallyDoQuery';
429  $dbr = $this->getRecacheDB();
430  $query = $this->getQueryInfo();
431  $order = $this->getOrderFields();
432 
433  if ( $this->sortDescending() ) {
434  foreach ( $order as &$field ) {
435  $field .= ' DESC';
436  }
437  }
438 
439  if ( is_array( $query ) ) {
440  $tables = isset( $query['tables'] ) ? (array)$query['tables'] : [];
441  $fields = isset( $query['fields'] ) ? (array)$query['fields'] : [];
442  $conds = isset( $query['conds'] ) ? (array)$query['conds'] : [];
443  $options = isset( $query['options'] ) ? (array)$query['options'] : [];
444  $join_conds = isset( $query['join_conds'] ) ? (array)$query['join_conds'] : [];
445 
446  if ( $order ) {
447  $options['ORDER BY'] = $order;
448  }
449 
450  if ( $limit !== false ) {
451  $options['LIMIT'] = intval( $limit );
452  }
453 
454  if ( $offset !== false ) {
455  $options['OFFSET'] = intval( $offset );
456  }
457 
458  $res = $dbr->select( $tables, $fields, $conds, $fname,
459  $options, $join_conds
460  );
461  } else {
462  // Old-fashioned raw SQL style, deprecated
463  $sql = $this->getSQL();
464  $sql .= ' ORDER BY ' . implode( ', ', $order );
465  $sql = $dbr->limitResult( $sql, $limit, $offset );
466  $res = $dbr->query( $sql, $fname );
467  }
468 
469  return $res;
470  }
471 
478  public function doQuery( $offset = false, $limit = false ) {
479  if ( $this->isCached() && $this->isCacheable() ) {
480  return $this->fetchFromCache( $limit, $offset );
481  } else {
482  return $this->reallyDoQuery( $limit, $offset );
483  }
484  }
485 
493  public function fetchFromCache( $limit, $offset = false ) {
494  $dbr = wfGetDB( DB_REPLICA );
495  $options = [];
496 
497  if ( $limit !== false ) {
498  $options['LIMIT'] = intval( $limit );
499  }
500 
501  if ( $offset !== false ) {
502  $options['OFFSET'] = intval( $offset );
503  }
504 
505  $order = $this->getCacheOrderFields();
506  if ( $this->sortDescending() ) {
507  foreach ( $order as &$field ) {
508  $field .= " DESC";
509  }
510  }
511  if ( $order ) {
512  $options['ORDER BY'] = $order;
513  }
514 
515  return $dbr->select( 'querycache',
516  [ 'qc_type',
517  'namespace' => 'qc_namespace',
518  'title' => 'qc_title',
519  'value' => 'qc_value' ],
520  [ 'qc_type' => $this->getName() ],
521  __METHOD__,
522  $options
523  );
524  }
525 
532  function getCacheOrderFields() {
533  return [ 'value' ];
534  }
535 
539  public function getCachedTimestamp() {
540  if ( is_null( $this->cachedTimestamp ) ) {
541  $dbr = wfGetDB( DB_REPLICA );
542  $fname = static::class . '::getCachedTimestamp';
543  $this->cachedTimestamp = $dbr->selectField( 'querycache_info', 'qci_timestamp',
544  [ 'qci_type' => $this->getName() ], $fname );
545  }
546  return $this->cachedTimestamp;
547  }
548 
561  protected function getLimitOffset() {
562  list( $limit, $offset ) = $this->getRequest()->getLimitOffset();
563  if ( $this->getConfig()->get( 'MiserMode' ) ) {
564  $maxResults = $this->getMaxResults();
565  // Can't display more than max results on a page
566  $limit = min( $limit, $maxResults );
567  // Can't skip over more than the end of $maxResults
568  $offset = min( $offset, $maxResults + 1 );
569  }
570  return [ $limit, $offset ];
571  }
572 
581  protected function getDBLimit( $uiLimit, $uiOffset ) {
582  $maxResults = $this->getMaxResults();
583  if ( $this->getConfig()->get( 'MiserMode' ) ) {
584  $limit = min( $uiLimit + 1, $maxResults - $uiOffset );
585  return max( $limit, 0 );
586  } else {
587  return $uiLimit + 1;
588  }
589  }
590 
600  protected function getMaxResults() {
601  // Max of 10000, unless we store more than 10000 in query cache.
602  return max( $this->getConfig()->get( 'QueryCacheLimit' ), 10000 );
603  }
604 
610  public function execute( $par ) {
611  $user = $this->getUser();
612  if ( !$this->userCanExecute( $user ) ) {
613  $this->displayRestrictionError();
614  return;
615  }
616 
617  $this->setHeaders();
618  $this->outputHeader();
619 
620  $out = $this->getOutput();
621 
622  if ( $this->isCached() && !$this->isCacheable() ) {
623  $out->addWikiMsg( 'querypage-disabled' );
624  return;
625  }
626 
627  $out->setSyndicated( $this->isSyndicated() );
628 
629  if ( $this->limit == 0 && $this->offset == 0 ) {
630  list( $this->limit, $this->offset ) = $this->getLimitOffset();
631  }
632  $dbLimit = $this->getDBLimit( $this->limit, $this->offset );
633  // @todo Use doQuery()
634  if ( !$this->isCached() ) {
635  # select one extra row for navigation
636  $res = $this->reallyDoQuery( $dbLimit, $this->offset );
637  } else {
638  # Get the cached result, select one extra row for navigation
639  $res = $this->fetchFromCache( $dbLimit, $this->offset );
640  if ( !$this->listoutput ) {
641  # Fetch the timestamp of this update
642  $ts = $this->getCachedTimestamp();
643  $lang = $this->getLanguage();
644  $maxResults = $lang->formatNum( $this->getConfig()->get( 'QueryCacheLimit' ) );
645 
646  if ( $ts ) {
647  $updated = $lang->userTimeAndDate( $ts, $user );
648  $updateddate = $lang->userDate( $ts, $user );
649  $updatedtime = $lang->userTime( $ts, $user );
650  $out->addMeta( 'Data-Cache-Time', $ts );
651  $out->addJsConfigVars( 'dataCacheTime', $ts );
652  $out->addWikiMsg( 'perfcachedts', $updated, $updateddate, $updatedtime, $maxResults );
653  } else {
654  $out->addWikiMsg( 'perfcached', $maxResults );
655  }
656 
657  # If updates on this page have been disabled, let the user know
658  # that the data set won't be refreshed for now
659  $disabledQueryPages = self::getDisabledQueryPages( $this->getConfig() );
660  if ( isset( $disabledQueryPages[$this->getName()] ) ) {
661  $runMode = $disabledQueryPages[$this->getName()];
662  if ( $runMode === 'disabled' ) {
663  $out->wrapWikiMsg(
664  "<div class=\"mw-querypage-no-updates\">\n$1\n</div>",
665  'querypage-no-updates'
666  );
667  } else {
668  // Messages used here: querypage-updates-periodical
669  $out->wrapWikiMsg(
670  "<div class=\"mw-querypage-updates-" . $runMode . "\">\n$1\n</div>",
671  'querypage-updates-' . $runMode
672  );
673  }
674  }
675  }
676  }
677 
678  $this->numRows = $res->numRows();
679 
680  $dbr = $this->getRecacheDB();
681  $this->preprocessResults( $dbr, $res );
682 
683  $out->addHTML( Xml::openElement( 'div', [ 'class' => 'mw-spcontent' ] ) );
684 
685  # Top header and navigation
686  if ( $this->shownavigation ) {
687  $out->addHTML( $this->getPageHeader() );
688  if ( $this->numRows > 0 ) {
689  $out->addHTML( $this->msg( 'showingresultsinrange' )->numParams(
690  min( $this->numRows, $this->limit ), # do not show the one extra row, if exist
691  $this->offset + 1, ( min( $this->numRows, $this->limit ) + $this->offset ) )->parseAsBlock() );
692  # Disable the "next" link when we reach the end
693  $miserMaxResults = $this->getConfig()->get( 'MiserMode' )
694  && ( $this->offset + $this->limit >= $this->getMaxResults() );
695  $atEnd = ( $this->numRows <= $this->limit ) || $miserMaxResults;
696  $paging = $this->buildPrevNextNavigation( $this->offset,
697  $this->limit, $this->linkParameters(), $atEnd, $par );
698  $out->addHTML( '<p>' . $paging . '</p>' );
699  } else {
700  # No results to show, so don't bother with "showing X of Y" etc.
701  # -- just let the user know and give up now
702  $this->showEmptyText();
703  $out->addHTML( Xml::closeElement( 'div' ) );
704  return;
705  }
706  }
707 
708  # The actual results; specialist subclasses will want to handle this
709  # with more than a straight list, so we hand them the info, plus
710  # an OutputPage, and let them get on with it
711  $this->outputResults( $out,
712  $this->getSkin(),
713  $dbr, # Should use IResultWrapper for this
714  $res,
715  min( $this->numRows, $this->limit ), # do not format the one extra row, if exist
716  $this->offset );
717 
718  # Repeat the paging links at the bottom
719  if ( $this->shownavigation ) {
720  $out->addHTML( '<p>' . $paging . '</p>' );
721  }
722 
723  $out->addHTML( Xml::closeElement( 'div' ) );
724  }
725 
737  protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) {
738  if ( $num > 0 ) {
739  $html = [];
740  if ( !$this->listoutput ) {
741  $html[] = $this->openList( $offset );
742  }
743 
744  # $res might contain the whole 1,000 rows, so we read up to
745  # $num [should update this to use a Pager]
746  for ( $i = 0; $i < $num && $row = $res->fetchObject(); $i++ ) {
747  $line = $this->formatResult( $skin, $row );
748  if ( $line ) {
749  $html[] = $this->listoutput
750  ? $line
751  : "<li>{$line}</li>\n";
752  }
753  }
754 
755  if ( !$this->listoutput ) {
756  $html[] = $this->closeList();
757  }
758 
759  $html = $this->listoutput
760  ? MediaWikiServices::getInstance()->getContentLanguage()->listToText( $html )
761  : implode( '', $html );
762 
763  $out->addHTML( $html );
764  }
765  }
766 
771  function openList( $offset ) {
772  return "\n<ol start='" . ( $offset + 1 ) . "' class='special'>\n";
773  }
774 
778  function closeList() {
779  return "</ol>\n";
780  }
781 
787  function preprocessResults( $db, $res ) {
788  }
789 
800  protected function executeLBFromResultWrapper( IResultWrapper $res, $ns = null ) {
801  if ( !$res->numRows() ) {
802  return;
803  }
804 
805  $batch = new LinkBatch;
806  foreach ( $res as $row ) {
807  $batch->add( $ns ?? $row->namespace, $row->title );
808  }
809  $batch->execute();
810 
811  $res->seek( 0 );
812  }
813 }
add( $ns, $dbkey)
Definition: LinkBatch.php:83
execute( $par)
This is the actual workhorse.
Definition: QueryPage.php:610
outputResults( $out, $skin, $dbr, $res, $num, $offset)
Format and output report results using the given information plus OutputPage.
Definition: QueryPage.php:737
bool $shownavigation
Whether to show prev/next links.
Definition: QueryPage.php:63
doQuery( $offset=false, $limit=false)
Somewhat deprecated, you probably want to be using execute()
Definition: QueryPage.php:478
if(!isset( $args[0])) $lang
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
getOrderFields()
Subclasses return an array of fields to order by here.
Definition: QueryPage.php:203
getMaxResults()
Get max number of results we can return in miser mode.
Definition: QueryPage.php:600
isCached()
Whether or not the output of the page in question is retrieved from the database cache.
Definition: QueryPage.php:258
fetchFromCache( $limit, $offset=false)
Fetch the query results from the query cache.
Definition: QueryPage.php:493
getOutput()
Get the OutputPage being used for this instance.
const DB_MASTER
Definition: defines.php:26
get( $name)
Get a configuration variable such as "Sitename" or "UploadMaintenance.".
timestamp( $ts=0)
Convert a timestamp in one of the formats accepted by ConvertibleTimestamp to the format used for ins...
showEmptyText()
Outputs some kind of an informative message (via OutputPage) to let the user know that the query retu...
Definition: QueryPage.php:298
getLimitOffset()
Returns limit and offset, as returned by $this->getRequest()->getLimitOffset().
Definition: QueryPage.php:561
This is a class for doing query pages; since they&#39;re almost all the same, we factor out some of the f...
Definition: QueryPage.php:36
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition: LinkBatch.php:34
openList( $offset)
Definition: QueryPage.php:771
getRecacheDB()
Get a DB connection to be used for slow recache queries.
Definition: QueryPage.php:399
buildPrevNextNavigation( $offset, $limit, array $query=[], $atend=false, $subpage=false)
Generate (prev x| next x) (20|50|100...) type links for paging.
linkParameters()
If using extra form wheely-dealies, return a set of parameters here as an associative array...
Definition: QueryPage.php:309
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
msg( $key,... $params)
Wrapper around wfMessage that sets the current context.
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:108
Interface for configuration instances.
Definition: Config.php:28
displayRestrictionError()
Output an error message telling the user what access level they have to have.
preprocessResults( $db, $res)
Do any necessary preprocessing of the result object.
Definition: QueryPage.php:787
reallyDoQuery( $limit, $offset=false)
Run the query and return the result.
Definition: QueryPage.php:427
getQueryInfo()
Subclasses return an SQL query here, formatted as an array with the following keys: tables => Table(s...
Definition: QueryPage.php:180
getDBLimit( $uiLimit, $uiOffset)
What is limit to fetch from DB.
Definition: QueryPage.php:581
sortDescending()
Override to sort by increasing values.
Definition: QueryPage.php:226
Result wrapper for grabbing data queried from an IDatabase object.
getSkin()
Shortcut to get the skin being used for this instance.
int $offset
The offset and limit in use, as passed to the query() function.
Definition: QueryPage.php:41
setHeaders()
Sets headers - this should be called from the execute() method of all derived classes! ...
getPageHeader()
The content returned by this function will be output before any result.
Definition: QueryPage.php:287
numRows()
Get the number of rows in a result object.
int $numRows
The number of rows returned by the query.
Definition: QueryPage.php:53
seek( $pos)
Change the position of the cursor in a result object.
static closeElement( $element)
Shortcut to close an XML element.
Definition: Xml.php:117
getName()
Get the name of this Special Page.
static getDisabledQueryPages(Config $config)
Get a list of query pages disabled and with it&#39;s run mode.
Definition: QueryPage.php:126
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
getCacheOrderFields()
Return the order fields for fetchFromCache.
Definition: QueryPage.php:532
setListoutput( $bool)
A mutator for $this->listoutput;.
Definition: QueryPage.php:150
getUser()
Shortcut to get the User executing this instance.
insert( $table, $rows, $fname=__METHOD__, $options=[])
INSERT wrapper, inserts an array into a table.
$line
Definition: cdb.php:59
bool $listoutput
Whether or not we want plain listoutput rather than an ordered list.
Definition: QueryPage.php:38
getConfig()
Shortcut to get main config object.
int $limit
Definition: QueryPage.php:44
getLanguage()
Shortcut to get user&#39;s language.
userCanExecute(User $user)
Checks if the given user (identified by an object) can execute this special page (as defined by $mRes...
usesTimestamps()
Does this query return timestamps rather than integers in its &#39;value&#39; field? If true, this class will convert &#39;value&#39; to a UNIX timestamp for caching.
Definition: QueryPage.php:217
getCachedTimestamp()
Definition: QueryPage.php:539
const DB_REPLICA
Definition: defines.php:25
executeLBFromResultWrapper(IResultWrapper $res, $ns=null)
Creates a new LinkBatch object, adds all pages from the passed result wrapper (MUST include title and...
Definition: QueryPage.php:800
getRequest()
Get the WebRequest being used for this instance.
getSQL()
For back-compat, subclasses may return a raw SQL query here, as a string.
Definition: QueryPage.php:190
isExpensive()
Is this query expensive (for some definition of expensive)? Then we don&#39;t let it run in miser mode...
Definition: QueryPage.php:237
isCacheable()
Is the output of this query cacheable? Non-cacheable expensive pages will be disabled in miser mode a...
Definition: QueryPage.php:248
delete( $table, $conds, $fname=__METHOD__)
DELETE query wrapper.
string null $cachedTimestamp
Definition: QueryPage.php:58
formatResult( $skin, $result)
Formats the results of the query for display.
outputHeader( $summaryMessageKey='')
Outputs a summary message on top of special pages Per default the message key is the canonical name o...
recache( $limit, $ignoreErrors=true)
Clear the cache and save new results.
Definition: QueryPage.php:321
Database error base class.
Definition: DBError.php:30
isSyndicated()
Sometime we don&#39;t want to build rss / atom feeds.
Definition: QueryPage.php:267
static getPages()
Get a list of query page classes and their associated special pages, for periodic updates...
Definition: QueryPage.php:74
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200