MediaWiki
REL1_20
|
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 }