MediaWiki
REL1_24
|
00001 <?php 00030 abstract class QueryPage extends SpecialPage { 00032 protected $listoutput = false; 00033 00035 protected $offset = 0; 00036 00038 protected $limit = 0; 00039 00045 protected $numRows; 00046 00047 protected $cachedTimestamp = null; 00048 00052 protected $shownavigation = true; 00053 00062 public static function getPages() { 00063 global $wgDisableCounters; 00064 static $qp = null; 00065 00066 if ( $qp === null ) { 00067 // QueryPage subclass, Special page name 00068 $qp = array( 00069 array( 'AncientPagesPage', 'Ancientpages' ), 00070 array( 'BrokenRedirectsPage', 'BrokenRedirects' ), 00071 array( 'DeadendPagesPage', 'Deadendpages' ), 00072 array( 'DoubleRedirectsPage', 'DoubleRedirects' ), 00073 array( 'FileDuplicateSearchPage', 'FileDuplicateSearch' ), 00074 array( 'ListDuplicatedFilesPage', 'ListDuplicatedFiles'), 00075 array( 'LinkSearchPage', 'LinkSearch' ), 00076 array( 'ListredirectsPage', 'Listredirects' ), 00077 array( 'LonelyPagesPage', 'Lonelypages' ), 00078 array( 'LongPagesPage', 'Longpages' ), 00079 array( 'MediaStatisticsPage', 'MediaStatistics' ), 00080 array( 'MIMEsearchPage', 'MIMEsearch' ), 00081 array( 'MostcategoriesPage', 'Mostcategories' ), 00082 array( 'MostimagesPage', 'Mostimages' ), 00083 array( 'MostinterwikisPage', 'Mostinterwikis' ), 00084 array( 'MostlinkedCategoriesPage', 'Mostlinkedcategories' ), 00085 array( 'MostlinkedtemplatesPage', 'Mostlinkedtemplates' ), 00086 array( 'MostlinkedPage', 'Mostlinked' ), 00087 array( 'MostrevisionsPage', 'Mostrevisions' ), 00088 array( 'FewestrevisionsPage', 'Fewestrevisions' ), 00089 array( 'ShortPagesPage', 'Shortpages' ), 00090 array( 'UncategorizedCategoriesPage', 'Uncategorizedcategories' ), 00091 array( 'UncategorizedPagesPage', 'Uncategorizedpages' ), 00092 array( 'UncategorizedImagesPage', 'Uncategorizedimages' ), 00093 array( 'UncategorizedTemplatesPage', 'Uncategorizedtemplates' ), 00094 array( 'UnusedCategoriesPage', 'Unusedcategories' ), 00095 array( 'UnusedimagesPage', 'Unusedimages' ), 00096 array( 'WantedCategoriesPage', 'Wantedcategories' ), 00097 array( 'WantedFilesPage', 'Wantedfiles' ), 00098 array( 'WantedPagesPage', 'Wantedpages' ), 00099 array( 'WantedTemplatesPage', 'Wantedtemplates' ), 00100 array( 'UnwatchedPagesPage', 'Unwatchedpages' ), 00101 array( 'UnusedtemplatesPage', 'Unusedtemplates' ), 00102 array( 'WithoutInterwikiPage', 'Withoutinterwiki' ), 00103 ); 00104 wfRunHooks( 'wgQueryPages', array( &$qp ) ); 00105 00106 if ( !$wgDisableCounters ) { 00107 $qp[] = array( 'PopularPagesPage', 'Popularpages' ); 00108 } 00109 } 00110 00111 return $qp; 00112 } 00113 00119 function setListoutput( $bool ) { 00120 $this->listoutput = $bool; 00121 } 00122 00149 function getQueryInfo() { 00150 return null; 00151 } 00152 00159 function getSQL() { 00160 /* Implement getQueryInfo() instead */ 00161 throw new MWException( "Bug in a QueryPage: doesn't implement getQueryInfo() nor " 00162 . "getQuery() properly" ); 00163 } 00164 00172 function getOrderFields() { 00173 return array( 'value' ); 00174 } 00175 00186 function usesTimestamps() { 00187 return false; 00188 } 00189 00195 function sortDescending() { 00196 return true; 00197 } 00198 00206 function isExpensive() { 00207 return $this->getConfig()->get( 'DisableQueryPages' ); 00208 } 00209 00217 public function isCacheable() { 00218 return true; 00219 } 00220 00227 function isCached() { 00228 return $this->isExpensive() && $this->getConfig()->get( 'MiserMode' ); 00229 } 00230 00236 function isSyndicated() { 00237 return true; 00238 } 00239 00249 abstract function formatResult( $skin, $result ); 00250 00256 function getPageHeader() { 00257 return ''; 00258 } 00259 00267 function linkParameters() { 00268 return array(); 00269 } 00270 00279 function tryLastResult() { 00280 return false; 00281 } 00282 00291 function recache( $limit, $ignoreErrors = true ) { 00292 if ( !$this->isCacheable() ) { 00293 return 0; 00294 } 00295 00296 $fname = get_class( $this ) . '::recache'; 00297 $dbw = wfGetDB( DB_MASTER ); 00298 if ( !$dbw ) { 00299 return false; 00300 } 00301 00302 try { 00303 # Do query 00304 $res = $this->reallyDoQuery( $limit, false ); 00305 $num = false; 00306 if ( $res ) { 00307 $num = $res->numRows(); 00308 # Fetch results 00309 $vals = array(); 00310 foreach ( $res as $row ) { 00311 if ( isset( $row->value ) ) { 00312 if ( $this->usesTimestamps() ) { 00313 $value = wfTimestamp( TS_UNIX, 00314 $row->value ); 00315 } else { 00316 $value = intval( $row->value ); // @bug 14414 00317 } 00318 } else { 00319 $value = 0; 00320 } 00321 00322 $vals[] = array( 'qc_type' => $this->getName(), 00323 'qc_namespace' => $row->namespace, 00324 'qc_title' => $row->title, 00325 'qc_value' => $value ); 00326 } 00327 00328 $dbw->startAtomic( __METHOD__ ); 00329 # Clear out any old cached data 00330 $dbw->delete( 'querycache', array( 'qc_type' => $this->getName() ), $fname ); 00331 # Save results into the querycache table on the master 00332 if ( count( $vals ) ) { 00333 $dbw->insert( 'querycache', $vals, __METHOD__ ); 00334 } 00335 # Update the querycache_info record for the page 00336 $dbw->delete( 'querycache_info', array( 'qci_type' => $this->getName() ), $fname ); 00337 $dbw->insert( 'querycache_info', 00338 array( 'qci_type' => $this->getName(), 'qci_timestamp' => $dbw->timestamp() ), 00339 $fname ); 00340 $dbw->endAtomic( __METHOD__ ); 00341 } 00342 } catch ( DBError $e ) { 00343 if ( !$ignoreErrors ) { 00344 throw $e; // report query error 00345 } 00346 $num = false; // set result to false to indicate error 00347 } 00348 00349 return $num; 00350 } 00351 00356 function getRecacheDB() { 00357 return wfGetDB( DB_SLAVE, array( $this->getName(), 'QueryPage::recache', 'vslow' ) ); 00358 } 00359 00367 function reallyDoQuery( $limit, $offset = false ) { 00368 $fname = get_class( $this ) . "::reallyDoQuery"; 00369 $dbr = $this->getRecacheDB(); 00370 $query = $this->getQueryInfo(); 00371 $order = $this->getOrderFields(); 00372 00373 if ( $this->sortDescending() ) { 00374 foreach ( $order as &$field ) { 00375 $field .= ' DESC'; 00376 } 00377 } 00378 00379 if ( is_array( $query ) ) { 00380 $tables = isset( $query['tables'] ) ? (array)$query['tables'] : array(); 00381 $fields = isset( $query['fields'] ) ? (array)$query['fields'] : array(); 00382 $conds = isset( $query['conds'] ) ? (array)$query['conds'] : array(); 00383 $options = isset( $query['options'] ) ? (array)$query['options'] : array(); 00384 $join_conds = isset( $query['join_conds'] ) ? (array)$query['join_conds'] : array(); 00385 00386 if ( count( $order ) ) { 00387 $options['ORDER BY'] = $order; 00388 } 00389 00390 if ( $limit !== false ) { 00391 $options['LIMIT'] = intval( $limit ); 00392 } 00393 00394 if ( $offset !== false ) { 00395 $options['OFFSET'] = intval( $offset ); 00396 } 00397 00398 $res = $dbr->select( $tables, $fields, $conds, $fname, 00399 $options, $join_conds 00400 ); 00401 } else { 00402 // Old-fashioned raw SQL style, deprecated 00403 $sql = $this->getSQL(); 00404 $sql .= ' ORDER BY ' . implode( ', ', $order ); 00405 $sql = $dbr->limitResult( $sql, $limit, $offset ); 00406 $res = $dbr->query( $sql, $fname ); 00407 } 00408 00409 return $res; 00410 } 00411 00418 function doQuery( $offset = false, $limit = false ) { 00419 if ( $this->isCached() && $this->isCacheable() ) { 00420 return $this->fetchFromCache( $limit, $offset ); 00421 } else { 00422 return $this->reallyDoQuery( $limit, $offset ); 00423 } 00424 } 00425 00433 function fetchFromCache( $limit, $offset = false ) { 00434 $dbr = wfGetDB( DB_SLAVE ); 00435 $options = array(); 00436 if ( $limit !== false ) { 00437 $options['LIMIT'] = intval( $limit ); 00438 } 00439 if ( $offset !== false ) { 00440 $options['OFFSET'] = intval( $offset ); 00441 } 00442 if ( $this->sortDescending() ) { 00443 $options['ORDER BY'] = 'qc_value DESC'; 00444 } else { 00445 $options['ORDER BY'] = 'qc_value ASC'; 00446 } 00447 $res = $dbr->select( 'querycache', array( 'qc_type', 00448 'namespace' => 'qc_namespace', 00449 'title' => 'qc_title', 00450 'value' => 'qc_value' ), 00451 array( 'qc_type' => $this->getName() ), 00452 __METHOD__, $options 00453 ); 00454 return $dbr->resultObject( $res ); 00455 } 00456 00457 public function getCachedTimestamp() { 00458 if ( is_null( $this->cachedTimestamp ) ) { 00459 $dbr = wfGetDB( DB_SLAVE ); 00460 $fname = get_class( $this ) . '::getCachedTimestamp'; 00461 $this->cachedTimestamp = $dbr->selectField( 'querycache_info', 'qci_timestamp', 00462 array( 'qci_type' => $this->getName() ), $fname ); 00463 } 00464 return $this->cachedTimestamp; 00465 } 00466 00472 function execute( $par ) { 00473 $user = $this->getUser(); 00474 if ( !$this->userCanExecute( $user ) ) { 00475 $this->displayRestrictionError(); 00476 return; 00477 } 00478 00479 $this->setHeaders(); 00480 $this->outputHeader(); 00481 00482 $out = $this->getOutput(); 00483 00484 if ( $this->isCached() && !$this->isCacheable() ) { 00485 $out->addWikiMsg( 'querypage-disabled' ); 00486 return; 00487 } 00488 00489 $out->setSyndicated( $this->isSyndicated() ); 00490 00491 if ( $this->limit == 0 && $this->offset == 0 ) { 00492 list( $this->limit, $this->offset ) = $this->getRequest()->getLimitOffset(); 00493 } 00494 00495 // @todo Use doQuery() 00496 if ( !$this->isCached() ) { 00497 # select one extra row for navigation 00498 $res = $this->reallyDoQuery( $this->limit + 1, $this->offset ); 00499 } else { 00500 # Get the cached result, select one extra row for navigation 00501 $res = $this->fetchFromCache( $this->limit + 1, $this->offset ); 00502 if ( !$this->listoutput ) { 00503 00504 # Fetch the timestamp of this update 00505 $ts = $this->getCachedTimestamp(); 00506 $lang = $this->getLanguage(); 00507 $maxResults = $lang->formatNum( $this->getConfig()->get( 'QueryCacheLimit' ) ); 00508 00509 if ( $ts ) { 00510 $updated = $lang->userTimeAndDate( $ts, $user ); 00511 $updateddate = $lang->userDate( $ts, $user ); 00512 $updatedtime = $lang->userTime( $ts, $user ); 00513 $out->addMeta( 'Data-Cache-Time', $ts ); 00514 $out->addJsConfigVars( 'dataCacheTime', $ts ); 00515 $out->addWikiMsg( 'perfcachedts', $updated, $updateddate, $updatedtime, $maxResults ); 00516 } else { 00517 $out->addWikiMsg( 'perfcached', $maxResults ); 00518 } 00519 00520 # If updates on this page have been disabled, let the user know 00521 # that the data set won't be refreshed for now 00522 if ( is_array( $this->getConfig()->get( 'DisableQueryPageUpdate' ) ) 00523 && in_array( $this->getName(), $this->getConfig()->get( 'DisableQueryPageUpdate' ) ) 00524 ) { 00525 $out->wrapWikiMsg( 00526 "<div class=\"mw-querypage-no-updates\">\n$1\n</div>", 00527 'querypage-no-updates' 00528 ); 00529 } 00530 } 00531 } 00532 00533 $this->numRows = $res->numRows(); 00534 00535 $dbr = wfGetDB( DB_SLAVE ); 00536 $this->preprocessResults( $dbr, $res ); 00537 00538 $out->addHTML( Xml::openElement( 'div', array( 'class' => 'mw-spcontent' ) ) ); 00539 00540 # Top header and navigation 00541 if ( $this->shownavigation ) { 00542 $out->addHTML( $this->getPageHeader() ); 00543 if ( $this->numRows > 0 ) { 00544 $out->addHTML( $this->msg( 'showingresultsinrange' )->numParams( 00545 min( $this->numRows, $this->limit ), # do not show the one extra row, if exist 00546 $this->offset + 1, ( min( $this->numRows, $this->limit ) + $this->offset ) )->parseAsBlock() ); 00547 # Disable the "next" link when we reach the end 00548 $paging = $this->getLanguage()->viewPrevNext( $this->getPageTitle( $par ), $this->offset, 00549 $this->limit, $this->linkParameters(), ( $this->numRows <= $this->limit ) ); 00550 $out->addHTML( '<p>' . $paging . '</p>' ); 00551 } else { 00552 # No results to show, so don't bother with "showing X of Y" etc. 00553 # -- just let the user know and give up now 00554 $out->addWikiMsg( 'specialpage-empty' ); 00555 $out->addHTML( Xml::closeElement( 'div' ) ); 00556 return; 00557 } 00558 } 00559 00560 # The actual results; specialist subclasses will want to handle this 00561 # with more than a straight list, so we hand them the info, plus 00562 # an OutputPage, and let them get on with it 00563 $this->outputResults( $out, 00564 $this->getSkin(), 00565 $dbr, # Should use a ResultWrapper for this 00566 $res, 00567 min( $this->numRows, $this->limit ), # do not format the one extra row, if exist 00568 $this->offset ); 00569 00570 # Repeat the paging links at the bottom 00571 if ( $this->shownavigation ) { 00572 $out->addHTML( '<p>' . $paging . '</p>' ); 00573 } 00574 00575 $out->addHTML( Xml::closeElement( 'div' ) ); 00576 } 00577 00589 protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) { 00590 global $wgContLang; 00591 00592 if ( $num > 0 ) { 00593 $html = array(); 00594 if ( !$this->listoutput ) { 00595 $html[] = $this->openList( $offset ); 00596 } 00597 00598 # $res might contain the whole 1,000 rows, so we read up to 00599 # $num [should update this to use a Pager] 00600 // @codingStandardsIgnoreStart Generic.CodeAnalysis.ForLoopWithTestFunctionCall.NotAllowed 00601 for ( $i = 0; $i < $num && $row = $res->fetchObject(); $i++ ) { 00602 // @codingStandardsIgnoreEnd 00603 $line = $this->formatResult( $skin, $row ); 00604 if ( $line ) { 00605 $attr = ( isset( $row->usepatrol ) && $row->usepatrol && $row->patrolled == 0 ) 00606 ? ' class="not-patrolled"' 00607 : ''; 00608 $html[] = $this->listoutput 00609 ? $line 00610 : "<li{$attr}>{$line}</li>\n"; 00611 } 00612 } 00613 00614 # Flush the final result 00615 if ( $this->tryLastResult() ) { 00616 $row = null; 00617 $line = $this->formatResult( $skin, $row ); 00618 if ( $line ) { 00619 $attr = ( isset( $row->usepatrol ) && $row->usepatrol && $row->patrolled == 0 ) 00620 ? ' class="not-patrolled"' 00621 : ''; 00622 $html[] = $this->listoutput 00623 ? $line 00624 : "<li{$attr}>{$line}</li>\n"; 00625 } 00626 } 00627 00628 if ( !$this->listoutput ) { 00629 $html[] = $this->closeList(); 00630 } 00631 00632 $html = $this->listoutput 00633 ? $wgContLang->listToText( $html ) 00634 : implode( '', $html ); 00635 00636 $out->addHTML( $html ); 00637 } 00638 } 00639 00644 function openList( $offset ) { 00645 return "\n<ol start='" . ( $offset + 1 ) . "' class='special'>\n"; 00646 } 00647 00651 function closeList() { 00652 return "</ol>\n"; 00653 } 00654 00660 function preprocessResults( $db, $res ) { 00661 } 00662 00669 function doFeed( $class = '', $limit = 50 ) { 00670 if ( !$this->getConfig()->get( 'Feed' ) ) { 00671 $this->getOutput()->addWikiMsg( 'feed-unavailable' ); 00672 return false; 00673 } 00674 00675 $limit = min( $limit, $this->getConfig()->get( 'FeedLimit' ) ); 00676 00677 $feedClasses = $this->getConfig()->get( 'FeedClasses' ); 00678 if ( isset( $feedClasses[$class] ) ) { 00680 $feed = new $feedClasses[$class]( 00681 $this->feedTitle(), 00682 $this->feedDesc(), 00683 $this->feedUrl() ); 00684 $feed->outHeader(); 00685 00686 $res = $this->reallyDoQuery( $limit, 0 ); 00687 foreach ( $res as $obj ) { 00688 $item = $this->feedResult( $obj ); 00689 if ( $item ) { 00690 $feed->outItem( $item ); 00691 } 00692 } 00693 00694 $feed->outFooter(); 00695 return true; 00696 } else { 00697 return false; 00698 } 00699 } 00700 00707 function feedResult( $row ) { 00708 if ( !isset( $row->title ) ) { 00709 return null; 00710 } 00711 $title = Title::makeTitle( intval( $row->namespace ), $row->title ); 00712 if ( $title ) { 00713 $date = isset( $row->timestamp ) ? $row->timestamp : ''; 00714 $comments = ''; 00715 if ( $title ) { 00716 $talkpage = $title->getTalkPage(); 00717 $comments = $talkpage->getFullURL(); 00718 } 00719 00720 return new FeedItem( 00721 $title->getPrefixedText(), 00722 $this->feedItemDesc( $row ), 00723 $title->getFullURL(), 00724 $date, 00725 $this->feedItemAuthor( $row ), 00726 $comments ); 00727 } else { 00728 return null; 00729 } 00730 } 00731 00732 function feedItemDesc( $row ) { 00733 return isset( $row->comment ) ? htmlspecialchars( $row->comment ) : ''; 00734 } 00735 00736 function feedItemAuthor( $row ) { 00737 return isset( $row->user_text ) ? $row->user_text : ''; 00738 } 00739 00740 function feedTitle() { 00741 $desc = $this->getDescription(); 00742 $code = $this->getConfig()->get( 'LanguageCode' ); 00743 $sitename = $this->getConfig()->get( 'Sitename' ); 00744 return "$sitename - $desc [$code]"; 00745 } 00746 00747 function feedDesc() { 00748 return $this->msg( 'tagline' )->text(); 00749 } 00750 00751 function feedUrl() { 00752 return $this->getPageTitle()->getFullURL(); 00753 } 00754 }