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