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