Fixed some doxygen warnings
[lhc/web/wiklou.git] / includes / specials / SpecialAllpages.php
1 <?php
2
3 /**
4 * Implements Special:Allpages
5 * @ingroup SpecialPage
6 */
7 class SpecialAllpages extends IncludableSpecialPage {
8
9 /**
10 * Maximum number of pages to show on single subpage.
11 */
12 protected $maxPerPage = 345;
13
14 /**
15 * Maximum number of pages to show on single index subpage.
16 */
17 protected $maxLineCount = 100;
18
19 /**
20 * Maximum number of chars to show for an entry.
21 */
22 protected $maxPageLength = 70;
23
24 /**
25 * Determines, which message describes the input field 'nsfrom'.
26 */
27 protected $nsfromMsg = 'allpagesfrom';
28
29 function __construct( $name = 'Allpages' ){
30 parent::__construct( $name );
31 }
32
33 /**
34 * Entry point : initialise variables and call subfunctions.
35 *
36 * @param $par String: becomes "FOO" when called like Special:Allpages/FOO (default NULL)
37 */
38 function execute( $par ) {
39 global $wgRequest, $wgOut, $wgContLang;
40
41 $this->setHeaders();
42 $this->outputHeader();
43
44 # GET values
45 $from = $wgRequest->getVal( 'from', null );
46 $to = $wgRequest->getVal( 'to', null );
47 $namespace = $wgRequest->getInt( 'namespace' );
48
49 $namespaces = $wgContLang->getNamespaces();
50
51 $wgOut->setPagetitle(
52 ( $namespace > 0 && in_array( $namespace, array_keys( $namespaces) ) ) ?
53 wfMsg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) ) :
54 wfMsg( 'allarticles' )
55 );
56
57 if( isset($par) ) {
58 $this->showChunk( $namespace, $par, $to );
59 } elseif( isset($from) && !isset($to) ) {
60 $this->showChunk( $namespace, $from, $to );
61 } else {
62 $this->showToplevel( $namespace, $from, $to );
63 }
64 }
65
66 /**
67 * HTML for the top form
68 *
69 * @param $namespace Integer: a namespace constant (default NS_MAIN).
70 * @param $from String: dbKey we are starting listing at.
71 * @param $to String: dbKey we are ending listing at.
72 */
73 function namespaceForm( $namespace = NS_MAIN, $from = '', $to = '' ) {
74 global $wgScript;
75 $t = $this->getTitle();
76
77 $out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) );
78 $out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) );
79 $out .= Xml::hidden( 'title', $t->getPrefixedText() );
80 $out .= Xml::openElement( 'fieldset' );
81 $out .= Xml::element( 'legend', null, wfMsg( 'allpages' ) );
82 $out .= Xml::openElement( 'table', array( 'id' => 'nsselect', 'class' => 'allpages' ) );
83 $out .= "<tr>
84 <td class='mw-label'>" .
85 Xml::label( wfMsg( 'allpagesfrom' ), 'nsfrom' ) .
86 " </td>
87 <td class='mw-input'>" .
88 Xml::input( 'from', 30, str_replace('_',' ',$from), array( 'id' => 'nsfrom' ) ) .
89 " </td>
90 </tr>
91 <tr>
92 <td class='mw-label'>" .
93 Xml::label( wfMsg( 'allpagesto' ), 'nsto' ) .
94 " </td>
95 <td class='mw-input'>" .
96 Xml::input( 'to', 30, str_replace('_',' ',$to), array( 'id' => 'nsto' ) ) .
97 " </td>
98 </tr>
99 <tr>
100 <td class='mw-label'>" .
101 Xml::label( wfMsg( 'namespace' ), 'namespace' ) .
102 " </td>
103 <td class='mw-input'>" .
104 Xml::namespaceSelector( $namespace, null ) . ' ' .
105 Xml::submitButton( wfMsg( 'allpagessubmit' ) ) .
106 " </td>
107 </tr>";
108 $out .= Xml::closeElement( 'table' );
109 $out .= Xml::closeElement( 'fieldset' );
110 $out .= Xml::closeElement( 'form' );
111 $out .= Xml::closeElement( 'div' );
112 return $out;
113 }
114
115 /**
116 * @param $namespace Integer (default NS_MAIN)
117 * @param $from String: list all pages from this name
118 * @param $to String: list all pages to this name
119 */
120 function showToplevel( $namespace = NS_MAIN, $from = '', $to = '' ) {
121 global $wgOut;
122
123 # TODO: Either make this *much* faster or cache the title index points
124 # in the querycache table.
125
126 $dbr = wfGetDB( DB_SLAVE );
127 $out = "";
128 $where = array( 'page_namespace' => $namespace );
129
130 $from = Title::makeTitleSafe( $namespace, $from );
131 $to = Title::makeTitleSafe( $namespace, $to );
132 $from = ( $from && $from->isLocal() ) ? $from->getDBkey() : null;
133 $to = ( $to && $to->isLocal() ) ? $to->getDBkey() : null;
134
135 if( isset($from) )
136 $where[] = 'page_title >= '.$dbr->addQuotes( $from );
137 if( isset($to) )
138 $where[] = 'page_title <= '.$dbr->addQuotes( $to );
139
140 global $wgMemc;
141 $key = wfMemcKey( 'allpages', 'ns', $namespace, $from, $to );
142 $lines = $wgMemc->get( $key );
143
144 $count = $dbr->estimateRowCount( 'page', '*', $where, __METHOD__ );
145 $maxPerSubpage = intval($count/$this->maxLineCount);
146 $maxPerSubpage = max($maxPerSubpage,$this->maxPerPage);
147
148 if( !is_array( $lines ) ) {
149 $options = array( 'LIMIT' => 1 );
150 $options['ORDER BY'] = 'page_title ASC';
151 $firstTitle = $dbr->selectField( 'page', 'page_title', $where, __METHOD__, $options );
152 $lastTitle = $firstTitle;
153 # This array is going to hold the page_titles in order.
154 $lines = array( $firstTitle );
155 # If we are going to show n rows, we need n+1 queries to find the relevant titles.
156 $done = false;
157 while( !$done ) {
158 // Fetch the last title of this chunk and the first of the next
159 $chunk = ( $lastTitle === false )
160 ? array()
161 : array( 'page_title >= ' . $dbr->addQuotes( $lastTitle ) );
162 $res = $dbr->select( 'page', /* FROM */
163 'page_title', /* WHAT */
164 array_merge($where,$chunk),
165 __METHOD__,
166 array ('LIMIT' => 2, 'OFFSET' => $maxPerSubpage - 1, 'ORDER BY' => 'page_title ASC')
167 );
168
169 if( $s = $dbr->fetchObject( $res ) ) {
170 array_push( $lines, $s->page_title );
171 } else {
172 // Final chunk, but ended prematurely. Go back and find the end.
173 $endTitle = $dbr->selectField( 'page', 'MAX(page_title)',
174 array_merge($where,$chunk),
175 __METHOD__ );
176 array_push( $lines, $endTitle );
177 $done = true;
178 }
179 if( $s = $res->fetchObject() ) {
180 array_push( $lines, $s->page_title );
181 $lastTitle = $s->page_title;
182 } else {
183 // This was a final chunk and ended exactly at the limit.
184 // Rare but convenient!
185 $done = true;
186 }
187 $res->free();
188 }
189 $wgMemc->add( $key, $lines, 3600 );
190 }
191
192 // If there are only two or less sections, don't even display them.
193 // Instead, display the first section directly.
194 if( count( $lines ) <= 2 ) {
195 if( !empty($lines) ) {
196 $this->showChunk( $namespace, $from, $to );
197 } else {
198 $wgOut->addHTML( $this->namespaceForm( $namespace, $from, $to ) );
199 }
200 return;
201 }
202
203 # At this point, $lines should contain an even number of elements.
204 $out .= Xml::openElement( 'table', array( 'class' => 'allpageslist' ) );
205 while( count ( $lines ) > 0 ) {
206 $inpoint = array_shift( $lines );
207 $outpoint = array_shift( $lines );
208 $out .= $this->showline( $inpoint, $outpoint, $namespace );
209 }
210 $out .= Xml::closeElement( 'table' );
211 $nsForm = $this->namespaceForm( $namespace, $from, $to );
212
213 # Is there more?
214 if( $this->including() ) {
215 $out2 = '';
216 } else {
217 if( isset($from) || isset($to) ) {
218 global $wgUser;
219 $out2 = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-form' ) ).
220 '<tr>
221 <td>' .
222 $nsForm .
223 '</td>
224 <td class="mw-allpages-nav">' .
225 $wgUser->getSkin()->link( $this->getTitle(), wfMsgHtml ( 'allpages' ),
226 array(), array(), 'known' ) .
227 "</td>
228 </tr>" .
229 Xml::closeElement( 'table' );
230 } else {
231 $out2 = $nsForm;
232 }
233 }
234 $wgOut->addHTML( $out2 . $out );
235 }
236
237 /**
238 * Show a line of "ABC to DEF" ranges of articles
239 *
240 * @param $inpoint String: lower limit of pagenames
241 * @param $outpoint String: upper limit of pagenames
242 * @param $namespace Integer (Default NS_MAIN)
243 */
244 function showline( $inpoint, $outpoint, $namespace = NS_MAIN ) {
245 global $wgContLang;
246 $inpointf = htmlspecialchars( str_replace( '_', ' ', $inpoint ) );
247 $outpointf = htmlspecialchars( str_replace( '_', ' ', $outpoint ) );
248 // Don't let the length runaway
249 $inpointf = $wgContLang->truncate( $inpointf, $this->maxPageLength );
250 $outpointf = $wgContLang->truncate( $outpointf, $this->maxPageLength );
251
252 $queryparams = $namespace ? "namespace=$namespace&" : '';
253 $special = $this->getTitle();
254 $link = $special->escapeLocalUrl( $queryparams . 'from=' . urlencode($inpoint) . '&to=' . urlencode($outpoint) );
255
256 $out = wfMsgHtml( 'alphaindexline',
257 "<a href=\"$link\">$inpointf</a></td><td>",
258 "</td><td><a href=\"$link\">$outpointf</a>"
259 );
260 return '<tr><td class="mw-allpages-alphaindexline">' . $out . '</td></tr>';
261 }
262
263 /**
264 * @param $namespace Integer (Default NS_MAIN)
265 * @param $from String: list all pages from this name (default FALSE)
266 * @param $to String: list all pages to this name (default FALSE)
267 */
268 function showChunk( $namespace = NS_MAIN, $from = false, $to = false ) {
269 global $wgOut, $wgUser, $wgContLang, $wgLang;
270
271 $sk = $wgUser->getSkin();
272
273 $fromList = $this->getNamespaceKeyAndText($namespace, $from);
274 $toList = $this->getNamespaceKeyAndText( $namespace, $to );
275 $namespaces = $wgContLang->getNamespaces();
276 $n = 0;
277
278 if ( !$fromList || !$toList ) {
279 $out = wfMsgWikiHtml( 'allpagesbadtitle' );
280 } elseif ( !in_array( $namespace, array_keys( $namespaces ) ) ) {
281 // Show errormessage and reset to NS_MAIN
282 $out = wfMsgExt( 'allpages-bad-ns', array( 'parseinline' ), $namespace );
283 $namespace = NS_MAIN;
284 } else {
285 list( $namespace, $fromKey, $from ) = $fromList;
286 list( $namespace2, $toKey, $to ) = $toList;
287
288 $dbr = wfGetDB( DB_SLAVE );
289 $conds = array(
290 'page_namespace' => $namespace,
291 'page_title >= ' . $dbr->addQuotes( $fromKey )
292 );
293 if( $toKey !== "" ) {
294 $conds[] = 'page_title <= ' . $dbr->addQuotes( $toKey );
295 }
296
297 $res = $dbr->select( 'page',
298 array( 'page_namespace', 'page_title', 'page_is_redirect' ),
299 $conds,
300 __METHOD__,
301 array(
302 'ORDER BY' => 'page_title',
303 'LIMIT' => $this->maxPerPage + 1,
304 'USE INDEX' => 'name_title',
305 )
306 );
307
308 if( $res->numRows() > 0 ) {
309 $out = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-chunk' ) );
310 while( ( $n < $this->maxPerPage ) && ( $s = $res->fetchObject() ) ) {
311 $t = Title::makeTitle( $s->page_namespace, $s->page_title );
312 if( $t ) {
313 $link = ( $s->page_is_redirect ? '<div class="allpagesredirect">' : '' ) .
314 $sk->linkKnown( $t, htmlspecialchars( $t->getText() ) ) .
315 ($s->page_is_redirect ? '</div>' : '' );
316 } else {
317 $link = '[[' . htmlspecialchars( $s->page_title ) . ']]';
318 }
319 if( $n % 3 == 0 ) {
320 $out .= '<tr>';
321 }
322 $out .= "<td width=\"33%\">$link</td>";
323 $n++;
324 if( $n % 3 == 0 ) {
325 $out .= "</tr>\n";
326 }
327 }
328 if( ($n % 3) != 0 ) {
329 $out .= "</tr>\n";
330 }
331 $out .= Xml::closeElement( 'table' );
332 } else {
333 $out = '';
334 }
335 }
336
337 if ( $this->including() ) {
338 $out2 = '';
339 } else {
340 if( $from == '' ) {
341 // First chunk; no previous link.
342 $prevTitle = null;
343 } else {
344 # Get the last title from previous chunk
345 $dbr = wfGetDB( DB_SLAVE );
346 $res_prev = $dbr->select(
347 'page',
348 'page_title',
349 array( 'page_namespace' => $namespace, 'page_title < '.$dbr->addQuotes($from) ),
350 __METHOD__,
351 array( 'ORDER BY' => 'page_title DESC',
352 'LIMIT' => $this->maxPerPage, 'OFFSET' => ($this->maxPerPage - 1 )
353 )
354 );
355
356 # Get first title of previous complete chunk
357 if( $dbr->numrows( $res_prev ) >= $this->maxPerPage ) {
358 $pt = $dbr->fetchObject( $res_prev );
359 $prevTitle = Title::makeTitle( $namespace, $pt->page_title );
360 } else {
361 # The previous chunk is not complete, need to link to the very first title
362 # available in the database
363 $options = array( 'LIMIT' => 1 );
364 if ( ! $dbr->implicitOrderby() ) {
365 $options['ORDER BY'] = 'page_title';
366 }
367 $reallyFirstPage_title = $dbr->selectField( 'page', 'page_title',
368 array( 'page_namespace' => $namespace ), __METHOD__, $options );
369 # Show the previous link if it s not the current requested chunk
370 if( $from != $reallyFirstPage_title ) {
371 $prevTitle = Title::makeTitle( $namespace, $reallyFirstPage_title );
372 } else {
373 $prevTitle = null;
374 }
375 }
376 }
377
378 $self = $this->getTitle();
379
380 $nsForm = $this->namespaceForm( $namespace, $from, $to );
381 $out2 = Xml::openElement( 'table', array( 'class' => 'mw-allpages-table-form' ) ).
382 '<tr>
383 <td>' .
384 $nsForm .
385 '</td>
386 <td class="mw-allpages-nav">' .
387 $sk->link( $self, wfMsgHtml ( 'allpages' ), array(), array(), 'known' );
388
389 # Do we put a previous link ?
390 if( isset( $prevTitle ) && $pt = $prevTitle->getText() ) {
391 $query = array( 'from' => $prevTitle->getText() );
392
393 if( $namespace )
394 $query['namespace'] = $namespace;
395
396 $prevLink = $sk->linkKnown(
397 $self,
398 htmlspecialchars( wfMsg( 'prevpage', $pt ) ),
399 array(),
400 $query
401 );
402 $out2 = $wgLang->pipeList( array( $out2, $prevLink ) );
403 }
404
405 if( $n == $this->maxPerPage && $s = $res->fetchObject() ) {
406 # $s is the first link of the next chunk
407 $t = Title::MakeTitle($namespace, $s->page_title);
408 $query = array( 'from' => $t->getText() );
409
410 if( $namespace )
411 $query['namespace'] = $namespace;
412
413 $nextLink = $sk->linkKnown(
414 $self,
415 htmlspecialchars( wfMsg( 'nextpage', $t->getText() ) ),
416 array(),
417 $query
418 );
419 $out2 = $wgLang->pipeList( array( $out2, $nextLink ) );
420 }
421 $out2 .= "</td></tr></table>";
422 }
423
424 $wgOut->addHTML( $out2 . $out );
425 if( isset($prevLink) or isset($nextLink) ) {
426 $wgOut->addHTML( '<hr /><p class="mw-allpages-nav">' );
427 if( isset( $prevLink ) ) {
428 $wgOut->addHTML( $prevLink );
429 }
430 if( isset( $prevLink ) && isset( $nextLink ) ) {
431 $wgOut->addHTML( wfMsgExt( 'pipe-separator' , 'escapenoentities' ) );
432 }
433 if( isset( $nextLink ) ) {
434 $wgOut->addHTML( $nextLink );
435 }
436 $wgOut->addHTML( '</p>' );
437
438 }
439
440 }
441
442 /**
443 * @param $ns Integer: the namespace of the article
444 * @param $text String: the name of the article
445 * @return array( int namespace, string dbkey, string pagename ) or NULL on error
446 * @static (sort of)
447 * @access private
448 */
449 function getNamespaceKeyAndText($ns, $text) {
450 if ( $text == '' )
451 return array( $ns, '', '' ); # shortcut for common case
452
453 $t = Title::makeTitleSafe($ns, $text);
454 if ( $t && $t->isLocal() ) {
455 return array( $t->getNamespace(), $t->getDBkey(), $t->getText() );
456 } else if ( $t ) {
457 return null;
458 }
459
460 # try again, in case the problem was an empty pagename
461 $text = preg_replace('/(#|$)/', 'X$1', $text);
462 $t = Title::makeTitleSafe($ns, $text);
463 if ( $t && $t->isLocal() ) {
464 return array( $t->getNamespace(), '', '' );
465 } else {
466 return null;
467 }
468 }
469 }