MediaWiki
REL1_21
|
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 00081 abstract class QueryPage extends SpecialPage { 00087 var $listoutput = false; 00088 00094 var $offset = 0; 00095 var $limit = 0; 00096 00102 protected $numRows; 00103 00104 protected $cachedTimestamp = null; 00105 00109 protected $shownavigation = true; 00110 00116 function setListoutput( $bool ) { 00117 $this->listoutput = $bool; 00118 } 00119 00146 function getQueryInfo() { 00147 return null; 00148 } 00149 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 try { 00305 # Clear out any old cached data 00306 $dbw->delete( 'querycache', array( 'qc_type' => $this->getName() ), $fname ); 00307 # Do query 00308 $res = $this->reallyDoQuery( $limit, false ); 00309 $num = false; 00310 if ( $res ) { 00311 $num = $res->numRows(); 00312 # Fetch results 00313 $vals = array(); 00314 while ( $res && $row = $dbr->fetchObject( $res ) ) { 00315 if ( isset( $row->value ) ) { 00316 if ( $this->usesTimestamps() ) { 00317 $value = wfTimestamp( TS_UNIX, 00318 $row->value ); 00319 } else { 00320 $value = intval( $row->value ); // @bug 14414 00321 } 00322 } else { 00323 $value = 0; 00324 } 00325 00326 $vals[] = array( 'qc_type' => $this->getName(), 00327 'qc_namespace' => $row->namespace, 00328 'qc_title' => $row->title, 00329 'qc_value' => $value ); 00330 } 00331 00332 # Save results into the querycache table on the master 00333 if ( count( $vals ) ) { 00334 $dbw->insert( 'querycache', $vals, __METHOD__ ); 00335 } 00336 # Update the querycache_info record for the page 00337 $dbw->delete( 'querycache_info', array( 'qci_type' => $this->getName() ), $fname ); 00338 $dbw->insert( 'querycache_info', 00339 array( 'qci_type' => $this->getName(), 'qci_timestamp' => $dbw->timestamp() ), 00340 $fname ); 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 00359 function reallyDoQuery( $limit, $offset = false ) { 00360 $fname = get_class( $this ) . "::reallyDoQuery"; 00361 $dbr = wfGetDB( DB_SLAVE ); 00362 $query = $this->getQueryInfo(); 00363 $order = $this->getOrderFields(); 00364 if ( $this->sortDescending() ) { 00365 foreach ( $order as &$field ) { 00366 $field .= ' DESC'; 00367 } 00368 } 00369 if ( is_array( $query ) ) { 00370 $tables = isset( $query['tables'] ) ? (array)$query['tables'] : array(); 00371 $fields = isset( $query['fields'] ) ? (array)$query['fields'] : array(); 00372 $conds = isset( $query['conds'] ) ? (array)$query['conds'] : array(); 00373 $options = isset( $query['options'] ) ? (array)$query['options'] : array(); 00374 $join_conds = isset( $query['join_conds'] ) ? (array)$query['join_conds'] : array(); 00375 if ( count( $order ) ) { 00376 $options['ORDER BY'] = $order; 00377 } 00378 if ( $limit !== false ) { 00379 $options['LIMIT'] = intval( $limit ); 00380 } 00381 if ( $offset !== false ) { 00382 $options['OFFSET'] = intval( $offset ); 00383 } 00384 00385 $res = $dbr->select( $tables, $fields, $conds, $fname, 00386 $options, $join_conds 00387 ); 00388 } else { 00389 // Old-fashioned raw SQL style, deprecated 00390 $sql = $this->getSQL(); 00391 $sql .= ' ORDER BY ' . implode( ', ', $order ); 00392 $sql = $dbr->limitResult( $sql, $limit, $offset ); 00393 $res = $dbr->query( $sql, $fname ); 00394 } 00395 return $dbr->resultObject( $res ); 00396 } 00397 00402 function doQuery( $offset = false, $limit = false ) { 00403 if ( $this->isCached() && $this->isCacheable() ) { 00404 return $this->fetchFromCache( $limit, $offset ); 00405 } else { 00406 return $this->reallyDoQuery( $limit, $offset ); 00407 } 00408 } 00409 00417 function fetchFromCache( $limit, $offset = false ) { 00418 $dbr = wfGetDB( DB_SLAVE ); 00419 $options = array (); 00420 if ( $limit !== false ) { 00421 $options['LIMIT'] = intval( $limit ); 00422 } 00423 if ( $offset !== false ) { 00424 $options['OFFSET'] = intval( $offset ); 00425 } 00426 if ( $this->sortDescending() ) { 00427 $options['ORDER BY'] = 'qc_value DESC'; 00428 } else { 00429 $options['ORDER BY'] = 'qc_value ASC'; 00430 } 00431 $res = $dbr->select( 'querycache', array( 'qc_type', 00432 'namespace' => 'qc_namespace', 00433 'title' => 'qc_title', 00434 'value' => 'qc_value' ), 00435 array( 'qc_type' => $this->getName() ), 00436 __METHOD__, $options 00437 ); 00438 return $dbr->resultObject( $res ); 00439 } 00440 00441 public function getCachedTimestamp() { 00442 if ( is_null( $this->cachedTimestamp ) ) { 00443 $dbr = wfGetDB( DB_SLAVE ); 00444 $fname = get_class( $this ) . '::getCachedTimestamp'; 00445 $this->cachedTimestamp = $dbr->selectField( 'querycache_info', 'qci_timestamp', 00446 array( 'qci_type' => $this->getName() ), $fname ); 00447 } 00448 return $this->cachedTimestamp; 00449 } 00450 00456 function execute( $par ) { 00457 global $wgQueryCacheLimit, $wgDisableQueryPageUpdate; 00458 00459 $user = $this->getUser(); 00460 if ( !$this->userCanExecute( $user ) ) { 00461 $this->displayRestrictionError(); 00462 return; 00463 } 00464 00465 $this->setHeaders(); 00466 $this->outputHeader(); 00467 00468 $out = $this->getOutput(); 00469 00470 if ( $this->isCached() && !$this->isCacheable() ) { 00471 $out->addWikiMsg( 'querypage-disabled' ); 00472 return 0; 00473 } 00474 00475 $out->setSyndicated( $this->isSyndicated() ); 00476 00477 if ( $this->limit == 0 && $this->offset == 0 ) { 00478 list( $this->limit, $this->offset ) = $this->getRequest()->getLimitOffset(); 00479 } 00480 00481 // TODO: Use doQuery() 00482 if ( !$this->isCached() ) { 00483 # select one extra row for navigation 00484 $res = $this->reallyDoQuery( $this->limit + 1, $this->offset ); 00485 } else { 00486 # Get the cached result, select one extra row for navigation 00487 $res = $this->fetchFromCache( $this->limit + 1, $this->offset ); 00488 if ( !$this->listoutput ) { 00489 00490 # Fetch the timestamp of this update 00491 $ts = $this->getCachedTimestamp(); 00492 $lang = $this->getLanguage(); 00493 $maxResults = $lang->formatNum( $wgQueryCacheLimit ); 00494 00495 if ( $ts ) { 00496 $updated = $lang->userTimeAndDate( $ts, $user ); 00497 $updateddate = $lang->userDate( $ts, $user ); 00498 $updatedtime = $lang->userTime( $ts, $user ); 00499 $out->addMeta( 'Data-Cache-Time', $ts ); 00500 $out->addJsConfigVars( 'dataCacheTime', $ts ); 00501 $out->addWikiMsg( 'perfcachedts', $updated, $updateddate, $updatedtime, $maxResults ); 00502 } else { 00503 $out->addWikiMsg( 'perfcached', $maxResults ); 00504 } 00505 00506 # If updates on this page have been disabled, let the user know 00507 # that the data set won't be refreshed for now 00508 if ( is_array( $wgDisableQueryPageUpdate ) && in_array( $this->getName(), $wgDisableQueryPageUpdate ) ) { 00509 $out->wrapWikiMsg( "<div class=\"mw-querypage-no-updates\">\n$1\n</div>", 'querypage-no-updates' ); 00510 } 00511 } 00512 } 00513 00514 $this->numRows = $res->numRows(); 00515 00516 $dbr = wfGetDB( DB_SLAVE ); 00517 $this->preprocessResults( $dbr, $res ); 00518 00519 $out->addHTML( Xml::openElement( 'div', array( 'class' => 'mw-spcontent' ) ) ); 00520 00521 # Top header and navigation 00522 if ( $this->shownavigation ) { 00523 $out->addHTML( $this->getPageHeader() ); 00524 if ( $this->numRows > 0 ) { 00525 $out->addHTML( $this->msg( 'showingresults' )->numParams( 00526 min( $this->numRows, $this->limit ), # do not show the one extra row, if exist 00527 $this->offset + 1 )->parseAsBlock() ); 00528 # Disable the "next" link when we reach the end 00529 $paging = $this->getLanguage()->viewPrevNext( $this->getTitle( $par ), $this->offset, 00530 $this->limit, $this->linkParameters(), ( $this->numRows <= $this->limit ) ); 00531 $out->addHTML( '<p>' . $paging . '</p>' ); 00532 } else { 00533 # No results to show, so don't bother with "showing X of Y" etc. 00534 # -- just let the user know and give up now 00535 $out->addWikiMsg( 'specialpage-empty' ); 00536 $out->addHTML( Xml::closeElement( 'div' ) ); 00537 return; 00538 } 00539 } 00540 00541 # The actual results; specialist subclasses will want to handle this 00542 # with more than a straight list, so we hand them the info, plus 00543 # an OutputPage, and let them get on with it 00544 $this->outputResults( $out, 00545 $this->getSkin(), 00546 $dbr, # Should use a ResultWrapper for this 00547 $res, 00548 min( $this->numRows, $this->limit ), # do not format the one extra row, if exist 00549 $this->offset ); 00550 00551 # Repeat the paging links at the bottom 00552 if ( $this->shownavigation ) { 00553 $out->addHTML( '<p>' . $paging . '</p>' ); 00554 } 00555 00556 $out->addHTML( Xml::closeElement( 'div' ) ); 00557 00558 return min( $this->numRows, $this->limit ); # do not return the one extra row, if exist 00559 } 00560 00572 protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) { 00573 global $wgContLang; 00574 00575 if ( $num > 0 ) { 00576 $html = array(); 00577 if ( !$this->listoutput ) { 00578 $html[] = $this->openList( $offset ); 00579 } 00580 00581 # $res might contain the whole 1,000 rows, so we read up to 00582 # $num [should update this to use a Pager] 00583 for ( $i = 0; $i < $num && $row = $dbr->fetchObject( $res ); $i++ ) { 00584 $line = $this->formatResult( $skin, $row ); 00585 if ( $line ) { 00586 $attr = ( isset( $row->usepatrol ) && $row->usepatrol && $row->patrolled == 0 ) 00587 ? ' class="not-patrolled"' 00588 : ''; 00589 $html[] = $this->listoutput 00590 ? $line 00591 : "<li{$attr}>{$line}</li>\n"; 00592 } 00593 } 00594 00595 # Flush the final result 00596 if ( $this->tryLastResult() ) { 00597 $row = null; 00598 $line = $this->formatResult( $skin, $row ); 00599 if ( $line ) { 00600 $attr = ( isset( $row->usepatrol ) && $row->usepatrol && $row->patrolled == 0 ) 00601 ? ' class="not-patrolled"' 00602 : ''; 00603 $html[] = $this->listoutput 00604 ? $line 00605 : "<li{$attr}>{$line}</li>\n"; 00606 } 00607 } 00608 00609 if ( !$this->listoutput ) { 00610 $html[] = $this->closeList(); 00611 } 00612 00613 $html = $this->listoutput 00614 ? $wgContLang->listToText( $html ) 00615 : implode( '', $html ); 00616 00617 $out->addHTML( $html ); 00618 } 00619 } 00620 00625 function openList( $offset ) { 00626 return "\n<ol start='" . ( $offset + 1 ) . "' class='special'>\n"; 00627 } 00628 00632 function closeList() { 00633 return "</ol>\n"; 00634 } 00635 00639 function preprocessResults( $db, $res ) {} 00640 00645 function doFeed( $class = '', $limit = 50 ) { 00646 global $wgFeed, $wgFeedClasses; 00647 00648 if ( !$wgFeed ) { 00649 $this->getOutput()->addWikiMsg( 'feed-unavailable' ); 00650 return false; 00651 } 00652 00653 global $wgFeedLimit; 00654 if ( $limit > $wgFeedLimit ) { 00655 $limit = $wgFeedLimit; 00656 } 00657 00658 if ( isset( $wgFeedClasses[$class] ) ) { 00659 $feed = new $wgFeedClasses[$class]( 00660 $this->feedTitle(), 00661 $this->feedDesc(), 00662 $this->feedUrl() ); 00663 $feed->outHeader(); 00664 00665 $res = $this->reallyDoQuery( $limit, 0 ); 00666 foreach ( $res as $obj ) { 00667 $item = $this->feedResult( $obj ); 00668 if ( $item ) { 00669 $feed->outItem( $item ); 00670 } 00671 } 00672 00673 $feed->outFooter(); 00674 return true; 00675 } else { 00676 return false; 00677 } 00678 } 00679 00685 function feedResult( $row ) { 00686 if ( !isset( $row->title ) ) { 00687 return null; 00688 } 00689 $title = Title::makeTitle( intval( $row->namespace ), $row->title ); 00690 if ( $title ) { 00691 $date = isset( $row->timestamp ) ? $row->timestamp : ''; 00692 $comments = ''; 00693 if ( $title ) { 00694 $talkpage = $title->getTalkPage(); 00695 $comments = $talkpage->getFullURL(); 00696 } 00697 00698 return new FeedItem( 00699 $title->getPrefixedText(), 00700 $this->feedItemDesc( $row ), 00701 $title->getFullURL(), 00702 $date, 00703 $this->feedItemAuthor( $row ), 00704 $comments ); 00705 } else { 00706 return null; 00707 } 00708 } 00709 00710 function feedItemDesc( $row ) { 00711 return isset( $row->comment ) ? htmlspecialchars( $row->comment ) : ''; 00712 } 00713 00714 function feedItemAuthor( $row ) { 00715 return isset( $row->user_text ) ? $row->user_text : ''; 00716 } 00717 00718 function feedTitle() { 00719 global $wgLanguageCode, $wgSitename; 00720 $desc = $this->getDescription(); 00721 return "$wgSitename - $desc [$wgLanguageCode]"; 00722 } 00723 00724 function feedDesc() { 00725 return $this->msg( 'tagline' )->text(); 00726 } 00727 00728 function feedUrl() { 00729 return $this->getTitle()->getFullURL(); 00730 } 00731 } 00732 00737 abstract class WantedQueryPage extends QueryPage { 00738 00739 function isExpensive() { 00740 return true; 00741 } 00742 00743 function isSyndicated() { 00744 return false; 00745 } 00746 00750 function preprocessResults( $db, $res ) { 00751 if ( !$res->numRows() ) { 00752 return; 00753 } 00754 00755 $batch = new LinkBatch; 00756 foreach ( $res as $row ) { 00757 $batch->add( $row->namespace, $row->title ); 00758 } 00759 $batch->execute(); 00760 00761 // Back to start for display 00762 $res->seek( 0 ); 00763 } 00764 00773 function forceExistenceCheck() { 00774 return false; 00775 } 00776 00784 public function formatResult( $skin, $result ) { 00785 $title = Title::makeTitleSafe( $result->namespace, $result->title ); 00786 if ( $title instanceof Title ) { 00787 if ( $this->isCached() || $this->forceExistenceCheck() ) { 00788 $pageLink = $title->isKnown() 00789 ? '<del>' . Linker::link( $title ) . '</del>' 00790 : Linker::link( 00791 $title, 00792 null, 00793 array(), 00794 array(), 00795 array( 'broken' ) 00796 ); 00797 } else { 00798 $pageLink = Linker::link( 00799 $title, 00800 null, 00801 array(), 00802 array(), 00803 array( 'broken' ) 00804 ); 00805 } 00806 return $this->getLanguage()->specialList( $pageLink, $this->makeWlhLink( $title, $result ) ); 00807 } else { 00808 return $this->msg( 'wantedpages-badtitle', $result->title )->escaped(); 00809 } 00810 } 00811 00819 private function makeWlhLink( $title, $result ) { 00820 $wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedText() ); 00821 $label = $this->msg( 'nlinks' )->numParams( $result->value )->escaped(); 00822 return Linker::link( $wlh, $label ); 00823 } 00824 }