MediaWiki
REL1_22
|
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( 'DoubleRedirectsPage', 'DoubleRedirects' ), 00039 array( 'FileDuplicateSearchPage', 'FileDuplicateSearch' ), 00040 array( 'LinkSearchPage', 'LinkSearch' ), 00041 array( 'ListredirectsPage', 'Listredirects' ), 00042 array( 'LonelyPagesPage', 'Lonelypages' ), 00043 array( 'LongPagesPage', 'Longpages' ), 00044 array( 'MIMEsearchPage', 'MIMEsearch' ), 00045 array( 'MostcategoriesPage', 'Mostcategories' ), 00046 array( 'MostimagesPage', 'Mostimages' ), 00047 array( 'MostinterwikisPage', 'Mostinterwikis' ), 00048 array( 'MostlinkedCategoriesPage', 'Mostlinkedcategories' ), 00049 array( 'MostlinkedtemplatesPage', 'Mostlinkedtemplates' ), 00050 array( 'MostlinkedPage', 'Mostlinked' ), 00051 array( 'MostrevisionsPage', 'Mostrevisions' ), 00052 array( 'FewestrevisionsPage', 'Fewestrevisions' ), 00053 array( 'ShortPagesPage', 'Shortpages' ), 00054 array( 'UncategorizedCategoriesPage', 'Uncategorizedcategories' ), 00055 array( 'UncategorizedPagesPage', 'Uncategorizedpages' ), 00056 array( 'UncategorizedImagesPage', 'Uncategorizedimages' ), 00057 array( 'UncategorizedTemplatesPage', 'Uncategorizedtemplates' ), 00058 array( 'UnusedCategoriesPage', 'Unusedcategories' ), 00059 array( 'UnusedimagesPage', 'Unusedimages' ), 00060 array( 'WantedCategoriesPage', 'Wantedcategories' ), 00061 array( 'WantedFilesPage', 'Wantedfiles' ), 00062 array( 'WantedPagesPage', 'Wantedpages' ), 00063 array( 'WantedTemplatesPage', 'Wantedtemplates' ), 00064 array( 'UnwatchedPagesPage', 'Unwatchedpages' ), 00065 array( 'UnusedtemplatesPage', 'Unusedtemplates' ), 00066 array( 'WithoutInterwikiPage', 'Withoutinterwiki' ), 00067 ); 00068 wfRunHooks( 'wgQueryPages', array( &$wgQueryPages ) ); 00069 00070 global $wgDisableCounters; 00071 if ( !$wgDisableCounters ) { 00072 $wgQueryPages[] = array( 'PopularPagesPage', 'Popularpages' ); 00073 } 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 00248 abstract function formatResult( $skin, $result ); 00249 00255 function getPageHeader() { 00256 return ''; 00257 } 00258 00266 function linkParameters() { 00267 return array(); 00268 } 00269 00278 function tryLastResult() { 00279 return false; 00280 } 00281 00290 function recache( $limit, $ignoreErrors = true ) { 00291 if ( !$this->isCacheable() ) { 00292 return 0; 00293 } 00294 00295 $fname = get_class( $this ) . '::recache'; 00296 $dbw = wfGetDB( DB_MASTER ); 00297 $dbr = wfGetDB( DB_SLAVE, array( $this->getName(), __METHOD__, 'vslow' ) ); 00298 if ( !$dbw || !$dbr ) { 00299 return false; 00300 } 00301 00302 try { 00303 # Clear out any old cached data 00304 $dbw->delete( 'querycache', array( 'qc_type' => $this->getName() ), $fname ); 00305 # Do query 00306 $res = $this->reallyDoQuery( $limit, false ); 00307 $num = false; 00308 if ( $res ) { 00309 $num = $res->numRows(); 00310 # Fetch results 00311 $vals = array(); 00312 while ( $res && $row = $dbr->fetchObject( $res ) ) { 00313 if ( isset( $row->value ) ) { 00314 if ( $this->usesTimestamps() ) { 00315 $value = wfTimestamp( TS_UNIX, 00316 $row->value ); 00317 } else { 00318 $value = intval( $row->value ); // @bug 14414 00319 } 00320 } else { 00321 $value = 0; 00322 } 00323 00324 $vals[] = array( 'qc_type' => $this->getName(), 00325 'qc_namespace' => $row->namespace, 00326 'qc_title' => $row->title, 00327 'qc_value' => $value ); 00328 } 00329 00330 # Save results into the querycache table on the master 00331 if ( count( $vals ) ) { 00332 $dbw->insert( 'querycache', $vals, __METHOD__ ); 00333 } 00334 # Update the querycache_info record for the page 00335 $dbw->delete( 'querycache_info', array( 'qci_type' => $this->getName() ), $fname ); 00336 $dbw->insert( 'querycache_info', 00337 array( 'qci_type' => $this->getName(), 'qci_timestamp' => $dbw->timestamp() ), 00338 $fname ); 00339 } 00340 } catch ( DBError $e ) { 00341 if ( !$ignoreErrors ) { 00342 throw $e; // report query error 00343 } 00344 $num = false; // set result to false to indicate error 00345 } 00346 00347 return $num; 00348 } 00349 00357 function reallyDoQuery( $limit, $offset = false ) { 00358 $fname = get_class( $this ) . "::reallyDoQuery"; 00359 $dbr = wfGetDB( DB_SLAVE ); 00360 $query = $this->getQueryInfo(); 00361 $order = $this->getOrderFields(); 00362 00363 if ( $this->sortDescending() ) { 00364 foreach ( $order as &$field ) { 00365 $field .= ' DESC'; 00366 } 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 00376 if ( count( $order ) ) { 00377 $options['ORDER BY'] = $order; 00378 } 00379 00380 if ( $limit !== false ) { 00381 $options['LIMIT'] = intval( $limit ); 00382 } 00383 00384 if ( $offset !== false ) { 00385 $options['OFFSET'] = intval( $offset ); 00386 } 00387 00388 $res = $dbr->select( $tables, $fields, $conds, $fname, 00389 $options, $join_conds 00390 ); 00391 } else { 00392 // Old-fashioned raw SQL style, deprecated 00393 $sql = $this->getSQL(); 00394 $sql .= ' ORDER BY ' . implode( ', ', $order ); 00395 $sql = $dbr->limitResult( $sql, $limit, $offset ); 00396 $res = $dbr->query( $sql, $fname ); 00397 } 00398 00399 return $dbr->resultObject( $res ); 00400 } 00401 00408 function doQuery( $offset = false, $limit = false ) { 00409 if ( $this->isCached() && $this->isCacheable() ) { 00410 return $this->fetchFromCache( $limit, $offset ); 00411 } else { 00412 return $this->reallyDoQuery( $limit, $offset ); 00413 } 00414 } 00415 00423 function fetchFromCache( $limit, $offset = false ) { 00424 $dbr = wfGetDB( DB_SLAVE ); 00425 $options = array(); 00426 if ( $limit !== false ) { 00427 $options['LIMIT'] = intval( $limit ); 00428 } 00429 if ( $offset !== false ) { 00430 $options['OFFSET'] = intval( $offset ); 00431 } 00432 if ( $this->sortDescending() ) { 00433 $options['ORDER BY'] = 'qc_value DESC'; 00434 } else { 00435 $options['ORDER BY'] = 'qc_value ASC'; 00436 } 00437 $res = $dbr->select( 'querycache', array( 'qc_type', 00438 'namespace' => 'qc_namespace', 00439 'title' => 'qc_title', 00440 'value' => 'qc_value' ), 00441 array( 'qc_type' => $this->getName() ), 00442 __METHOD__, $options 00443 ); 00444 return $dbr->resultObject( $res ); 00445 } 00446 00447 public function getCachedTimestamp() { 00448 if ( is_null( $this->cachedTimestamp ) ) { 00449 $dbr = wfGetDB( DB_SLAVE ); 00450 $fname = get_class( $this ) . '::getCachedTimestamp'; 00451 $this->cachedTimestamp = $dbr->selectField( 'querycache_info', 'qci_timestamp', 00452 array( 'qci_type' => $this->getName() ), $fname ); 00453 } 00454 return $this->cachedTimestamp; 00455 } 00456 00463 function execute( $par ) { 00464 global $wgQueryCacheLimit, $wgDisableQueryPageUpdate; 00465 00466 $user = $this->getUser(); 00467 if ( !$this->userCanExecute( $user ) ) { 00468 $this->displayRestrictionError(); 00469 return; 00470 } 00471 00472 $this->setHeaders(); 00473 $this->outputHeader(); 00474 00475 $out = $this->getOutput(); 00476 00477 if ( $this->isCached() && !$this->isCacheable() ) { 00478 $out->addWikiMsg( 'querypage-disabled' ); 00479 return 0; 00480 } 00481 00482 $out->setSyndicated( $this->isSyndicated() ); 00483 00484 if ( $this->limit == 0 && $this->offset == 0 ) { 00485 list( $this->limit, $this->offset ) = $this->getRequest()->getLimitOffset(); 00486 } 00487 00488 // TODO: Use doQuery() 00489 if ( !$this->isCached() ) { 00490 # select one extra row for navigation 00491 $res = $this->reallyDoQuery( $this->limit + 1, $this->offset ); 00492 } else { 00493 # Get the cached result, select one extra row for navigation 00494 $res = $this->fetchFromCache( $this->limit + 1, $this->offset ); 00495 if ( !$this->listoutput ) { 00496 00497 # Fetch the timestamp of this update 00498 $ts = $this->getCachedTimestamp(); 00499 $lang = $this->getLanguage(); 00500 $maxResults = $lang->formatNum( $wgQueryCacheLimit ); 00501 00502 if ( $ts ) { 00503 $updated = $lang->userTimeAndDate( $ts, $user ); 00504 $updateddate = $lang->userDate( $ts, $user ); 00505 $updatedtime = $lang->userTime( $ts, $user ); 00506 $out->addMeta( 'Data-Cache-Time', $ts ); 00507 $out->addJsConfigVars( 'dataCacheTime', $ts ); 00508 $out->addWikiMsg( 'perfcachedts', $updated, $updateddate, $updatedtime, $maxResults ); 00509 } else { 00510 $out->addWikiMsg( 'perfcached', $maxResults ); 00511 } 00512 00513 # If updates on this page have been disabled, let the user know 00514 # that the data set won't be refreshed for now 00515 if ( is_array( $wgDisableQueryPageUpdate ) && in_array( $this->getName(), $wgDisableQueryPageUpdate ) ) { 00516 $out->wrapWikiMsg( "<div class=\"mw-querypage-no-updates\">\n$1\n</div>", 'querypage-no-updates' ); 00517 } 00518 } 00519 } 00520 00521 $this->numRows = $res->numRows(); 00522 00523 $dbr = wfGetDB( DB_SLAVE ); 00524 $this->preprocessResults( $dbr, $res ); 00525 00526 $out->addHTML( Xml::openElement( 'div', array( 'class' => 'mw-spcontent' ) ) ); 00527 00528 # Top header and navigation 00529 if ( $this->shownavigation ) { 00530 $out->addHTML( $this->getPageHeader() ); 00531 if ( $this->numRows > 0 ) { 00532 $out->addHTML( $this->msg( 'showingresults' )->numParams( 00533 min( $this->numRows, $this->limit ), # do not show the one extra row, if exist 00534 $this->offset + 1 )->parseAsBlock() ); 00535 # Disable the "next" link when we reach the end 00536 $paging = $this->getLanguage()->viewPrevNext( $this->getTitle( $par ), $this->offset, 00537 $this->limit, $this->linkParameters(), ( $this->numRows <= $this->limit ) ); 00538 $out->addHTML( '<p>' . $paging . '</p>' ); 00539 } else { 00540 # No results to show, so don't bother with "showing X of Y" etc. 00541 # -- just let the user know and give up now 00542 $out->addWikiMsg( 'specialpage-empty' ); 00543 $out->addHTML( Xml::closeElement( 'div' ) ); 00544 return; 00545 } 00546 } 00547 00548 # The actual results; specialist subclasses will want to handle this 00549 # with more than a straight list, so we hand them the info, plus 00550 # an OutputPage, and let them get on with it 00551 $this->outputResults( $out, 00552 $this->getSkin(), 00553 $dbr, # Should use a ResultWrapper for this 00554 $res, 00555 min( $this->numRows, $this->limit ), # do not format the one extra row, if exist 00556 $this->offset ); 00557 00558 # Repeat the paging links at the bottom 00559 if ( $this->shownavigation ) { 00560 $out->addHTML( '<p>' . $paging . '</p>' ); 00561 } 00562 00563 $out->addHTML( Xml::closeElement( 'div' ) ); 00564 00565 return min( $this->numRows, $this->limit ); # do not return the one extra row, if exist 00566 } 00567 00579 protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) { 00580 global $wgContLang; 00581 00582 if ( $num > 0 ) { 00583 $html = array(); 00584 if ( !$this->listoutput ) { 00585 $html[] = $this->openList( $offset ); 00586 } 00587 00588 # $res might contain the whole 1,000 rows, so we read up to 00589 # $num [should update this to use a Pager] 00590 for ( $i = 0; $i < $num && $row = $res->fetchObject(); $i++ ) { 00591 $line = $this->formatResult( $skin, $row ); 00592 if ( $line ) { 00593 $attr = ( isset( $row->usepatrol ) && $row->usepatrol && $row->patrolled == 0 ) 00594 ? ' class="not-patrolled"' 00595 : ''; 00596 $html[] = $this->listoutput 00597 ? $line 00598 : "<li{$attr}>{$line}</li>\n"; 00599 } 00600 } 00601 00602 # Flush the final result 00603 if ( $this->tryLastResult() ) { 00604 $row = null; 00605 $line = $this->formatResult( $skin, $row ); 00606 if ( $line ) { 00607 $attr = ( isset( $row->usepatrol ) && $row->usepatrol && $row->patrolled == 0 ) 00608 ? ' class="not-patrolled"' 00609 : ''; 00610 $html[] = $this->listoutput 00611 ? $line 00612 : "<li{$attr}>{$line}</li>\n"; 00613 } 00614 } 00615 00616 if ( !$this->listoutput ) { 00617 $html[] = $this->closeList(); 00618 } 00619 00620 $html = $this->listoutput 00621 ? $wgContLang->listToText( $html ) 00622 : implode( '', $html ); 00623 00624 $out->addHTML( $html ); 00625 } 00626 } 00627 00632 function openList( $offset ) { 00633 return "\n<ol start='" . ( $offset + 1 ) . "' class='special'>\n"; 00634 } 00635 00639 function closeList() { 00640 return "</ol>\n"; 00641 } 00642 00648 function preprocessResults( $db, $res ) {} 00649 00656 function doFeed( $class = '', $limit = 50 ) { 00657 global $wgFeed, $wgFeedClasses, $wgFeedLimit; 00658 00659 if ( !$wgFeed ) { 00660 $this->getOutput()->addWikiMsg( 'feed-unavailable' ); 00661 return false; 00662 } 00663 00664 $limit = min( $limit, $wgFeedLimit ); 00665 00666 if ( isset( $wgFeedClasses[$class] ) ) { 00667 $feed = new $wgFeedClasses[$class]( 00668 $this->feedTitle(), 00669 $this->feedDesc(), 00670 $this->feedUrl() ); 00671 $feed->outHeader(); 00672 00673 $res = $this->reallyDoQuery( $limit, 0 ); 00674 foreach ( $res as $obj ) { 00675 $item = $this->feedResult( $obj ); 00676 if ( $item ) { 00677 $feed->outItem( $item ); 00678 } 00679 } 00680 00681 $feed->outFooter(); 00682 return true; 00683 } else { 00684 return false; 00685 } 00686 } 00687 00694 function feedResult( $row ) { 00695 if ( !isset( $row->title ) ) { 00696 return null; 00697 } 00698 $title = Title::makeTitle( intval( $row->namespace ), $row->title ); 00699 if ( $title ) { 00700 $date = isset( $row->timestamp ) ? $row->timestamp : ''; 00701 $comments = ''; 00702 if ( $title ) { 00703 $talkpage = $title->getTalkPage(); 00704 $comments = $talkpage->getFullURL(); 00705 } 00706 00707 return new FeedItem( 00708 $title->getPrefixedText(), 00709 $this->feedItemDesc( $row ), 00710 $title->getFullURL(), 00711 $date, 00712 $this->feedItemAuthor( $row ), 00713 $comments ); 00714 } else { 00715 return null; 00716 } 00717 } 00718 00719 function feedItemDesc( $row ) { 00720 return isset( $row->comment ) ? htmlspecialchars( $row->comment ) : ''; 00721 } 00722 00723 function feedItemAuthor( $row ) { 00724 return isset( $row->user_text ) ? $row->user_text : ''; 00725 } 00726 00727 function feedTitle() { 00728 global $wgLanguageCode, $wgSitename; 00729 $desc = $this->getDescription(); 00730 return "$wgSitename - $desc [$wgLanguageCode]"; 00731 } 00732 00733 function feedDesc() { 00734 return $this->msg( 'tagline' )->text(); 00735 } 00736 00737 function feedUrl() { 00738 return $this->getTitle()->getFullURL(); 00739 } 00740 } 00741 00746 abstract class WantedQueryPage extends QueryPage { 00747 function isExpensive() { 00748 return true; 00749 } 00750 00751 function isSyndicated() { 00752 return false; 00753 } 00754 00760 function preprocessResults( $db, $res ) { 00761 if ( !$res->numRows() ) { 00762 return; 00763 } 00764 00765 $batch = new LinkBatch; 00766 foreach ( $res as $row ) { 00767 $batch->add( $row->namespace, $row->title ); 00768 } 00769 $batch->execute(); 00770 00771 // Back to start for display 00772 $res->seek( 0 ); 00773 } 00774 00783 function forceExistenceCheck() { 00784 return false; 00785 } 00786 00794 public function formatResult( $skin, $result ) { 00795 $title = Title::makeTitleSafe( $result->namespace, $result->title ); 00796 if ( $title instanceof Title ) { 00797 if ( $this->isCached() || $this->forceExistenceCheck() ) { 00798 $pageLink = $title->isKnown() 00799 ? '<del>' . Linker::link( $title ) . '</del>' 00800 : Linker::link( 00801 $title, 00802 null, 00803 array(), 00804 array(), 00805 array( 'broken' ) 00806 ); 00807 } else { 00808 $pageLink = Linker::link( 00809 $title, 00810 null, 00811 array(), 00812 array(), 00813 array( 'broken' ) 00814 ); 00815 } 00816 return $this->getLanguage()->specialList( $pageLink, $this->makeWlhLink( $title, $result ) ); 00817 } else { 00818 return $this->msg( 'wantedpages-badtitle', $result->title )->escaped(); 00819 } 00820 } 00821 00829 private function makeWlhLink( $title, $result ) { 00830 $wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedText() ); 00831 $label = $this->msg( 'nlinks' )->numParams( $result->value )->escaped(); 00832 return Linker::link( $wlh, $label ); 00833 } 00834 }