MediaWiki  REL1_20
QueryPage.php
Go to the documentation of this file.
00001 <?php
00031 global $wgQueryPages; // not redundant
00032 $wgQueryPages = array(
00033 //         QueryPage subclass           Special page name         Limit (false for none, none for the default)
00034 // ----------------------------------------------------------------------------
00035         array( 'AncientPagesPage',              'Ancientpages'                  ),
00036         array( 'BrokenRedirectsPage',           'BrokenRedirects'               ),
00037         array( 'DeadendPagesPage',              'Deadendpages'                  ),
00038         array( 'DisambiguationsPage',           'Disambiguations'               ),
00039         array( 'DoubleRedirectsPage',           'DoubleRedirects'               ),
00040         array( 'FileDuplicateSearchPage',       'FileDuplicateSearch'           ),
00041         array( 'LinkSearchPage',                'LinkSearch'                    ),
00042         array( 'ListredirectsPage',             'Listredirects'                 ),
00043         array( 'LonelyPagesPage',               'Lonelypages'                   ),
00044         array( 'LongPagesPage',                 'Longpages'                     ),
00045         array( 'MIMEsearchPage',                'MIMEsearch'                    ),
00046         array( 'MostcategoriesPage',            'Mostcategories'                ),
00047         array( 'MostimagesPage',                'Mostimages'                    ),
00048         array( 'MostinterwikisPage',            'Mostinterwikis'                ),
00049         array( 'MostlinkedCategoriesPage',      'Mostlinkedcategories'          ),
00050         array( 'MostlinkedtemplatesPage',       'Mostlinkedtemplates'           ),
00051         array( 'MostlinkedPage',                'Mostlinked'                    ),
00052         array( 'MostrevisionsPage',             'Mostrevisions'                 ),
00053         array( 'FewestrevisionsPage',           'Fewestrevisions'               ),
00054         array( 'ShortPagesPage',                'Shortpages'                    ),
00055         array( 'UncategorizedCategoriesPage',   'Uncategorizedcategories'       ),
00056         array( 'UncategorizedPagesPage',        'Uncategorizedpages'            ),
00057         array( 'UncategorizedImagesPage',       'Uncategorizedimages'           ),
00058         array( 'UncategorizedTemplatesPage',    'Uncategorizedtemplates'        ),
00059         array( 'UnusedCategoriesPage',          'Unusedcategories'              ),
00060         array( 'UnusedimagesPage',              'Unusedimages'                  ),
00061         array( 'WantedCategoriesPage',          'Wantedcategories'              ),
00062         array( 'WantedFilesPage',               'Wantedfiles'                   ),
00063         array( 'WantedPagesPage',               'Wantedpages'                   ),
00064         array( 'WantedTemplatesPage',           'Wantedtemplates'               ),
00065         array( 'UnwatchedPagesPage',            'Unwatchedpages'                ),
00066         array( 'UnusedtemplatesPage',           'Unusedtemplates'               ),
00067         array( 'WithoutInterwikiPage',          'Withoutinterwiki'              ),
00068 );
00069 wfRunHooks( 'wgQueryPages', array( &$wgQueryPages ) );
00070 
00071 global $wgDisableCounters;
00072 if ( !$wgDisableCounters )
00073         $wgQueryPages[] = array( 'PopularPagesPage', 'Popularpages' );
00074 
00075 
00082 abstract class QueryPage extends SpecialPage {
00088         var $listoutput = false;
00089 
00095         var $offset = 0;
00096         var $limit = 0;
00097 
00103         protected $numRows;
00104 
00105         protected $cachedTimestamp = null;
00106 
00110         protected $shownavigation = true;
00111 
00117         function setListoutput( $bool ) {
00118                 $this->listoutput = $bool;
00119         }
00120 
00147         function getQueryInfo() {
00148                 return null;
00149         }
00150 
00156         function getSQL() {
00157                 /* Implement getQueryInfo() instead */
00158                 throw new MWException( "Bug in a QueryPage: doesn't implement getQueryInfo() nor getQuery() properly" );
00159         }
00160 
00168         function getOrderFields() {
00169                 return array( 'value' );
00170         }
00171 
00182         function usesTimestamps() {
00183                 return false;
00184         }
00185 
00191         function sortDescending() {
00192                 return true;
00193         }
00194 
00202         function isExpensive() {
00203                 global $wgDisableQueryPages;
00204                 return $wgDisableQueryPages;
00205         }
00206 
00214         public function isCacheable() {
00215                 return true;
00216         }
00217 
00224         function isCached() {
00225                 global $wgMiserMode;
00226 
00227                 return $this->isExpensive() && $wgMiserMode;
00228         }
00229 
00235         function isSyndicated() {
00236                 return true;
00237         }
00238 
00251         abstract function formatResult( $skin, $result );
00252 
00258         function getPageHeader() {
00259                 return '';
00260         }
00261 
00269         function linkParameters() {
00270                 return array();
00271         }
00272 
00281         function tryLastResult() {
00282                 return false;
00283         }
00284 
00292         function recache( $limit, $ignoreErrors = true ) {
00293                 if ( !$this->isCacheable() ) {
00294                         return 0;
00295                 }
00296 
00297                 $fname = get_class( $this ) . '::recache';
00298                 $dbw = wfGetDB( DB_MASTER );
00299                 $dbr = wfGetDB( DB_SLAVE, array( $this->getName(), __METHOD__, 'vslow' ) );
00300                 if ( !$dbw || !$dbr ) {
00301                         return false;
00302                 }
00303 
00304                 if ( $ignoreErrors ) {
00305                         $ignoreW = $dbw->ignoreErrors( true );
00306                         $ignoreR = $dbr->ignoreErrors( true );
00307                 }
00308 
00309                 # Clear out any old cached data
00310                 $dbw->delete( 'querycache', array( 'qc_type' => $this->getName() ), $fname );
00311                 # Do query
00312                 $res = $this->reallyDoQuery( $limit, false );
00313                 $num = false;
00314                 if ( $res ) {
00315                         $num = $res->numRows();
00316                         # Fetch results
00317                         $vals = array();
00318                         while ( $res && $row = $dbr->fetchObject( $res ) ) {
00319                                 if ( isset( $row->value ) ) {
00320                                         if ( $this->usesTimestamps() ) {
00321                                                 $value = wfTimestamp( TS_UNIX,
00322                                                         $row->value );
00323                                         } else {
00324                                                 $value = intval( $row->value ); // @bug 14414
00325                                         }
00326                                 } else {
00327                                         $value = 0;
00328                                 }
00329 
00330                                 $vals[] = array( 'qc_type' => $this->getName(),
00331                                                 'qc_namespace' => $row->namespace,
00332                                                 'qc_title' => $row->title,
00333                                                 'qc_value' => $value );
00334                         }
00335 
00336                         # Save results into the querycache table on the master
00337                         if ( count( $vals ) ) {
00338                                 if ( !$dbw->insert( 'querycache', $vals, __METHOD__ ) ) {
00339                                         // Set result to false to indicate error
00340                                         $num = false;
00341                                 }
00342                         }
00343                         if ( $ignoreErrors ) {
00344                                 $dbw->ignoreErrors( $ignoreW );
00345                                 $dbr->ignoreErrors( $ignoreR );
00346                         }
00347 
00348                         # Update the querycache_info record for the page
00349                         $dbw->delete( 'querycache_info', array( 'qci_type' => $this->getName() ), $fname );
00350                         $dbw->insert( 'querycache_info', array( 'qci_type' => $this->getName(), 'qci_timestamp' => $dbw->timestamp() ), $fname );
00351 
00352                 }
00353                 return $num;
00354         }
00355 
00363         function reallyDoQuery( $limit, $offset = false ) {
00364                 $fname = get_class( $this ) . "::reallyDoQuery";
00365                 $dbr = wfGetDB( DB_SLAVE );
00366                 $query = $this->getQueryInfo();
00367                 $order = $this->getOrderFields();
00368                 if ( $this->sortDescending() ) {
00369                         foreach ( $order as &$field ) {
00370                                 $field .= ' DESC';
00371                         }
00372                 }
00373                 if ( is_array( $query ) ) {
00374                         $tables = isset( $query['tables'] ) ? (array)$query['tables'] : array();
00375                         $fields = isset( $query['fields'] ) ? (array)$query['fields'] : array();
00376                         $conds = isset( $query['conds'] ) ? (array)$query['conds'] : array();
00377                         $options = isset( $query['options'] ) ? (array)$query['options'] : array();
00378                         $join_conds = isset( $query['join_conds'] ) ? (array)$query['join_conds'] : array();
00379                         if ( count( $order ) ) {
00380                                 $options['ORDER BY'] = $order;
00381                         }
00382                         if ( $limit !== false ) {
00383                                 $options['LIMIT'] = intval( $limit );
00384                         }
00385                         if ( $offset !== false ) {
00386                                 $options['OFFSET'] = intval( $offset );
00387                         }
00388 
00389                         $res = $dbr->select( $tables, $fields, $conds, $fname,
00390                                         $options, $join_conds
00391                         );
00392                 } else {
00393                         // Old-fashioned raw SQL style, deprecated
00394                         $sql = $this->getSQL();
00395                         $sql .= ' ORDER BY ' . implode( ', ', $order );
00396                         $sql = $dbr->limitResult( $sql, $limit, $offset );
00397                         $res = $dbr->query( $sql, $fname );
00398                 }
00399                 return $dbr->resultObject( $res );
00400         }
00401 
00406         function doQuery( $offset = false, $limit = false ) {
00407                 if ( $this->isCached() && $this->isCacheable() ) {
00408                         return $this->fetchFromCache( $limit, $offset );
00409                 } else {
00410                         return $this->reallyDoQuery( $limit, $offset );
00411                 }
00412         }
00413 
00421         function fetchFromCache( $limit, $offset = false ) {
00422                 $dbr = wfGetDB( DB_SLAVE );
00423                 $options = array ();
00424                 if ( $limit !== false ) {
00425                         $options['LIMIT'] = intval( $limit );
00426                 }
00427                 if ( $offset !== false ) {
00428                         $options['OFFSET'] = intval( $offset );
00429                 }
00430                 if ( $this->sortDescending() ) {
00431                         $options['ORDER BY'] = 'qc_value DESC';
00432                 } else {
00433                         $options['ORDER BY'] = 'qc_value ASC';
00434                 }
00435                 $res = $dbr->select( 'querycache', array( 'qc_type',
00436                                 'namespace' => 'qc_namespace',
00437                                 'title' => 'qc_title',
00438                                 'value' => 'qc_value' ),
00439                                 array( 'qc_type' => $this->getName() ),
00440                                 __METHOD__, $options
00441                 );
00442                 return $dbr->resultObject( $res );
00443         }
00444 
00445         public function getCachedTimestamp() {
00446                 if ( is_null( $this->cachedTimestamp ) ) {
00447                         $dbr = wfGetDB( DB_SLAVE );
00448                         $fname = get_class( $this ) . '::getCachedTimestamp';
00449                         $this->cachedTimestamp = $dbr->selectField( 'querycache_info', 'qci_timestamp',
00450                                 array( 'qci_type' => $this->getName() ), $fname );
00451                 }
00452                 return $this->cachedTimestamp;
00453         }
00454 
00460         function execute( $par ) {
00461                 global $wgQueryCacheLimit, $wgDisableQueryPageUpdate;
00462 
00463                 $user = $this->getUser();
00464                 if ( !$this->userCanExecute( $user ) ) {
00465                         $this->displayRestrictionError();
00466                         return;
00467                 }
00468 
00469                 $this->setHeaders();
00470                 $this->outputHeader();
00471 
00472                 $out = $this->getOutput();
00473 
00474                 if ( $this->isCached() && !$this->isCacheable() ) {
00475                         $out->addWikiMsg( 'querypage-disabled' );
00476                         return 0;
00477                 }
00478 
00479                 $out->setSyndicated( $this->isSyndicated() );
00480 
00481                 if ( $this->limit == 0 && $this->offset == 0 ) {
00482                         list( $this->limit, $this->offset ) = $this->getRequest()->getLimitOffset();
00483                 }
00484 
00485                 // TODO: Use doQuery()
00486                 if ( !$this->isCached() ) {
00487                         # select one extra row for navigation
00488                         $res = $this->reallyDoQuery( $this->limit + 1, $this->offset );
00489                 } else {
00490                         # Get the cached result, select one extra row for navigation
00491                         $res = $this->fetchFromCache( $this->limit + 1, $this->offset );
00492                         if ( !$this->listoutput ) {
00493 
00494                                 # Fetch the timestamp of this update
00495                                 $ts = $this->getCachedTimestamp();
00496                                 $lang = $this->getLanguage();
00497                                 $maxResults = $lang->formatNum( $wgQueryCacheLimit );
00498 
00499                                 if ( $ts ) {
00500                                         $updated = $lang->userTimeAndDate( $ts, $user );
00501                                         $updateddate = $lang->userDate( $ts, $user );
00502                                         $updatedtime = $lang->userTime( $ts, $user );
00503                                         $out->addMeta( 'Data-Cache-Time', $ts );
00504                                         $out->addJsConfigVars( 'dataCacheTime', $ts );
00505                                         $out->addWikiMsg( 'perfcachedts', $updated, $updateddate, $updatedtime, $maxResults );
00506                                 } else {
00507                                         $out->addWikiMsg( 'perfcached', $maxResults );
00508                                 }
00509 
00510                                 # If updates on this page have been disabled, let the user know
00511                                 # that the data set won't be refreshed for now
00512                                 if ( is_array( $wgDisableQueryPageUpdate ) && in_array( $this->getName(), $wgDisableQueryPageUpdate ) ) {
00513                                         $out->wrapWikiMsg( "<div class=\"mw-querypage-no-updates\">\n$1\n</div>", 'querypage-no-updates' );
00514                                 }
00515                         }
00516                 }
00517 
00518                 $this->numRows = $res->numRows();
00519 
00520                 $dbr = wfGetDB( DB_SLAVE );
00521                 $this->preprocessResults( $dbr, $res );
00522 
00523                 $out->addHTML( Xml::openElement( 'div', array( 'class' => 'mw-spcontent' ) ) );
00524 
00525                 # Top header and navigation
00526                 if ( $this->shownavigation ) {
00527                         $out->addHTML( $this->getPageHeader() );
00528                         if ( $this->numRows > 0 ) {
00529                                 $out->addHTML( $this->msg( 'showingresults' )->numParams(
00530                                         min( $this->numRows, $this->limit ), # do not show the one extra row, if exist
00531                                         $this->offset + 1 )->parseAsBlock() );
00532                                 # Disable the "next" link when we reach the end
00533                                 $paging = $this->getLanguage()->viewPrevNext( $this->getTitle( $par ), $this->offset,
00534                                         $this->limit, $this->linkParameters(), ( $this->numRows <= $this->limit ) );
00535                                 $out->addHTML( '<p>' . $paging . '</p>' );
00536                         } else {
00537                                 # No results to show, so don't bother with "showing X of Y" etc.
00538                                 # -- just let the user know and give up now
00539                                 $out->addWikiMsg( 'specialpage-empty' );
00540                                 $out->addHTML( Xml::closeElement( 'div' ) );
00541                                 return;
00542                         }
00543                 }
00544 
00545                 # The actual results; specialist subclasses will want to handle this
00546                 # with more than a straight list, so we hand them the info, plus
00547                 # an OutputPage, and let them get on with it
00548                 $this->outputResults( $out,
00549                         $this->getSkin(),
00550                         $dbr, # Should use a ResultWrapper for this
00551                         $res,
00552                         min( $this->numRows, $this->limit ), # do not format the one extra row, if exist
00553                         $this->offset );
00554 
00555                 # Repeat the paging links at the bottom
00556                 if ( $this->shownavigation ) {
00557                         $out->addHTML( '<p>' . $paging . '</p>' );
00558                 }
00559 
00560                 $out->addHTML( Xml::closeElement( 'div' ) );
00561 
00562                 return min( $this->numRows, $this->limit ); # do not return the one extra row, if exist
00563         }
00564 
00576         protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) {
00577                 global $wgContLang;
00578 
00579                 if ( $num > 0 ) {
00580                         $html = array();
00581                         if ( !$this->listoutput ) {
00582                                 $html[] = $this->openList( $offset );
00583                         }
00584 
00585                         # $res might contain the whole 1,000 rows, so we read up to
00586                         # $num [should update this to use a Pager]
00587                         for ( $i = 0; $i < $num && $row = $dbr->fetchObject( $res ); $i++ ) {
00588                                 $line = $this->formatResult( $skin, $row );
00589                                 if ( $line ) {
00590                                         $attr = ( isset( $row->usepatrol ) && $row->usepatrol && $row->patrolled == 0 )
00591                                                 ? ' class="not-patrolled"'
00592                                                 : '';
00593                                         $html[] = $this->listoutput
00594                                                 ? $line
00595                                                 : "<li{$attr}>{$line}</li>\n";
00596                                 }
00597                         }
00598 
00599                         # Flush the final result
00600                         if ( $this->tryLastResult() ) {
00601                                 $row = null;
00602                                 $line = $this->formatResult( $skin, $row );
00603                                 if ( $line ) {
00604                                         $attr = ( isset( $row->usepatrol ) && $row->usepatrol && $row->patrolled == 0 )
00605                                                 ? ' class="not-patrolled"'
00606                                                 : '';
00607                                         $html[] = $this->listoutput
00608                                                 ? $line
00609                                                 : "<li{$attr}>{$line}</li>\n";
00610                                 }
00611                         }
00612 
00613                         if ( !$this->listoutput ) {
00614                                 $html[] = $this->closeList();
00615                         }
00616 
00617                         $html = $this->listoutput
00618                                 ? $wgContLang->listToText( $html )
00619                                 : implode( '', $html );
00620 
00621                         $out->addHTML( $html );
00622                 }
00623         }
00624 
00629         function openList( $offset ) {
00630                 return "\n<ol start='" . ( $offset + 1 ) . "' class='special'>\n";
00631         }
00632 
00636         function closeList() {
00637                 return "</ol>\n";
00638         }
00639 
00643         function preprocessResults( $db, $res ) {}
00644 
00649         function doFeed( $class = '', $limit = 50 ) {
00650                 global $wgFeed, $wgFeedClasses;
00651 
00652                 if ( !$wgFeed ) {
00653                         $this->getOutput()->addWikiMsg( 'feed-unavailable' );
00654                         return;
00655                 }
00656 
00657                 global $wgFeedLimit;
00658                 if ( $limit > $wgFeedLimit ) {
00659                         $limit = $wgFeedLimit;
00660                 }
00661 
00662                 if ( isset( $wgFeedClasses[$class] ) ) {
00663                         $feed = new $wgFeedClasses[$class](
00664                                 $this->feedTitle(),
00665                                 $this->feedDesc(),
00666                                 $this->feedUrl() );
00667                         $feed->outHeader();
00668 
00669                         $res = $this->reallyDoQuery( $limit, 0 );
00670                         foreach ( $res as $obj ) {
00671                                 $item = $this->feedResult( $obj );
00672                                 if ( $item ) {
00673                                         $feed->outItem( $item );
00674                                 }
00675                         }
00676 
00677                         $feed->outFooter();
00678                         return true;
00679                 } else {
00680                         return false;
00681                 }
00682         }
00683 
00689         function feedResult( $row ) {
00690                 if ( !isset( $row->title ) ) {
00691                         return null;
00692                 }
00693                 $title = Title::makeTitle( intval( $row->namespace ), $row->title );
00694                 if ( $title ) {
00695                         $date = isset( $row->timestamp ) ? $row->timestamp : '';
00696                         $comments = '';
00697                         if ( $title ) {
00698                                 $talkpage = $title->getTalkPage();
00699                                 $comments = $talkpage->getFullURL();
00700                         }
00701 
00702                         return new FeedItem(
00703                                 $title->getPrefixedText(),
00704                                 $this->feedItemDesc( $row ),
00705                                 $title->getFullURL(),
00706                                 $date,
00707                                 $this->feedItemAuthor( $row ),
00708                                 $comments );
00709                 } else {
00710                         return null;
00711                 }
00712         }
00713 
00714         function feedItemDesc( $row ) {
00715                 return isset( $row->comment ) ? htmlspecialchars( $row->comment ) : '';
00716         }
00717 
00718         function feedItemAuthor( $row ) {
00719                 return isset( $row->user_text ) ? $row->user_text : '';
00720         }
00721 
00722         function feedTitle() {
00723                 global $wgLanguageCode, $wgSitename;
00724                 $desc = $this->getDescription();
00725                 return "$wgSitename - $desc [$wgLanguageCode]";
00726         }
00727 
00728         function feedDesc() {
00729                 return $this->msg( 'tagline' )->text();
00730         }
00731 
00732         function feedUrl() {
00733                 return $this->getTitle()->getFullURL();
00734         }
00735 }
00736 
00741 abstract class WantedQueryPage extends QueryPage {
00742 
00743         function isExpensive() {
00744                 return true;
00745         }
00746 
00747         function isSyndicated() {
00748                 return false;
00749         }
00750 
00754         function preprocessResults( $db, $res ) {
00755                 if ( !$res->numRows() ) {
00756                         return;
00757                 }
00758 
00759                 $batch = new LinkBatch;
00760                 foreach ( $res as $row ) {
00761                         $batch->add( $row->namespace, $row->title );
00762                 }
00763                 $batch->execute();
00764 
00765                 // Back to start for display
00766                 $res->seek( 0 );
00767         }
00768 
00777         function forceExistenceCheck() {
00778                 return false;
00779         }
00780 
00788         public function formatResult( $skin, $result ) {
00789                 $title = Title::makeTitleSafe( $result->namespace, $result->title );
00790                 if ( $title instanceof Title ) {
00791                         if ( $this->isCached() || $this->forceExistenceCheck() ) {
00792                                 $pageLink = $title->isKnown()
00793                                         ? '<del>' . Linker::link( $title ) . '</del>'
00794                                         : Linker::link(
00795                                                 $title,
00796                                                 null,
00797                                                 array(),
00798                                                 array(),
00799                                                 array( 'broken' )
00800                                         );
00801                         } else {
00802                                 $pageLink = Linker::link(
00803                                         $title,
00804                                         null,
00805                                         array(),
00806                                         array(),
00807                                         array( 'broken' )
00808                                 );
00809                         }
00810                         return $this->getLanguage()->specialList( $pageLink, $this->makeWlhLink( $title, $result ) );
00811                 } else {
00812                         return $this->msg( 'wantedpages-badtitle', $result->title )->escaped();
00813                 }
00814         }
00815 
00823         private function makeWlhLink( $title, $result ) {
00824                 $wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedText() );
00825                 $label = $this->msg( 'nlinks' )->numParams( $result->value )->escaped();
00826                 return Linker::link( $wlh, $label );
00827         }
00828 }