MediaWiki
REL1_19
|
00001 <?php 00015 global $wgQueryPages; // not redundant 00016 $wgQueryPages = array( 00017 // QueryPage subclass Special page name Limit (false for none, none for the default) 00018 // ---------------------------------------------------------------------------- 00019 array( 'AncientPagesPage', 'Ancientpages' ), 00020 array( 'BrokenRedirectsPage', 'BrokenRedirects' ), 00021 array( 'DeadendPagesPage', 'Deadendpages' ), 00022 array( 'DisambiguationsPage', 'Disambiguations' ), 00023 array( 'DoubleRedirectsPage', 'DoubleRedirects' ), 00024 array( 'FileDuplicateSearchPage', 'FileDuplicateSearch' ), 00025 array( 'LinkSearchPage', 'LinkSearch' ), 00026 array( 'ListredirectsPage', 'Listredirects' ), 00027 array( 'LonelyPagesPage', 'Lonelypages' ), 00028 array( 'LongPagesPage', 'Longpages' ), 00029 array( 'MIMEsearchPage', 'MIMEsearch' ), 00030 array( 'MostcategoriesPage', 'Mostcategories' ), 00031 array( 'MostimagesPage', 'Mostimages' ), 00032 array( 'MostlinkedCategoriesPage', 'Mostlinkedcategories' ), 00033 array( 'MostlinkedtemplatesPage', 'Mostlinkedtemplates' ), 00034 array( 'MostlinkedPage', 'Mostlinked' ), 00035 array( 'MostrevisionsPage', 'Mostrevisions' ), 00036 array( 'FewestrevisionsPage', 'Fewestrevisions' ), 00037 array( 'ShortPagesPage', 'Shortpages' ), 00038 array( 'UncategorizedCategoriesPage', 'Uncategorizedcategories' ), 00039 array( 'UncategorizedPagesPage', 'Uncategorizedpages' ), 00040 array( 'UncategorizedImagesPage', 'Uncategorizedimages' ), 00041 array( 'UncategorizedTemplatesPage', 'Uncategorizedtemplates' ), 00042 array( 'UnusedCategoriesPage', 'Unusedcategories' ), 00043 array( 'UnusedimagesPage', 'Unusedimages' ), 00044 array( 'WantedCategoriesPage', 'Wantedcategories' ), 00045 array( 'WantedFilesPage', 'Wantedfiles' ), 00046 array( 'WantedPagesPage', 'Wantedpages' ), 00047 array( 'WantedTemplatesPage', 'Wantedtemplates' ), 00048 array( 'UnwatchedPagesPage', 'Unwatchedpages' ), 00049 array( 'UnusedtemplatesPage', 'Unusedtemplates' ), 00050 array( 'WithoutInterwikiPage', 'Withoutinterwiki' ), 00051 ); 00052 wfRunHooks( 'wgQueryPages', array( &$wgQueryPages ) ); 00053 00054 global $wgDisableCounters; 00055 if ( !$wgDisableCounters ) 00056 $wgQueryPages[] = array( 'PopularPagesPage', 'Popularpages' ); 00057 00058 00065 abstract class QueryPage extends SpecialPage { 00071 var $listoutput = false; 00072 00078 var $offset = 0; 00079 var $limit = 0; 00080 00086 protected $numRows; 00087 00088 protected $cachedTimestamp = null; 00089 00093 protected $shownavigation = true; 00094 00100 function setListoutput( $bool ) { 00101 $this->listoutput = $bool; 00102 } 00103 00130 function getQueryInfo() { 00131 return null; 00132 } 00133 00139 function getSQL() { 00140 /* Implement getQueryInfo() instead */ 00141 throw new MWException( "Bug in a QueryPage: doesn't implement getQueryInfo() nor getQuery() properly" ); 00142 } 00143 00151 function getOrderFields() { 00152 return array( 'value' ); 00153 } 00154 00165 function usesTimestamps() { 00166 return false; 00167 } 00168 00174 function sortDescending() { 00175 return true; 00176 } 00177 00185 function isExpensive() { 00186 global $wgDisableQueryPages; 00187 return $wgDisableQueryPages; 00188 } 00189 00197 public function isCacheable() { 00198 return true; 00199 } 00200 00207 function isCached() { 00208 global $wgMiserMode; 00209 00210 return $this->isExpensive() && $wgMiserMode; 00211 } 00212 00218 function isSyndicated() { 00219 return true; 00220 } 00221 00234 abstract function formatResult( $skin, $result ); 00235 00241 function getPageHeader() { 00242 return ''; 00243 } 00244 00252 function linkParameters() { 00253 return array(); 00254 } 00255 00263 function tryLastResult() { 00264 return false; 00265 } 00266 00273 function recache( $limit, $ignoreErrors = true ) { 00274 if ( !$this->isCacheable() ) { 00275 return 0; 00276 } 00277 00278 $fname = get_class( $this ) . '::recache'; 00279 $dbw = wfGetDB( DB_MASTER ); 00280 $dbr = wfGetDB( DB_SLAVE, array( $this->getName(), __METHOD__, 'vslow' ) ); 00281 if ( !$dbw || !$dbr ) { 00282 return false; 00283 } 00284 00285 if ( $ignoreErrors ) { 00286 $ignoreW = $dbw->ignoreErrors( true ); 00287 $ignoreR = $dbr->ignoreErrors( true ); 00288 } 00289 00290 # Clear out any old cached data 00291 $dbw->delete( 'querycache', array( 'qc_type' => $this->getName() ), $fname ); 00292 # Do query 00293 $res = $this->reallyDoQuery( $limit, false ); 00294 $num = false; 00295 if ( $res ) { 00296 $num = $dbr->numRows( $res ); 00297 # Fetch results 00298 $vals = array(); 00299 while ( $res && $row = $dbr->fetchObject( $res ) ) { 00300 if ( isset( $row->value ) ) { 00301 if ( $this->usesTimestamps() ) { 00302 $value = wfTimestamp( TS_UNIX, 00303 $row->value ); 00304 } else { 00305 $value = intval( $row->value ); // @bug 14414 00306 } 00307 } else { 00308 $value = 0; 00309 } 00310 00311 $vals[] = array( 'qc_type' => $this->getName(), 00312 'qc_namespace' => $row->namespace, 00313 'qc_title' => $row->title, 00314 'qc_value' => $value ); 00315 } 00316 00317 # Save results into the querycache table on the master 00318 if ( count( $vals ) ) { 00319 if ( !$dbw->insert( 'querycache', $vals, __METHOD__ ) ) { 00320 // Set result to false to indicate error 00321 $num = false; 00322 } 00323 } 00324 if ( $ignoreErrors ) { 00325 $dbw->ignoreErrors( $ignoreW ); 00326 $dbr->ignoreErrors( $ignoreR ); 00327 } 00328 00329 # Update the querycache_info record for the page 00330 $dbw->delete( 'querycache_info', array( 'qci_type' => $this->getName() ), $fname ); 00331 $dbw->insert( 'querycache_info', array( 'qci_type' => $this->getName(), 'qci_timestamp' => $dbw->timestamp() ), $fname ); 00332 00333 } 00334 return $num; 00335 } 00336 00344 function reallyDoQuery( $limit, $offset = false ) { 00345 $fname = get_class( $this ) . "::reallyDoQuery"; 00346 $dbr = wfGetDB( DB_SLAVE ); 00347 $query = $this->getQueryInfo(); 00348 $order = $this->getOrderFields(); 00349 if ( $this->sortDescending() ) { 00350 foreach ( $order as &$field ) { 00351 $field .= ' DESC'; 00352 } 00353 } 00354 if ( is_array( $query ) ) { 00355 $tables = isset( $query['tables'] ) ? (array)$query['tables'] : array(); 00356 $fields = isset( $query['fields'] ) ? (array)$query['fields'] : array(); 00357 $conds = isset( $query['conds'] ) ? (array)$query['conds'] : array(); 00358 $options = isset( $query['options'] ) ? (array)$query['options'] : array(); 00359 $join_conds = isset( $query['join_conds'] ) ? (array)$query['join_conds'] : array(); 00360 if ( count( $order ) ) { 00361 $options['ORDER BY'] = implode( ', ', $order ); 00362 } 00363 if ( $limit !== false ) { 00364 $options['LIMIT'] = intval( $limit ); 00365 } 00366 if ( $offset !== false ) { 00367 $options['OFFSET'] = intval( $offset ); 00368 } 00369 00370 $res = $dbr->select( $tables, $fields, $conds, $fname, 00371 $options, $join_conds 00372 ); 00373 } else { 00374 // Old-fashioned raw SQL style, deprecated 00375 $sql = $this->getSQL(); 00376 $sql .= ' ORDER BY ' . implode( ', ', $order ); 00377 $sql = $dbr->limitResult( $sql, $limit, $offset ); 00378 $res = $dbr->query( $sql, $fname ); 00379 } 00380 return $dbr->resultObject( $res ); 00381 } 00382 00386 function doQuery( $offset = false, $limit = false ) { 00387 if ( $this->isCached() && $this->isCacheable() ) { 00388 return $this->fetchFromCache( $limit, $offset ); 00389 } else { 00390 return $this->reallyDoQuery( $limit, $offset ); 00391 } 00392 } 00393 00401 function fetchFromCache( $limit, $offset = false ) { 00402 $dbr = wfGetDB( DB_SLAVE ); 00403 $options = array (); 00404 if ( $limit !== false ) { 00405 $options['LIMIT'] = intval( $limit ); 00406 } 00407 if ( $offset !== false ) { 00408 $options['OFFSET'] = intval( $offset ); 00409 } 00410 if ( $this->sortDescending() ) { 00411 $options['ORDER BY'] = 'qc_value DESC'; 00412 } else { 00413 $options['ORDER BY'] = 'qc_value ASC'; 00414 } 00415 $res = $dbr->select( 'querycache', array( 'qc_type', 00416 'qc_namespace AS namespace', 00417 'qc_title AS title', 00418 'qc_value AS value' ), 00419 array( 'qc_type' => $this->getName() ), 00420 __METHOD__, $options 00421 ); 00422 return $dbr->resultObject( $res ); 00423 } 00424 00425 public function getCachedTimestamp() { 00426 if ( is_null( $this->cachedTimestamp ) ) { 00427 $dbr = wfGetDB( DB_SLAVE ); 00428 $fname = get_class( $this ) . '::getCachedTimestamp'; 00429 $this->cachedTimestamp = $dbr->selectField( 'querycache_info', 'qci_timestamp', 00430 array( 'qci_type' => $this->getName() ), $fname ); 00431 } 00432 return $this->cachedTimestamp; 00433 } 00434 00439 function execute( $par ) { 00440 global $wgQueryCacheLimit, $wgDisableQueryPageUpdate; 00441 00442 $user = $this->getUser(); 00443 if ( !$this->userCanExecute( $user ) ) { 00444 $this->displayRestrictionError(); 00445 return; 00446 } 00447 00448 $this->setHeaders(); 00449 $this->outputHeader(); 00450 00451 $out = $this->getOutput(); 00452 00453 if ( $this->isCached() && !$this->isCacheable() ) { 00454 $out->addWikiMsg( 'querypage-disabled' ); 00455 return 0; 00456 } 00457 00458 $out->setSyndicated( $this->isSyndicated() ); 00459 00460 if ( $this->limit == 0 && $this->offset == 0 ) { 00461 list( $this->limit, $this->offset ) = $this->getRequest()->getLimitOffset(); 00462 } 00463 00464 // TODO: Use doQuery() 00465 if ( !$this->isCached() ) { 00466 $res = $this->reallyDoQuery( $this->limit, $this->offset ); 00467 } else { 00468 # Get the cached result 00469 $res = $this->fetchFromCache( $this->limit, $this->offset ); 00470 if ( !$this->listoutput ) { 00471 00472 # Fetch the timestamp of this update 00473 $ts = $this->getCachedTimestamp(); 00474 $lang = $this->getLanguage(); 00475 $maxResults = $lang->formatNum( $wgQueryCacheLimit ); 00476 00477 if ( $ts ) { 00478 $updated = $lang->userTimeAndDate( $ts, $user ); 00479 $updateddate = $lang->userDate( $ts, $user ); 00480 $updatedtime = $lang->userTime( $ts, $user ); 00481 $out->addMeta( 'Data-Cache-Time', $ts ); 00482 $out->addInlineScript( "var dataCacheTime = '$ts';" ); 00483 $out->addWikiMsg( 'perfcachedts', $updated, $updateddate, $updatedtime, $maxResults ); 00484 } else { 00485 $out->addWikiMsg( 'perfcached', $maxResults ); 00486 } 00487 00488 # If updates on this page have been disabled, let the user know 00489 # that the data set won't be refreshed for now 00490 if ( is_array( $wgDisableQueryPageUpdate ) && in_array( $this->getName(), $wgDisableQueryPageUpdate ) ) { 00491 $out->addWikiMsg( 'querypage-no-updates' ); 00492 } 00493 } 00494 } 00495 00496 $this->numRows = $res->numRows(); 00497 00498 $dbr = wfGetDB( DB_SLAVE ); 00499 $this->preprocessResults( $dbr, $res ); 00500 00501 $out->addHTML( Xml::openElement( 'div', array( 'class' => 'mw-spcontent' ) ) ); 00502 00503 # Top header and navigation 00504 if ( $this->shownavigation ) { 00505 $out->addHTML( $this->getPageHeader() ); 00506 if ( $this->numRows > 0 ) { 00507 $out->addHTML( $this->msg( 'showingresults' )->numParams( 00508 $this->numRows, $this->offset + 1 )->parseAsBlock() ); 00509 # Disable the "next" link when we reach the end 00510 $paging = $this->getLanguage()->viewPrevNext( $this->getTitle( $par ), $this->offset, 00511 $this->limit, $this->linkParameters(), ( $this->numRows < $this->limit ) ); 00512 $out->addHTML( '<p>' . $paging . '</p>' ); 00513 } else { 00514 # No results to show, so don't bother with "showing X of Y" etc. 00515 # -- just let the user know and give up now 00516 $out->addWikiMsg( 'specialpage-empty' ); 00517 $out->addHTML( Xml::closeElement( 'div' ) ); 00518 return; 00519 } 00520 } 00521 00522 # The actual results; specialist subclasses will want to handle this 00523 # with more than a straight list, so we hand them the info, plus 00524 # an OutputPage, and let them get on with it 00525 $this->outputResults( $out, 00526 $this->getSkin(), 00527 $dbr, # Should use a ResultWrapper for this 00528 $res, 00529 $this->numRows, 00530 $this->offset ); 00531 00532 # Repeat the paging links at the bottom 00533 if ( $this->shownavigation ) { 00534 $out->addHTML( '<p>' . $paging . '</p>' ); 00535 } 00536 00537 $out->addHTML( Xml::closeElement( 'div' ) ); 00538 00539 return $this->numRows; 00540 } 00541 00553 protected function outputResults( $out, $skin, $dbr, $res, $num, $offset ) { 00554 global $wgContLang; 00555 00556 if ( $num > 0 ) { 00557 $html = array(); 00558 if ( !$this->listoutput ) { 00559 $html[] = $this->openList( $offset ); 00560 } 00561 00562 # $res might contain the whole 1,000 rows, so we read up to 00563 # $num [should update this to use a Pager] 00564 for ( $i = 0; $i < $num && $row = $dbr->fetchObject( $res ); $i++ ) { 00565 $line = $this->formatResult( $skin, $row ); 00566 if ( $line ) { 00567 $attr = ( isset( $row->usepatrol ) && $row->usepatrol && $row->patrolled == 0 ) 00568 ? ' class="not-patrolled"' 00569 : ''; 00570 $html[] = $this->listoutput 00571 ? $line 00572 : "<li{$attr}>{$line}</li>\n"; 00573 } 00574 } 00575 00576 # Flush the final result 00577 if ( $this->tryLastResult() ) { 00578 $row = null; 00579 $line = $this->formatResult( $skin, $row ); 00580 if ( $line ) { 00581 $attr = ( isset( $row->usepatrol ) && $row->usepatrol && $row->patrolled == 0 ) 00582 ? ' class="not-patrolled"' 00583 : ''; 00584 $html[] = $this->listoutput 00585 ? $line 00586 : "<li{$attr}>{$line}</li>\n"; 00587 } 00588 } 00589 00590 if ( !$this->listoutput ) { 00591 $html[] = $this->closeList(); 00592 } 00593 00594 $html = $this->listoutput 00595 ? $wgContLang->listToText( $html ) 00596 : implode( '', $html ); 00597 00598 $out->addHTML( $html ); 00599 } 00600 } 00601 00606 function openList( $offset ) { 00607 return "\n<ol start='" . ( $offset + 1 ) . "' class='special'>\n"; 00608 } 00609 00613 function closeList() { 00614 return "</ol>\n"; 00615 } 00616 00620 function preprocessResults( $db, $res ) {} 00621 00625 function doFeed( $class = '', $limit = 50 ) { 00626 global $wgFeed, $wgFeedClasses; 00627 00628 if ( !$wgFeed ) { 00629 $this->getOutput()->addWikiMsg( 'feed-unavailable' ); 00630 return; 00631 } 00632 00633 global $wgFeedLimit; 00634 if ( $limit > $wgFeedLimit ) { 00635 $limit = $wgFeedLimit; 00636 } 00637 00638 if ( isset( $wgFeedClasses[$class] ) ) { 00639 $feed = new $wgFeedClasses[$class]( 00640 $this->feedTitle(), 00641 $this->feedDesc(), 00642 $this->feedUrl() ); 00643 $feed->outHeader(); 00644 00645 $res = $this->reallyDoQuery( $limit, 0 ); 00646 foreach ( $res as $obj ) { 00647 $item = $this->feedResult( $obj ); 00648 if ( $item ) { 00649 $feed->outItem( $item ); 00650 } 00651 } 00652 00653 $feed->outFooter(); 00654 return true; 00655 } else { 00656 return false; 00657 } 00658 } 00659 00664 function feedResult( $row ) { 00665 if ( !isset( $row->title ) ) { 00666 return null; 00667 } 00668 $title = Title::MakeTitle( intval( $row->namespace ), $row->title ); 00669 if ( $title ) { 00670 $date = isset( $row->timestamp ) ? $row->timestamp : ''; 00671 $comments = ''; 00672 if ( $title ) { 00673 $talkpage = $title->getTalkPage(); 00674 $comments = $talkpage->getFullURL(); 00675 } 00676 00677 return new FeedItem( 00678 $title->getPrefixedText(), 00679 $this->feedItemDesc( $row ), 00680 $title->getFullURL(), 00681 $date, 00682 $this->feedItemAuthor( $row ), 00683 $comments ); 00684 } else { 00685 return null; 00686 } 00687 } 00688 00689 function feedItemDesc( $row ) { 00690 return isset( $row->comment ) ? htmlspecialchars( $row->comment ) : ''; 00691 } 00692 00693 function feedItemAuthor( $row ) { 00694 return isset( $row->user_text ) ? $row->user_text : ''; 00695 } 00696 00697 function feedTitle() { 00698 global $wgLanguageCode, $wgSitename; 00699 $desc = $this->getDescription(); 00700 return "$wgSitename - $desc [$wgLanguageCode]"; 00701 } 00702 00703 function feedDesc() { 00704 return $this->msg( 'tagline' )->text(); 00705 } 00706 00707 function feedUrl() { 00708 return $this->getTitle()->getFullURL(); 00709 } 00710 } 00711 00716 abstract class WantedQueryPage extends QueryPage { 00717 00718 function isExpensive() { 00719 return true; 00720 } 00721 00722 function isSyndicated() { 00723 return false; 00724 } 00725 00729 function preprocessResults( $db, $res ) { 00730 $batch = new LinkBatch; 00731 foreach ( $res as $row ) { 00732 $batch->add( $row->namespace, $row->title ); 00733 } 00734 $batch->execute(); 00735 00736 // Back to start for display 00737 if ( $db->numRows( $res ) > 0 ) 00738 // If there are no rows we get an error seeking. 00739 $db->dataSeek( $res, 0 ); 00740 } 00741 00749 function forceExistenceCheck() { 00750 return false; 00751 } 00752 00760 public function formatResult( $skin, $result ) { 00761 $title = Title::makeTitleSafe( $result->namespace, $result->title ); 00762 if ( $title instanceof Title ) { 00763 if ( $this->isCached() || $this->forceExistenceCheck() ) { 00764 $pageLink = $title->isKnown() 00765 ? '<del>' . Linker::link( $title ) . '</del>' 00766 : Linker::link( 00767 $title, 00768 null, 00769 array(), 00770 array(), 00771 array( 'broken' ) 00772 ); 00773 } else { 00774 $pageLink = Linker::link( 00775 $title, 00776 null, 00777 array(), 00778 array(), 00779 array( 'broken' ) 00780 ); 00781 } 00782 return $this->getLanguage()->specialList( $pageLink, $this->makeWlhLink( $title, $result ) ); 00783 } else { 00784 return $this->msg( 'wantedpages-badtitle', $result->title )->escaped(); 00785 } 00786 } 00787 00795 private function makeWlhLink( $title, $result ) { 00796 $wlh = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedText() ); 00797 $label = $this->msg( 'nlinks' )->numParams( $result->value )->escaped(); 00798 return Linker::link( $wlh, $label ); 00799 } 00800 }