double-check with isLocal() to be safe
[lhc/web/wiklou.git] / includes / specials / SpecialAllpages.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 $to = $wgRequest->getVal( 'to', '' );
18 $namespace = $wgRequest->getInt( 'namespace' );
19
20 $namespaces = $wgContLang->getNamespaces();
21
22 $indexPage = new SpecialAllpages();
23
24 $wgOut->setPagetitle( ( $namespace > 0 && in_array( $namespace, array_keys( $namespaces) ) ) ?
25 wfMsg( 'allinnamespace', str_replace( '_', ' ', $namespaces[$namespace] ) ) :
26 wfMsg( 'allarticles' )
27 );
28
29 $indexPage->showToplevel( $namespace, $from, $to, $specialPage->including() );
30 }
31
32 /**
33 * Implements Special:Allpages
34 * @ingroup SpecialPage
35 */
36 class SpecialAllpages {
37 /**
38 * Maximum number of pages to show on single list subpage.
39 */
40 protected $maxPerPage = 345;
41
42 /**
43 * Maximum number of pages to show on single index subpage.
44 */
45 protected $maxLineCount = 200;
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 * HTML for the top form
55 * @param integer $namespace A namespace constant (default NS_MAIN).
56 * @param string $from dbKey we are starting listing at.
57 * @param string $to dbKey we are ending listing at.
58 */
59 function namespaceForm( $namespace = NS_MAIN, $from = '', $to = '' ) {
60 global $wgScript;
61 $t = SpecialPage::getTitleFor( $this->name );
62
63 $out = Xml::openElement( 'div', array( 'class' => 'namespaceoptions' ) );
64 $out .= Xml::openElement( 'form', array( 'method' => 'get', 'action' => $wgScript ) );
65 $out .= Xml::hidden( 'title', $t->getPrefixedText() );
66 $out .= Xml::openElement( 'fieldset' );
67 $out .= Xml::element( 'legend', null, wfMsg( 'allpages' ) );
68 $out .= Xml::openElement( 'table', array( 'id' => 'nsselect', 'class' => 'allpages' ) );
69 $out .= "<tr>
70 <td class='mw-label'>" .
71 Xml::label( wfMsg( 'allpagesfrom' ), 'nsfrom' ) .
72 "</td>
73 <td class='mw-input'>" .
74 Xml::input( 'from', 30, str_replace('_',' ',$from), array( 'id' => 'nsfrom' ) ) .
75 "</td>
76 </tr>
77 <tr>
78 <td class='mw-label'>" .
79 Xml::label( wfMsg( 'allpagesto' ), 'nsto' ) .
80 "</td>
81 <td class='mw-input'>" .
82 Xml::input( 'to', 30, str_replace('_',' ',$to), array( 'id' => 'nsto' ) ) .
83 "</td>
84 </tr>
85 <tr>
86 <td class='mw-label'>" .
87 Xml::label( wfMsg( 'namespace' ), 'namespace' ) .
88 "</td>
89 <td class='mw-input'>" .
90 Xml::namespaceSelector( $namespace, null ) . ' ' .
91 Xml::submitButton( wfMsg( 'allpagessubmit' ) ) .
92 "</td>
93 </tr>";
94 $out .= Xml::closeElement( 'table' );
95 $out .= Xml::closeElement( 'fieldset' );
96 $out .= Xml::closeElement( 'form' );
97 $out .= Xml::closeElement( 'div' );
98 return $out;
99 }
100
101 /**
102 * @param integer $namespace (default NS_MAIN)
103 */
104 function showToplevel( $namespace = NS_MAIN, $from = '', $to = '', $including = false ) {
105 global $wgOut, $wgContLang;
106 $align = $wgContLang->isRtl() ? 'left' : 'right';
107
108 # TODO: Either make this *much* faster or cache the title index points
109 # in the querycache table.
110
111 $dbr = wfGetDB( DB_SLAVE );
112 $out = "";
113 $where = array( 'page_namespace' => $namespace );
114
115 $from = Title::makeTitleSafe( $namespace, $from );
116 $to = Title::makeTitleSafe( $namespace, $to );
117 $from = ( $from && $from->isLocal() ) ? $from->getDBKey() : null;
118 $to = ( $to && $to->isLocal() ) ? $to->getDBKey() : null;
119
120 if( isset($from) )
121 $where[] = 'page_title >= '.$dbr->addQuotes( $from );
122 if( isset($to) )
123 $where[] = 'page_title <= '.$dbr->addQuotes( $to );
124
125 global $wgMemc;
126 $key = wfMemcKey( 'allpages', 'ns', $namespace, $from, $to );
127 $lines = $wgMemc->get( $key );
128
129 $count = $dbr->estimateRowCount( 'page', '*', $where, __METHOD__ );
130 $maxPerSubpage = intval($count/$this->maxLineCount);
131 $maxPerSubpage = max($maxPerSubpage,$this->maxPerPage);
132
133 if( !is_array( $lines ) ) {
134 $options = array( 'LIMIT' => 1 );
135 $options['ORDER BY'] = 'page_title ASC';
136 $firstTitle = $dbr->selectField( 'page', 'page_title', $where, __METHOD__, $options );
137 $lastTitle = $firstTitle;
138 # This array is going to hold the page_titles in order.
139 $lines = array( $firstTitle );
140 # If we are going to show n rows, we need n+1 queries to find the relevant titles.
141 $done = false;
142 while( !$done ) {
143 // Fetch the last title of this chunk and the first of the next
144 $chunk = is_null( $lastTitle )
145 ? ''
146 : 'page_title >= ' . $dbr->addQuotes( $lastTitle );
147 $chunk = array($chunk);
148 $res = $dbr->select( 'page', /* FROM */
149 'page_title', /* WHAT */
150 array_merge($where,$chunk),
151 __METHOD__,
152 array ('LIMIT' => 2, 'OFFSET' => $maxPerSubpage - 1, 'ORDER BY' => 'page_title ASC')
153 );
154
155 if( $s = $dbr->fetchObject( $res ) ) {
156 array_push( $lines, $s->page_title );
157 } else {
158 // Final chunk, but ended prematurely. Go back and find the end.
159 $endTitle = $dbr->selectField( 'page', 'MAX(page_title)',
160 array_merge($where,$chunk),
161 __METHOD__ );
162 array_push( $lines, $endTitle );
163 $done = true;
164 }
165 if( $s = $dbr->fetchObject( $res ) ) {
166 array_push( $lines, $s->page_title );
167 $lastTitle = $s->page_title;
168 } else {
169 // This was a final chunk and ended exactly at the limit.
170 // Rare but convenient!
171 $done = true;
172 }
173 $dbr->freeResult( $res );
174 }
175 $wgMemc->add( $key, $lines, 3600 );
176 }
177
178 // If there are only two or less sections, don't even display them.
179 // Instead, display the first section directly.
180 if( count( $lines ) <= 2 ) {
181 $this->showChunk( $namespace, $from, $to, $including );
182 return;
183 }
184
185 # At this point, $lines should contain an even number of elements.
186 $out .= "<table class='allpageslist' style='background: inherit;'>";
187 while( count ( $lines ) > 0 ) {
188 $inpoint = array_shift( $lines );
189 $outpoint = array_shift( $lines );
190 $out .= $this->showline( $inpoint, $outpoint, $namespace );
191 }
192 $out .= '</table>';
193 $nsForm = $this->namespaceForm( $namespace, $from, $to );
194
195 # Is there more?
196 if( $including ) {
197 $out2 = '';
198 } else {
199 if( isset($from) || isset($to) ) {
200 global $wgUser;
201 $out2 = '<table style="background: inherit;" width="100%" cellpadding="0" cellspacing="0" border="0">';
202 $out2 .= '<tr valign="top"><td>' . $nsForm;
203 $out2 .= '</td><td align="' . $align . '" style="font-size: smaller; margin-bottom: 1em;">' .
204 $wgUser->getSkin()->makeKnownLink( $wgContLang->specialPage( "Allpages" ), wfMsgHtml ( 'allpages' ) );
205 $out2 .= "</td></tr></table><hr />";
206 } else {
207 $out2 = $nsForm . '<hr />';
208 }
209 }
210 $wgOut->addHtml( $out2 . $out );
211 }
212
213 /**
214 * @todo Document
215 * @param string $from
216 * @param integer $namespace (Default NS_MAIN)
217 */
218 function showline( $inpoint, $outpoint, $namespace = NS_MAIN ) {
219 global $wgContLang;
220 $align = $wgContLang->isRtl() ? 'left' : 'right';
221 $inpointf = htmlspecialchars( str_replace( '_', ' ', $inpoint ) );
222 $outpointf = htmlspecialchars( str_replace( '_', ' ', $outpoint ) );
223 $queryparams = $namespace ? "namespace=$namespace&" : '';
224 $special = SpecialPage::getTitleFor( $this->name );
225 $link = $special->escapeLocalUrl( $queryparams . 'from=' . urlencode($inpoint) . '&to=' . urlencode($outpoint) );
226
227 $out = wfMsgHtml(
228 'alphaindexline',
229 "<a href=\"$link\">$inpointf</a></td><td>",
230 "</td><td><a href=\"$link\">$outpointf</a>"
231 );
232 return '<tr><td align="' . $align . '">'.$out.'</td></tr>';
233 }
234
235 /**
236 * @param integer $namespace (Default NS_MAIN)
237 * @param string $from list all pages from this name (default FALSE)
238 */
239 function showChunk( $namespace = NS_MAIN, $from, $to, $including = false ) {
240 global $wgOut, $wgUser, $wgContLang;
241
242 $sk = $wgUser->getSkin();
243
244 $fromList = $this->getNamespaceKeyAndText( $namespace, $from );
245 $toList = $this->getNamespaceKeyAndText( $namespace, $to );
246
247 $namespaces = $wgContLang->getNamespaces();
248 $align = $wgContLang->isRtl() ? 'left' : 'right';
249
250 $n = 0;
251
252 if ( !$fromList || !$toList ) {
253 $out = wfMsgWikiHtml( 'allpagesbadtitle' );
254 } elseif ( !in_array( $namespace, array_keys( $namespaces ) ) ) {
255 // Show errormessage and reset to NS_MAIN
256 $out = wfMsgExt( 'allpages-bad-ns', array( 'parseinline' ), $namespace );
257 $namespace = NS_MAIN;
258 } else {
259 list( $namespace, $fromKey, $from ) = $fromList;
260 list( $namespace, $toKey, $to ) = $toList;
261
262 $dbr = wfGetDB( DB_SLAVE );
263 $res = $dbr->select( 'page',
264 array( 'page_namespace', 'page_title', 'page_is_redirect' ),
265 array(
266 'page_namespace' => $namespace,
267 'page_title >= ' . $dbr->addQuotes( $fromKey ),
268 'page_title <= ' . $dbr->addQuotes( $toKey ),
269 ),
270 __METHOD__,
271 array(
272 'ORDER BY' => 'page_title ASC',
273 'LIMIT' => $this->maxPerPage + 1,
274 'USE INDEX' => 'name_title',
275 )
276 );
277
278 if( $res->numRows() > 0 ) {
279 $out = '<table style="background: inherit;" border="0" width="100%">';
280
281 while( ($n < $this->maxPerPage) && ($s = $dbr->fetchObject( $res )) ) {
282 $t = Title::makeTitle( $s->page_namespace, $s->page_title );
283 if( $t ) {
284 $link = ($s->page_is_redirect ? '<div class="allpagesredirect">' : '' ) .
285 $sk->makeKnownLinkObj( $t, htmlspecialchars( $t->getText() ), false, false ) .
286 ($s->page_is_redirect ? '</div>' : '' );
287 } else {
288 $link = '[[' . htmlspecialchars( $s->page_title ) . ']]';
289 }
290 if( $n % 3 == 0 ) {
291 $out .= '<tr>';
292 }
293 $out .= "<td width=\"33%\">$link</td>";
294 $n++;
295 if( $n % 3 == 0 ) {
296 $out .= '</tr>';
297 }
298 }
299 if( ($n % 3) != 0 ) {
300 $out .= '</tr>';
301 }
302 $out .= '</table>';
303 } else {
304 $out = '';
305 }
306 }
307
308 if ( $including ) {
309 $out2 = '';
310 } else {
311 $nsForm = $this->namespaceForm( $namespace, $from, $to );
312 $out2 = '<table style="background: inherit;" width="100%" cellpadding="0" cellspacing="0" border="0">';
313 $out2 .= '<tr valign="top"><td>' . $nsForm;
314 $out2 .= '</td><td align="' . $align . '" style="font-size: smaller; margin-bottom: 1em;">' .
315 $sk->makeKnownLink( $wgContLang->specialPage( "Allpages" ), wfMsgHtml ( 'allpages' ) );
316 $out2 .= "</td></tr></table><hr />";
317 }
318
319 $wgOut->addHtml( $out2 . $out );
320 }
321
322 /**
323 * @param int $ns the namespace of the article
324 * @param string $text the name of the article
325 * @return array( int namespace, string dbkey, string pagename ) or NULL on error
326 * @static (sort of)
327 * @access private
328 */
329 function getNamespaceKeyAndText( $ns, $text ) {
330 if ( $text == '' )
331 return array( $ns, '', '' ); # shortcut for common case
332
333 $t = Title::makeTitleSafe($ns, $text);
334 if ( $t && $t->isLocal() ) {
335 return array( $t->getNamespace(), $t->getDBkey(), $t->getText() );
336 } else if ( $t ) {
337 return NULL;
338 }
339
340 # try again, in case the problem was an empty pagename
341 $text = preg_replace('/(#|$)/', 'X$1', $text);
342 $t = Title::makeTitleSafe($ns, $text);
343 if ( $t && $t->isLocal() ) {
344 return array( $t->getNamespace(), '', '' );
345 } else {
346 return NULL;
347 }
348 }
349
350 }