MediaWiki
REL1_22
|
00001 <?php 00029 class SpecialAllpages extends IncludableSpecialPage { 00030 00036 protected $maxPerPage = 345; 00037 00043 protected $maxLineCount = 100; 00044 00050 protected $maxPageLength = 70; 00051 00057 protected $nsfromMsg = 'allpagesfrom'; 00058 00064 function __construct( $name = 'Allpages' ) { 00065 parent::__construct( $name ); 00066 } 00067 00073 function execute( $par ) { 00074 $request = $this->getRequest(); 00075 $out = $this->getOutput(); 00076 00077 $this->setHeaders(); 00078 $this->outputHeader(); 00079 $out->allowClickjacking(); 00080 00081 # GET values 00082 $from = $request->getVal( 'from', null ); 00083 $to = $request->getVal( 'to', null ); 00084 $namespace = $request->getInt( 'namespace' ); 00085 $hideredirects = $request->getBool( 'hideredirects', false ); 00086 00087 $namespaces = $this->getContext()->getLanguage()->getNamespaces(); 00088 00089 $out->setPageTitle( 00090 ( $namespace > 0 && in_array( $namespace, array_keys( $namespaces ) ) ) ? 00091 $this->msg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) ) : 00092 $this->msg( 'allarticles' ) 00093 ); 00094 $out->addModuleStyles( 'mediawiki.special' ); 00095 00096 if ( $par !== null ) { 00097 $this->showChunk( $namespace, $par, $to, $hideredirects ); 00098 } elseif ( $from !== null && $to === null ) { 00099 $this->showChunk( $namespace, $from, $to, $hideredirects ); 00100 } else { 00101 $this->showToplevel( $namespace, $from, $to, $hideredirects ); 00102 } 00103 } 00104 00114 function namespaceForm( $namespace = NS_MAIN, $from = '', $to = '', $hideredirects = false ) { 00115 global $wgScript; 00116 $t = $this->getTitle(); 00117 00118 $out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) ); 00119 $out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) ); 00120 $out .= Html::hidden( 'title', $t->getPrefixedText() ); 00121 $out .= Xml::openElement( 'fieldset' ); 00122 $out .= Xml::element( 'legend', null, $this->msg( 'allpages' )->text() ); 00123 $out .= Xml::openElement( 'table', array( 'id' => 'nsselect', 'class' => 'allpages' ) ); 00124 $out .= "<tr> 00125 <td class='mw-label'>" . 00126 Xml::label( $this->msg( 'allpagesfrom' )->text(), 'nsfrom' ) . 00127 " </td> 00128 <td class='mw-input'>" . 00129 Xml::input( 'from', 30, str_replace( '_', ' ', $from ), array( 'id' => 'nsfrom' ) ) . 00130 " </td> 00131 </tr> 00132 <tr> 00133 <td class='mw-label'>" . 00134 Xml::label( $this->msg( 'allpagesto' )->text(), 'nsto' ) . 00135 " </td> 00136 <td class='mw-input'>" . 00137 Xml::input( 'to', 30, str_replace( '_', ' ', $to ), array( 'id' => 'nsto' ) ) . 00138 " </td> 00139 </tr> 00140 <tr> 00141 <td class='mw-label'>" . 00142 Xml::label( $this->msg( 'namespace' )->text(), 'namespace' ) . 00143 " </td> 00144 <td class='mw-input'>" . 00145 Html::namespaceSelector( 00146 array( 'selected' => $namespace ), 00147 array( 'name' => 'namespace', 'id' => 'namespace' ) 00148 ) . ' ' . 00149 Xml::checkLabel( 00150 $this->msg( 'allpages-hide-redirects' )->text(), 00151 'hideredirects', 00152 'hideredirects', 00153 $hideredirects 00154 ) . ' ' . 00155 Xml::submitButton( $this->msg( 'allpagessubmit' )->text() ) . 00156 " </td> 00157 </tr>"; 00158 $out .= Xml::closeElement( 'table' ); 00159 $out .= Xml::closeElement( 'fieldset' ); 00160 $out .= Xml::closeElement( 'form' ); 00161 $out .= Xml::closeElement( 'div' ); 00162 00163 return $out; 00164 } 00165 00172 function showToplevel( $namespace = NS_MAIN, $from = '', $to = '', $hideredirects = false ) { 00173 $output = $this->getOutput(); 00174 00175 # TODO: Either make this *much* faster or cache the title index points 00176 # in the querycache table. 00177 00178 $dbr = wfGetDB( DB_SLAVE ); 00179 $out = ""; 00180 $where = array( 'page_namespace' => $namespace ); 00181 00182 if ( $hideredirects ) { 00183 $where['page_is_redirect'] = 0; 00184 } 00185 00186 $from = Title::makeTitleSafe( $namespace, $from ); 00187 $to = Title::makeTitleSafe( $namespace, $to ); 00188 $from = ( $from && $from->isLocal() ) ? $from->getDBkey() : null; 00189 $to = ( $to && $to->isLocal() ) ? $to->getDBkey() : null; 00190 00191 if ( isset( $from ) ) { 00192 $where[] = 'page_title >= ' . $dbr->addQuotes( $from ); 00193 } 00194 00195 if ( isset( $to ) ) { 00196 $where[] = 'page_title <= ' . $dbr->addQuotes( $to ); 00197 } 00198 00199 global $wgMemc; 00200 $key = wfMemcKey( 'allpages', 'ns', $namespace, sha1( $from ), sha1( $to ) ); 00201 $lines = $wgMemc->get( $key ); 00202 00203 $count = $dbr->estimateRowCount( 'page', '*', $where, __METHOD__ ); 00204 $maxPerSubpage = intval( $count / $this->maxLineCount ); 00205 $maxPerSubpage = max( $maxPerSubpage, $this->maxPerPage ); 00206 00207 if ( !is_array( $lines ) ) { 00208 $options = array( 'LIMIT' => 1 ); 00209 $options['ORDER BY'] = 'page_title ASC'; 00210 $firstTitle = $dbr->selectField( 'page', 'page_title', $where, __METHOD__, $options ); 00211 $lastTitle = $firstTitle; 00212 # This array is going to hold the page_titles in order. 00213 $lines = array( $firstTitle ); 00214 # If we are going to show n rows, we need n+1 queries to find the relevant titles. 00215 $done = false; 00216 while ( !$done ) { 00217 // Fetch the last title of this chunk and the first of the next 00218 $chunk = ( $lastTitle === false ) 00219 ? array() 00220 : array( 'page_title >= ' . $dbr->addQuotes( $lastTitle ) ); 00221 $res = $dbr->select( 'page', /* FROM */ 00222 'page_title', /* WHAT */ 00223 array_merge( $where, $chunk ), 00224 __METHOD__, 00225 array( 'LIMIT' => 2, 'OFFSET' => $maxPerSubpage - 1, 'ORDER BY' => 'page_title ASC' ) 00226 ); 00227 00228 $s = $dbr->fetchObject( $res ); 00229 if ( $s ) { 00230 array_push( $lines, $s->page_title ); 00231 } else { 00232 // Final chunk, but ended prematurely. Go back and find the end. 00233 $endTitle = $dbr->selectField( 'page', 'MAX(page_title)', 00234 array_merge( $where, $chunk ), 00235 __METHOD__ ); 00236 array_push( $lines, $endTitle ); 00237 $done = true; 00238 } 00239 00240 $s = $res->fetchObject(); 00241 if ( $s ) { 00242 array_push( $lines, $s->page_title ); 00243 $lastTitle = $s->page_title; 00244 } else { 00245 // This was a final chunk and ended exactly at the limit. 00246 // Rare but convenient! 00247 $done = true; 00248 } 00249 $res->free(); 00250 } 00251 $wgMemc->add( $key, $lines, 3600 ); 00252 } 00253 00254 // If there are only two or less sections, don't even display them. 00255 // Instead, display the first section directly. 00256 if ( count( $lines ) <= 2 ) { 00257 if ( !empty( $lines ) ) { 00258 $this->showChunk( $namespace, $from, $to, $hideredirects ); 00259 } else { 00260 $output->addHTML( $this->namespaceForm( $namespace, $from, $to, $hideredirects ) ); 00261 } 00262 00263 return; 00264 } 00265 00266 # At this point, $lines should contain an even number of elements. 00267 $out .= Xml::openElement( 'table', array( 'class' => 'allpageslist' ) ); 00268 while ( count( $lines ) > 0 ) { 00269 $inpoint = array_shift( $lines ); 00270 $outpoint = array_shift( $lines ); 00271 $out .= $this->showline( $inpoint, $outpoint, $namespace, $hideredirects ); 00272 } 00273 $out .= Xml::closeElement( 'table' ); 00274 $nsForm = $this->namespaceForm( $namespace, $from, $to, $hideredirects ); 00275 00276 # Is there more? 00277 if ( $this->including() ) { 00278 $out2 = ''; 00279 } else { 00280 if ( isset( $from ) || isset( $to ) ) { 00281 $out2 = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-form' ) ) . 00282 '<tr> 00283 <td>' . 00284 $nsForm . 00285 '</td> 00286 <td class="mw-allpages-nav">' . 00287 Linker::link( $this->getTitle(), $this->msg( 'allpages' )->escaped(), 00288 array(), array(), 'known' ) . 00289 "</td> 00290 </tr>" . 00291 Xml::closeElement( 'table' ); 00292 } else { 00293 $out2 = $nsForm; 00294 } 00295 } 00296 $output->addHTML( $out2 . $out ); 00297 } 00298 00308 function showline( $inpoint, $outpoint, $namespace = NS_MAIN, $hideRedirects = false ) { 00309 // Use content language since page titles are considered to use content language 00310 global $wgContLang; 00311 00312 $inpointf = str_replace( '_', ' ', $inpoint ); 00313 $outpointf = str_replace( '_', ' ', $outpoint ); 00314 00315 // Don't let the length runaway 00316 $inpointf = $wgContLang->truncate( $inpointf, $this->maxPageLength ); 00317 $outpointf = $wgContLang->truncate( $outpointf, $this->maxPageLength ); 00318 00319 $queryParams = array( 00320 'from' => $inpoint, 00321 'to' => $outpoint, 00322 ); 00323 00324 if ( $namespace ) { 00325 $queryParams['namespace'] = $namespace; 00326 } 00327 if ( $hideRedirects ) { 00328 $queryParams['hideredirects'] = 1; 00329 } 00330 00331 $url = $this->getTitle()->getLocalURL( $queryParams ); 00332 $inlink = Html::element( 'a', array( 'href' => $url ), $inpointf ); 00333 $outlink = Html::element( 'a', array( 'href' => $url ), $outpointf ); 00334 00335 $out = $this->msg( 'alphaindexline' )->rawParams( 00336 "$inlink</td><td>", 00337 "</td><td>$outlink" 00338 )->escaped(); 00339 00340 return '<tr><td class="mw-allpages-alphaindexline">' . $out . '</td></tr>'; 00341 } 00342 00349 function showChunk( $namespace = NS_MAIN, $from = false, $to = false, $hideredirects = false ) { 00350 $output = $this->getOutput(); 00351 00352 $fromList = $this->getNamespaceKeyAndText( $namespace, $from ); 00353 $toList = $this->getNamespaceKeyAndText( $namespace, $to ); 00354 $namespaces = $this->getContext()->getLanguage()->getNamespaces(); 00355 $n = 0; 00356 00357 if ( !$fromList || !$toList ) { 00358 $out = $this->msg( 'allpagesbadtitle' )->parseAsBlock(); 00359 } elseif ( !in_array( $namespace, array_keys( $namespaces ) ) ) { 00360 // Show errormessage and reset to NS_MAIN 00361 $out = $this->msg( 'allpages-bad-ns', $namespace )->parse(); 00362 $namespace = NS_MAIN; 00363 } else { 00364 list( $namespace, $fromKey, $from ) = $fromList; 00365 list( , $toKey, $to ) = $toList; 00366 00367 $dbr = wfGetDB( DB_SLAVE ); 00368 $conds = array( 00369 'page_namespace' => $namespace, 00370 'page_title >= ' . $dbr->addQuotes( $fromKey ) 00371 ); 00372 00373 if ( $hideredirects ) { 00374 $conds['page_is_redirect'] = 0; 00375 } 00376 00377 if ( $toKey !== "" ) { 00378 $conds[] = 'page_title <= ' . $dbr->addQuotes( $toKey ); 00379 } 00380 00381 $res = $dbr->select( 'page', 00382 array( 'page_namespace', 'page_title', 'page_is_redirect', 'page_id' ), 00383 $conds, 00384 __METHOD__, 00385 array( 00386 'ORDER BY' => 'page_title', 00387 'LIMIT' => $this->maxPerPage + 1, 00388 'USE INDEX' => 'name_title', 00389 ) 00390 ); 00391 00392 if ( $res->numRows() > 0 ) { 00393 $out = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-chunk' ) ); 00394 while ( ( $n < $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) { 00395 $t = Title::newFromRow( $s ); 00396 if ( $t ) { 00397 $link = ( $s->page_is_redirect ? '<div class="allpagesredirect">' : '' ) . 00398 Linker::link( $t ) . 00399 ( $s->page_is_redirect ? '</div>' : '' ); 00400 } else { 00401 $link = '[[' . htmlspecialchars( $s->page_title ) . ']]'; 00402 } 00403 00404 if ( $n % 3 == 0 ) { 00405 $out .= '<tr>'; 00406 } 00407 00408 $out .= "<td style=\"width:33%\">$link</td>"; 00409 $n++; 00410 if ( $n % 3 == 0 ) { 00411 $out .= "</tr>\n"; 00412 } 00413 } 00414 00415 if ( ( $n % 3 ) != 0 ) { 00416 $out .= "</tr>\n"; 00417 } 00418 $out .= Xml::closeElement( 'table' ); 00419 } else { 00420 $out = ''; 00421 } 00422 } 00423 00424 if ( $this->including() ) { 00425 $out2 = ''; 00426 } else { 00427 if ( $from == '' ) { 00428 // First chunk; no previous link. 00429 $prevTitle = null; 00430 } else { 00431 # Get the last title from previous chunk 00432 $dbr = wfGetDB( DB_SLAVE ); 00433 $res_prev = $dbr->select( 00434 'page', 00435 'page_title', 00436 array( 'page_namespace' => $namespace, 'page_title < ' . $dbr->addQuotes( $from ) ), 00437 __METHOD__, 00438 array( 'ORDER BY' => 'page_title DESC', 00439 'LIMIT' => $this->maxPerPage, 'OFFSET' => ( $this->maxPerPage - 1 ) 00440 ) 00441 ); 00442 00443 # Get first title of previous complete chunk 00444 if ( $dbr->numrows( $res_prev ) >= $this->maxPerPage ) { 00445 $pt = $dbr->fetchObject( $res_prev ); 00446 $prevTitle = Title::makeTitle( $namespace, $pt->page_title ); 00447 } else { 00448 # The previous chunk is not complete, need to link to the very first title 00449 # available in the database 00450 $options = array( 'LIMIT' => 1 ); 00451 if ( !$dbr->implicitOrderby() ) { 00452 $options['ORDER BY'] = 'page_title'; 00453 } 00454 $reallyFirstPage_title = $dbr->selectField( 'page', 'page_title', 00455 array( 'page_namespace' => $namespace ), __METHOD__, $options ); 00456 # Show the previous link if it s not the current requested chunk 00457 if ( $from != $reallyFirstPage_title ) { 00458 $prevTitle = Title::makeTitle( $namespace, $reallyFirstPage_title ); 00459 } else { 00460 $prevTitle = null; 00461 } 00462 } 00463 } 00464 00465 $self = $this->getTitle(); 00466 00467 $nsForm = $this->namespaceForm( $namespace, $from, $to, $hideredirects ); 00468 $out2 = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-form' ) ) . 00469 '<tr> 00470 <td>' . 00471 $nsForm . 00472 '</td> 00473 <td class="mw-allpages-nav">' . 00474 Linker::link( $self, $this->msg( 'allpages' )->escaped() ); 00475 00476 # Do we put a previous link ? 00477 if ( isset( $prevTitle ) && $pt = $prevTitle->getText() ) { 00478 $query = array( 'from' => $prevTitle->getText() ); 00479 00480 if ( $namespace ) { 00481 $query['namespace'] = $namespace; 00482 } 00483 00484 if ( $hideredirects ) { 00485 $query['hideredirects'] = $hideredirects; 00486 } 00487 00488 $prevLink = Linker::linkKnown( 00489 $self, 00490 $this->msg( 'prevpage', $pt )->escaped(), 00491 array(), 00492 $query 00493 ); 00494 $out2 = $this->getLanguage()->pipeList( array( $out2, $prevLink ) ); 00495 } 00496 00497 if ( $n == $this->maxPerPage && $s = $res->fetchObject() ) { 00498 # $s is the first link of the next chunk 00499 $t = Title::makeTitle( $namespace, $s->page_title ); 00500 $query = array( 'from' => $t->getText() ); 00501 00502 if ( $namespace ) { 00503 $query['namespace'] = $namespace; 00504 } 00505 00506 if ( $hideredirects ) { 00507 $query['hideredirects'] = $hideredirects; 00508 } 00509 00510 $nextLink = Linker::linkKnown( 00511 $self, 00512 $this->msg( 'nextpage', $t->getText() )->escaped(), 00513 array(), 00514 $query 00515 ); 00516 $out2 = $this->getLanguage()->pipeList( array( $out2, $nextLink ) ); 00517 } 00518 $out2 .= "</td></tr></table>"; 00519 } 00520 00521 $output->addHTML( $out2 . $out ); 00522 00523 $links = array(); 00524 if ( isset( $prevLink ) ) { 00525 $links[] = $prevLink; 00526 } 00527 00528 if ( isset( $nextLink ) ) { 00529 $links[] = $nextLink; 00530 } 00531 00532 if ( count( $links ) ) { 00533 $output->addHTML( 00534 Html::element( 'hr' ) . 00535 Html::rawElement( 'div', array( 'class' => 'mw-allpages-nav' ), 00536 $this->getLanguage()->pipeList( $links ) 00537 ) 00538 ); 00539 } 00540 } 00541 00547 protected function getNamespaceKeyAndText( $ns, $text ) { 00548 if ( $text == '' ) { 00549 # shortcut for common case 00550 return array( $ns, '', '' ); 00551 } 00552 00553 $t = Title::makeTitleSafe( $ns, $text ); 00554 if ( $t && $t->isLocal() ) { 00555 return array( $t->getNamespace(), $t->getDBkey(), $t->getText() ); 00556 } elseif ( $t ) { 00557 return null; 00558 } 00559 00560 # try again, in case the problem was an empty pagename 00561 $text = preg_replace( '/(#|$)/', 'X$1', $text ); 00562 $t = Title::makeTitleSafe( $ns, $text ); 00563 if ( $t && $t->isLocal() ) { 00564 return array( $t->getNamespace(), '', '' ); 00565 } else { 00566 return null; 00567 } 00568 } 00569 00570 protected function getGroupName() { 00571 return 'pages'; 00572 } 00573 }