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