MediaWiki
master
|
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 00157 function getSQL() { 00158 /* Implement getQueryInfo() instead */ 00159 throw new MWException( "Bug in a QueryPage: doesn't implement getQueryInfo() nor getQuery() properly" ); 00160 } 00161 00169 function getOrderFields() { 00170 return array( 'value' ); 00171 } 00172 00183 function usesTimestamps() { 00184 return false; 00185 } 00186 00192 function sortDescending() { 00193 return true; 00194 } 00195 00203 function isExpensive() { 00204 global $wgDisableQueryPages; 00205 return $wgDisableQueryPages; 00206 } 00207 00215 public function isCacheable() { 00216 return true; 00217 } 00218 00225 function isCached() { 00226 global $wgMiserMode; 00227 00228 return $this->isExpensive() && $wgMiserMode; 00229 } 00230 00236 function isSyndicated() { 00237 return true; 00238 } 00239 00252 abstract function formatResult( $skin, $result ); 00253 00259 function getPageHeader() { 00260 return ''; 00261 } 00262 00270 function linkParameters() { 00271 return array(); 00272 } 00273 00282 function tryLastResult() { 00283 return false; 00284 } 00285 00293 function recache( $limit, $ignoreErrors = true ) { 00294 if ( !$this->isCacheable() ) { 00295 return 0; 00296 } 00297 00298 $fname = get_class( $this ) . '::recache'; 00299 $dbw = wfGetDB( DB_MASTER ); 00300 $dbr = wfGetDB( DB_SLAVE, array( $this->getName(), __METHOD__, 'vslow' ) ); 00301 if ( !$dbw || !$dbr ) { 00302 return false; 00303 } 00304 00305 if ( $ignoreErrors ) { 00306 $ignoreW = $dbw->ignoreErrors( true ); 00307 $ignoreR = $dbr->ignoreErrors( true ); 00308 } 00309 00310 # Clear out any old cached data 00311 $dbw->delete( 'querycache', array( 'qc_type' => $this->getName() ), $fname ); 00312 # Do query 00313 $res = $this->reallyDoQuery( $limit, false ); 00314 $num = false; 00315 if ( $res ) { 00316 $num = $res->numRows(); 00317 # Fetch results 00318 $vals = array(); 00319 while ( $res && $row = $dbr->fetchObject( $res ) ) { 00320 if ( isset( $row->value ) ) { 00321 if ( $this->usesTimestamps() ) { 00322 $value = wfTimestamp( TS_UNIX, 00323 $row->value ); 00324 } else { 00325 $value = intval( $row->value ); // @bug 14414 00326 } 00327 } else { 00328 $value = 0; 00329 } 00330 00331 $vals[] = array( 'qc_type' => $this->getName(), 00332 'qc_namespace' => $row->namespace, 00333 'qc_title' => $row->title, 00334 'qc_value' => $value ); 00335 } 00336 00337 # Save results into the querycache table on the master 00338 if ( count( $vals ) ) { 00339 if ( !$dbw->insert( 'querycache', $vals, __METHOD__ ) ) { 00340 // Set result to false to indicate error 00341 $num = false; 00342 } 00343 } 00344 if ( $ignoreErrors ) { 00345 $dbw->ignoreErrors( $ignoreW ); 00346 $dbr->ignoreErrors( $ignoreR ); 00347 } 00348 00349 # Update the querycache_info record for the page 00350 $dbw->delete( 'querycache_info', array( 'qci_type' => $this->getName() ), $fname ); 00351 $dbw->insert( 'querycache_info', array( 'qci_type' => $this->getName(), 'qci_timestamp' => $dbw->timestamp() ), $fname ); 00352 00353 } 00354 return $num; 00355 } 00356 00364 function reallyDoQuery( $limit, $offset = false ) { 00365 $fname = get_class( $this ) . "::reallyDoQuery"; 00366 $dbr = wfGetDB( DB_SLAVE ); 00367 $query = $this->getQueryInfo(); 00368 $order = $this->getOrderFields(); 00369 if ( $this->sortDescending() ) { 00370 foreach ( $order as &$field ) { 00371 $field .= ' DESC'; 00372 } 00373 } 00374 if ( is_array( $query ) ) { 00375 $tables = isset( $query['tables'] ) ? (array)$query['tables'] : array(); 00376 $fields = isset( $query['fields'] ) ? (array)$query['fields'] : array(); 00377 $conds = isset( $query['conds'] ) ? (array)$query['conds'] : array(); 00378 $options = isset( $query['options'] ) ? (array)$query['options'] : array(); 00379 $join_conds = isset( $query['join_conds'] ) ? (array)$query['join_conds'] : array(); 00380 if ( count( $order ) ) { 00381 $options['ORDER BY'] = $order; 00382 } 00383 if ( $limit !== false ) { 00384 $options['LIMIT'] = intval( $limit ); 00385 } 00386 if ( $offset !== false ) { 00387 $options['OFFSET'] = intval( $offset ); 00388 } 00389 00390 $res = $dbr->select( $tables, $fields, $conds, $fname, 00391 $options, $join_conds 00392 ); 00393 } else { 00394 // Old-fashioned raw SQL style, deprecated 00395 $sql = $this->getSQL(); 00396 $sql .= ' ORDER BY ' . implode( ', ', $order ); 00397 $sql = $dbr->limitResult( $sql, $limit, $offset ); 00398 $res = $dbr->query( $sql, $fname ); 00399 } 00400 return $dbr->resultObject( $res ); 00401 } 00402 00407 function doQuery( $offset = false, $limit = false ) { 00408 if ( $this->isCached() && $this->isCacheable() ) { 00409 return $this->fetchFromCache( $limit, $offset ); 00410 } else { 00411 return $this->reallyDoQuery( $limit, $offset ); 00412 } 00413 } 00414 00422 function fetchFromCache( $limit, $offset = false ) { 00423 $dbr = wfGetDB( DB_SLAVE ); 00424 $options = array (); 00425 if ( $limit !== false ) { 00426 $options['LIMIT'] = intval( $limit ); 00427 } 00428 if ( $offset !== false ) { 00429 $options['OFFSET'] = intval( $offset ); 00430 } 00431 if ( $this->sortDescending() ) { 00432 $options['ORDER BY'] = 'qc_value DESC'; 00433 } else { 00434 $options['ORDER BY'] = 'qc_value ASC'; 00435 } 00436 $res = $dbr->select( 'querycache', array( 'qc_type', 00437 'namespace' => 'qc_namespace', 00438 'title' => 'qc_title', 00439 'value' => 'qc_value' ), 00440 array( 'qc_type' => $this->getName() ), 00441 __METHOD__, $options 00442 ); 00443 return $dbr->resultObject( $res ); 00444 } 00445 00446 public function getCachedTimestamp() { 00447 if ( is_null( $this->cachedTimestamp ) ) { 00448 $dbr = wfGetDB( DB_SLAVE ); 00449 $fname = get_class( $this ) . '::getCachedTimestamp'; 00450 $this->cachedTimestamp = $dbr->selectField( 'querycache_info', 'qci_timestamp', 00451 array( 'qci_type' => $this->getName() ), $fname ); 00452 } 00453 return $this->cachedTimestamp; 00454 } 00455 00461 function execute( $par ) { 00462 global $wgQueryCacheLimit, $wgDisableQueryPageUpdate; 00463 00464 $user = $this->getUser(); 00465 if ( !$this->userCanExecute( $user ) ) { 00466 $this->displayRestrictionError(); 00467 return; 00468 } 00469 00470 $this->setHeaders(); 00471 $this->outputHeader(); 00472 00473 $out = $this->getOutput(); 00474 00475 if ( $this->isCached() && !$this->isCacheable() ) { 00476 $out->addWikiMsg( 'querypage-disabled' ); 00477 return 0; 00478 } 00479 00480 $out->setSyndicated( $this->isSyndicated() ); 00481 00482 if ( $this->limit == 0 && $this->offset == 0 ) { 00483 list( $this->limit, $this->offset ) = $this->getRequest()->getLimitOffset(); 00484 } 00485 00486 // TODO: Use doQuery() 00487 if ( !$this->isCached() ) { 00488 # select one extra row for navigation 00489 $res = $this->reallyDoQuery( $this->limit + 1, $this->offset ); 00490 } else { 00491 # Get the cached result, select one extra row for navigation 00492 $res = $this->fetchFromCache( $this->limit + 1, $this->offset ); 00493 if ( !$this->listoutput ) { 00494 00495 # Fetch the timestamp of this update 00496 $ts = $this->getCachedTimestamp(); 00497 $lang = $this->getLanguage(); 00498 $maxResults = $lang->formatNum( $wgQueryCacheLimit ); 00499 00500 if ( $ts ) { 00501 $updated = $lang->userTimeAndDate( $ts, $user ); 00502 $updateddate = $lang->userDate( $ts, $user ); 00503 $updatedtime = $lang->userTime( $ts, $user ); 00504 $out->addMeta( 'Data-Cache-Time', $ts ); 00505 $out->addJsConfigVars( 'dataCacheTime', $ts ); 00506 $out->addWikiMsg( 'perfcachedts', $updated, $updateddate, $updatedtime, $maxResults ); 00507 } else { 00508 $out->addWikiMsg( 'perfcached', $maxResults ); 00509 } 00510 00511 # If updates on this page have been disabled, let the user know 00512 # that the data set won't be refreshed for now 00513 if ( is_array( $wgDisableQueryPageUpdate ) && in_array( $this->getName(), $wgDisableQueryPageUpdate ) ) { 00514 $out->wrapWikiMsg( "<div class=\"mw-querypage-no-updates\">\n$1\n</div>", 'querypage-no-updates' ); 00515 } 00516 } 00517 } 00518 00519 $this->numRows = $res->numRows(); 00520 00521 $dbr = wfGetDB( DB_SLAVE ); 00522 $this->preprocessResults( $dbr, $res ); 00523 00524 $out->addHTML( Xml::openElement( 'div', array( 'class' => 'mw-spcontent' ) ) ); 00525 00526 # Top header and navigation 00527 if ( $this->shownavigation ) { 00528 $out->addHTML( $this->getPageHeader() ); 00529 if ( $this->numRows > 0 ) { 00530 $out->addHTML( $this->msg( 'showingresults' )->numParams( 00531 min( $this->numRows, $this->limit ), # do not show the one extra row, if exist 00532 $this->offset + 1 )->parseAsBlock() ); 00533 # Disable the "next" link when we reach the end 00534 $paging = $this->getLanguage()->viewPrevNext( $this->getTitle( $par ), $this->offset, 00535 $this->limit, $this->linkParameters(), ( $this->numRows <= $this->limit ) ); 00536 $out->addHTML( '<p>' . $paging . '</p>' ); 00537 } else { 00538 # No results to show, so don't bother with "showing X of Y" etc. 00539 # -- just let the user know and give up now 00540 $out->addWikiMsg( 'specialpage-empty' ); 00541 $out->addHTML( Xml::closeElement( 'div' ) ); 00542 return; 00543 } 00544 } 00545 00546 # The actual results; specialist subclasses will want to handle this 00547 # with more than a straight list, so we hand them the info, plus 00548 # an OutputPage, and let them get on with it 00549 $this->outputResults( $out, 00550 $this->getSkin(), 00551 $dbr, # Should use a ResultWrapper for this 00552 $res, 00553 min( $this->numRows, $this->limit ), # do not format the one extra row, if exist 00554 $this->offset ); 00555 00556 # Repeat the paging links at the bottom 00557 if ( $this->shownavigation ) { 00558 $out->addHTML( '<p>' . $paging . '</p>' ); 00559 } 00560 00561 $out->addHTML( Xml::closeElement( 'div' ) ); 00562 00563 return min( $this->numRows, $this->limit ); # do not return the one extra row, if exist 00564 } 00565 00577 protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) { 00578 global $wgContLang; 00579 00580 if ( $num > 0 ) { 00581 $html = array(); 00582 if ( !$this->listoutput ) { 00583 $html[] = $this->openList( $offset ); 00584 } 00585 00586 # $res might contain the whole 1,000 rows, so we read up to 00587 # $num [should update this to use a Pager] 00588 for ( $i = 0; $i < $num && $row = $dbr->fetchObject( $res ); $i++ ) { 00589 $line = $this->formatResult( $skin, $row ); 00590 if ( $line ) { 00591 $attr = ( isset( $row->usepatrol ) && $row->usepatrol && $row->patrolled == 0 ) 00592 ? ' class="not-patrolled"' 00593 : ''; 00594 $html[] = $this->listoutput 00595 ? $line 00596 : "<li{$attr}>{$line}</li>\n"; 00597 } 00598 } 00599 00600 # Flush the final result 00601 if ( $this->tryLastResult() ) { 00602 $row = null; 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 if ( !$this->listoutput ) { 00615 $html[] = $this->closeList(); 00616 } 00617 00618 $html = $this->listoutput 00619 ? $wgContLang->listToText( $html ) 00620 : implode( '', $html ); 00621 00622 $out->addHTML( $html ); 00623 } 00624 } 00625 00630 function openList( $offset ) { 00631 return "\n<ol start='" . ( $offset + 1 ) . "' class='special'>\n"; 00632 } 00633 00637 function closeList() { 00638 return "</ol>\n"; 00639 } 00640 00644 function preprocessResults( $db, $res ) {} 00645 00650 function doFeed( $class = '', $limit = 50 ) { 00651 global $wgFeed, $wgFeedClasses; 00652 00653 if ( !$wgFeed ) { 00654 $this->getOutput()->addWikiMsg( 'feed-unavailable' ); 00655 return; 00656 } 00657 00658 global $wgFeedLimit; 00659 if ( $limit > $wgFeedLimit ) { 00660 $limit = $wgFeedLimit; 00661 } 00662 00663 if ( isset( $wgFeedClasses[$class] ) ) { 00664 $feed = new $wgFeedClasses[$class]( 00665 $this->feedTitle(), 00666 $this->feedDesc(), 00667 $this->feedUrl() ); 00668 $feed->outHeader(); 00669 00670 $res = $this->reallyDoQuery( $limit, 0 ); 00671 foreach ( $res as $obj ) { 00672 $item = $this->feedResult( $obj ); 00673 if ( $item ) { 00674 $feed->outItem( $item ); 00675 } 00676 } 00677 00678 $feed->outFooter(); 00679 return true; 00680 } else { 00681 return false; 00682 } 00683 } 00684 00690 function feedResult( $row ) { 00691 if ( !isset( $row->title ) ) { 00692 return null; 00693 } 00694 $title = Title::makeTitle( intval( $row->namespace ), $row->title ); 00695 if ( $title ) { 00696 $date = isset( $row->timestamp ) ? $row->timestamp : ''; 00697 $comments = ''; 00698 if ( $title ) { 00699 $talkpage = $title->getTalkPage(); 00700 $comments = $talkpage->getFullURL(); 00701 } 00702 00703 return new FeedItem( 00704 $title->getPrefixedText(), 00705 $this->feedItemDesc( $row ), 00706 $title->getFullURL(), 00707 $date, 00708 $this->feedItemAuthor( $row ), 00709 $comments ); 00710 } else { 00711 return null; 00712 } 00713 } 00714 00715 function feedItemDesc( $row ) { 00716 return isset( $row->comment ) ? htmlspecialchars( $row->comment ) : ''; 00717 } 00718 00719 function feedItemAuthor( $row ) { 00720 return isset( $row->user_text ) ? $row->user_text : ''; 00721 } 00722 00723 function feedTitle() { 00724 global $wgLanguageCode, $wgSitename; 00725 $desc = $this->getDescription(); 00726 return "$wgSitename - $desc [$wgLanguageCode]"; 00727 } 00728 00729 function feedDesc() { 00730 return $this->msg( 'tagline' )->text(); 00731 } 00732 00733 function feedUrl() { 00734 return $this->getTitle()->getFullURL(); 00735 } 00736 } 00737 00742 abstract class WantedQueryPage extends QueryPage { 00743 00744 function isExpensive() { 00745 return true; 00746 } 00747 00748 function isSyndicated() { 00749 return false; 00750 } 00751 00755 function preprocessResults( $db, $res ) { 00756 if ( !$res->numRows() ) { 00757 return; 00758 } 00759 00760 $batch = new LinkBatch; 00761 foreach ( $res as $row ) { 00762 $batch->add( $row->namespace, $row->title ); 00763 } 00764 $batch->execute(); 00765 00766 // Back to start for display 00767 $res->seek( 0 ); 00768 } 00769 00778 function forceExistenceCheck() { 00779 return false; 00780 } 00781 00789 public function formatResult( $skin, $result ) { 00790 $title = Title::makeTitleSafe( $result->namespace, $result->title ); 00791 if ( $title instanceof Title ) { 00792 if ( $this->isCached() || $this->forceExistenceCheck() ) { 00793 $pageLink = $title->isKnown() 00794 ? '<del>' . Linker::link( $title ) . '</del>' 00795 : Linker::link( 00796 $title, 00797 null, 00798 array(), 00799 array(), 00800 array( 'broken' ) 00801 ); 00802 } else { 00803 $pageLink = Linker::link( 00804 $title, 00805 null, 00806 array(), 00807 array(), 00808 array( 'broken' ) 00809 ); 00810 } 00811 return $this->getLanguage()->specialList( $pageLink, $this->makeWlhLink( $title, $result ) ); 00812 } else { 00813 return $this->msg( 'wantedpages-badtitle', $result->title )->escaped(); 00814 } 00815 } 00816 00824 private function makeWlhLink( $title, $result ) { 00825 $wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedText() ); 00826 $label = $this->msg( 'nlinks' )->numParams( $result->value )->escaped(); 00827 return Linker::link( $wlh, $label ); 00828 } 00829 }