]> scripts.mit.edu Git - autoinstallsdev/mediawiki.git/blob - includes/CategoryPage.php
MediaWiki 1.11.0
[autoinstallsdev/mediawiki.git] / includes / CategoryPage.php
1 <?php
2 /**
3  * Special handling for category description pages
4  * Modelled after ImagePage.php
5  *
6  */
7
8 if( !defined( 'MEDIAWIKI' ) )
9         die( 1 );
10
11 /**
12  */
13 class CategoryPage extends Article {
14         function view() {
15                 global $wgRequest, $wgUser;
16
17                 $diff = $wgRequest->getVal( 'diff' );
18                 $diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) );
19
20                 if ( isset( $diff ) && $diffOnly )
21                         return Article::view();
22
23                 if(!wfRunHooks('CategoryPageView', array(&$this))) return;
24
25                 if ( NS_CATEGORY == $this->mTitle->getNamespace() ) {
26                         $this->openShowCategory();
27                 }
28
29                 Article::view();
30
31                 # If the article we've just shown is in the "Image" namespace,
32                 # follow it with the history list and link list for the image
33                 # it describes.
34
35                 if ( NS_CATEGORY == $this->mTitle->getNamespace() ) {
36                         $this->closeShowCategory();
37                 }
38         }
39
40         function openShowCategory() {
41                 # For overloading
42         }
43
44         function closeShowCategory() {
45                 global $wgOut, $wgRequest;
46                 $from = $wgRequest->getVal( 'from' );
47                 $until = $wgRequest->getVal( 'until' );
48
49                 $viewer = new CategoryViewer( $this->mTitle, $from, $until );
50                 $wgOut->addHTML( $viewer->getHTML() );
51         }
52 }
53
54 class CategoryViewer {
55         var $title, $limit, $from, $until,
56                 $articles, $articles_start_char, 
57                 $children, $children_start_char,
58                 $showGallery, $gallery,
59                 $skin;
60
61         function __construct( $title, $from = '', $until = '' ) {
62                 global $wgCategoryPagingLimit;
63                 $this->title = $title;
64                 $this->from = $from;
65                 $this->until = $until;
66                 $this->limit = $wgCategoryPagingLimit;
67         }
68         
69         /**
70          * Format the category data list.
71          *
72          * @param string $from -- return only sort keys from this item on
73          * @param string $until -- don't return keys after this point.
74          * @return string HTML output
75          * @private
76          */
77         function getHTML() {
78                 global $wgOut, $wgCategoryMagicGallery, $wgCategoryPagingLimit;
79                 wfProfileIn( __METHOD__ );
80
81                 $this->showGallery = $wgCategoryMagicGallery && !$wgOut->mNoGallery;
82
83                 $this->clearCategoryState();
84                 $this->doCategoryQuery();
85                 $this->finaliseCategoryState();
86
87                 $r = $this->getCategoryTop() .
88                         $this->getSubcategorySection() .
89                         $this->getPagesSection() .
90                         $this->getImageSection() .
91                         $this->getCategoryBottom();
92
93                 // Give a proper message if category is empty
94                 if ( $r == '' ) {
95                         $r = wfMsgExt( 'category-empty', array( 'parse' ) );
96                 }
97
98                 wfProfileOut( __METHOD__ );
99                 return $r;
100         }
101
102         function clearCategoryState() {
103                 $this->articles = array();
104                 $this->articles_start_char = array();
105                 $this->children = array();
106                 $this->children_start_char = array();
107                 if( $this->showGallery ) {
108                         $this->gallery = new ImageGallery();
109                         $this->gallery->setHideBadImages();
110                 }
111         }
112
113         function getSkin() {
114                 if ( !$this->skin ) {
115                         global $wgUser;
116                         $this->skin = $wgUser->getSkin();
117                 }
118                 return $this->skin;
119         }
120
121         /**
122          * Add a subcategory to the internal lists
123          */
124         function addSubcategory( $title, $sortkey, $pageLength ) {
125                 global $wgContLang;
126                 // Subcategory; strip the 'Category' namespace from the link text.
127                 $this->children[] = $this->getSkin()->makeKnownLinkObj( 
128                         $title, $wgContLang->convertHtml( $title->getText() ) );
129
130                 $this->children_start_char[] = $this->getSubcategorySortChar( $title, $sortkey );
131         }
132
133         /**
134         * Get the character to be used for sorting subcategories.
135         * If there's a link from Category:A to Category:B, the sortkey of the resulting
136         * entry in the categorylinks table is Category:A, not A, which it SHOULD be.
137         * Workaround: If sortkey == "Category:".$title, than use $title for sorting,
138         * else use sortkey...
139         */
140         function getSubcategorySortChar( $title, $sortkey ) {
141                 global $wgContLang;
142                 
143                 if( $title->getPrefixedText() == $sortkey ) {
144                         $firstChar = $wgContLang->firstChar( $title->getDBkey() );
145                 } else {
146                         $firstChar = $wgContLang->firstChar( $sortkey );
147                 }
148                 
149                 return $wgContLang->convert( $firstChar );
150         }
151
152         /**
153          * Add a page in the image namespace
154          */
155         function addImage( Title $title, $sortkey, $pageLength, $isRedirect = false ) {
156                 if ( $this->showGallery ) {
157                         $image = new Image( $title );
158                         if( $this->flip ) {
159                                 $this->gallery->insert( $image );
160                         } else {
161                                 $this->gallery->add( $image );
162                         }
163                 } else {
164                         $this->addPage( $title, $sortkey, $pageLength, $isRedirect );
165                 }
166         }
167
168         /**
169          * Add a miscellaneous page
170          */
171         function addPage( $title, $sortkey, $pageLength, $isRedirect = false ) {
172                 global $wgContLang;
173                 $this->articles[] = $isRedirect
174                         ? '<span class="redirect-in-category">' . $this->getSkin()->makeKnownLinkObj( $title ) . '</span>'
175                         : $this->getSkin()->makeSizeLinkObj( $pageLength, $title );
176                 $this->articles_start_char[] = $wgContLang->convert( $wgContLang->firstChar( $sortkey ) );
177         }
178
179         function finaliseCategoryState() {
180                 if( $this->flip ) {
181                         $this->children            = array_reverse( $this->children );
182                         $this->children_start_char = array_reverse( $this->children_start_char );
183                         $this->articles            = array_reverse( $this->articles );
184                         $this->articles_start_char = array_reverse( $this->articles_start_char );
185                 }
186         }
187
188         function doCategoryQuery() {
189                 $dbr = wfGetDB( DB_SLAVE );
190                 if( $this->from != '' ) {
191                         $pageCondition = 'cl_sortkey >= ' . $dbr->addQuotes( $this->from );
192                         $this->flip = false;
193                 } elseif( $this->until != '' ) {
194                         $pageCondition = 'cl_sortkey < ' . $dbr->addQuotes( $this->until );
195                         $this->flip = true;
196                 } else {
197                         $pageCondition = '1 = 1';
198                         $this->flip = false;
199                 }
200                 $res = $dbr->select(
201                         array( 'page', 'categorylinks' ),
202                         array( 'page_title', 'page_namespace', 'page_len', 'page_is_redirect', 'cl_sortkey' ),
203                         array( $pageCondition,
204                                'cl_from          =  page_id',
205                                'cl_to'           => $this->title->getDBKey()),
206                                #'page_is_redirect' => 0),
207                         #+ $pageCondition,
208                         __METHOD__,
209                         array( 'ORDER BY' => $this->flip ? 'cl_sortkey DESC' : 'cl_sortkey',
210                                'USE INDEX' => 'cl_sortkey', 
211                                'LIMIT'    => $this->limit + 1 ) );
212
213                 $count = 0;
214                 $this->nextPage = null;
215                 while( $x = $dbr->fetchObject ( $res ) ) {
216                         if( ++$count > $this->limit ) {
217                                 // We've reached the one extra which shows that there are
218                                 // additional pages to be had. Stop here...
219                                 $this->nextPage = $x->cl_sortkey;
220                                 break;
221                         }
222
223                         $title = Title::makeTitle( $x->page_namespace, $x->page_title );
224
225                         if( $title->getNamespace() == NS_CATEGORY ) {
226                                 $this->addSubcategory( $title, $x->cl_sortkey, $x->page_len );
227                         } elseif( $this->showGallery && $title->getNamespace() == NS_IMAGE ) {
228                                 $this->addImage( $title, $x->cl_sortkey, $x->page_len, $x->page_is_redirect );
229                         } else {
230                                 $this->addPage( $title, $x->cl_sortkey, $x->page_len, $x->page_is_redirect );
231                         }
232                 }
233                 $dbr->freeResult( $res );
234         }
235
236         function getCategoryTop() {
237                 $r = '';
238                 if( $this->until != '' ) {
239                         $r .= $this->pagingLinks( $this->title, $this->nextPage, $this->until, $this->limit );
240                 } elseif( $this->nextPage != '' || $this->from != '' ) {
241                         $r .= $this->pagingLinks( $this->title, $this->from, $this->nextPage, $this->limit );
242                 }
243                 return $r == ''
244                         ? $r
245                         : "<br style=\"clear:both;\"/>\n" . $r;
246         }
247
248         function getSubcategorySection() {
249                 # Don't show subcategories section if there are none.
250                 $r = '';
251                 $c = count( $this->children );
252                 if( $c > 0 ) {
253                         # Showing subcategories
254                         $r .= "<div id=\"mw-subcategories\">\n";
255                         $r .= '<h2>' . wfMsg( 'subcategories' ) . "</h2>\n";
256                         $r .= wfMsgExt( 'subcategorycount', array( 'parse' ), $c );
257                         $r .= $this->formatList( $this->children, $this->children_start_char );
258                         $r .= "\n</div>";
259                 }
260                 return $r;
261         }
262
263         function getPagesSection() {
264                 $ti = htmlspecialchars( $this->title->getText() );
265                 # Don't show articles section if there are none.
266                 $r = '';
267                 $c = count( $this->articles );
268                 if( $c > 0 ) {
269                         $r = "<div id=\"mw-pages\">\n";
270                         $r .= '<h2>' . wfMsg( 'category_header', $ti ) . "</h2>\n";
271                         $r .= wfMsgExt( 'categoryarticlecount', array( 'parse' ), $c );
272                         $r .= $this->formatList( $this->articles, $this->articles_start_char );
273                         $r .= "\n</div>";
274                 }
275                 return $r;
276         }
277
278         function getImageSection() {
279                 if( $this->showGallery && ! $this->gallery->isEmpty() ) {
280                         return "<div id=\"mw-category-media\">\n" .
281                         '<h2>' . wfMsg( 'category-media-header', htmlspecialchars($this->title->getText()) ) . "</h2>\n" .
282                         wfMsgExt( 'category-media-count', array( 'parse' ), $this->gallery->count() ) .
283                         $this->gallery->toHTML() . "\n</div>";
284                 } else {
285                         return '';
286                 }
287         }
288
289         function getCategoryBottom() {
290                 if( $this->until != '' ) {
291                         return $this->pagingLinks( $this->title, $this->nextPage, $this->until, $this->limit );
292                 } elseif( $this->nextPage != '' || $this->from != '' ) {
293                         return $this->pagingLinks( $this->title, $this->from, $this->nextPage, $this->limit );
294                 } else {
295                         return '';
296                 }
297         }
298
299         /**
300          * Format a list of articles chunked by letter, either as a
301          * bullet list or a columnar format, depending on the length.
302          *
303          * @param array $articles
304          * @param array $articles_start_char
305          * @param int   $cutoff
306          * @return string
307          * @private
308          */
309         function formatList( $articles, $articles_start_char, $cutoff = 6 ) {
310                 if ( count ( $articles ) > $cutoff ) {
311                         return $this->columnList( $articles, $articles_start_char );
312                 } elseif ( count($articles) > 0) {
313                         // for short lists of articles in categories.
314                         return $this->shortList( $articles, $articles_start_char );
315                 }
316                 return '';
317         }
318
319         /**
320          * Format a list of articles chunked by letter in a three-column
321          * list, ordered vertically.
322          *
323          * @param array $articles
324          * @param array $articles_start_char
325          * @return string
326          * @private
327          */
328         function columnList( $articles, $articles_start_char ) {
329                 // divide list into three equal chunks
330                 $chunk = (int) (count ( $articles ) / 3);
331
332                 // get and display header
333                 $r = '<table width="100%"><tr valign="top">';
334
335                 $prev_start_char = 'none';
336
337                 // loop through the chunks
338                 for($startChunk = 0, $endChunk = $chunk, $chunkIndex = 0;
339                         $chunkIndex < 3;
340                         $chunkIndex++, $startChunk = $endChunk, $endChunk += $chunk + 1)
341                 {
342                         $r .= "<td>\n";
343                         $atColumnTop = true;
344
345                         // output all articles in category
346                         for ($index = $startChunk ;
347                                 $index < $endChunk && $index < count($articles);
348                                 $index++ )
349                         {
350                                 // check for change of starting letter or begining of chunk
351                                 if ( ($index == $startChunk) ||
352                                          ($articles_start_char[$index] != $articles_start_char[$index - 1]) )
353
354                                 {
355                                         if( $atColumnTop ) {
356                                                 $atColumnTop = false;
357                                         } else {
358                                                 $r .= "</ul>\n";
359                                         }
360                                         $cont_msg = "";
361                                         if ( $articles_start_char[$index] == $prev_start_char )
362                                                 $cont_msg = ' ' . wfMsgHtml( 'listingcontinuesabbrev' );
363                                         $r .= "<h3>" . htmlspecialchars( $articles_start_char[$index] ) . "$cont_msg</h3>\n<ul>";
364                                         $prev_start_char = $articles_start_char[$index];
365                                 }
366
367                                 $r .= "<li>{$articles[$index]}</li>";
368                         }
369                         if( !$atColumnTop ) {
370                                 $r .= "</ul>\n";
371                         }
372                         $r .= "</td>\n";
373
374
375                 }
376                 $r .= '</tr></table>';
377                 return $r;
378         }
379
380         /**
381          * Format a list of articles chunked by letter in a bullet list.
382          * @param array $articles
383          * @param array $articles_start_char
384          * @return string
385          * @private
386          */
387         function shortList( $articles, $articles_start_char ) {
388                 $r = '<h3>' . htmlspecialchars( $articles_start_char[0] ) . "</h3>\n";
389                 $r .= '<ul><li>'.$articles[0].'</li>';
390                 for ($index = 1; $index < count($articles); $index++ )
391                 {
392                         if ($articles_start_char[$index] != $articles_start_char[$index - 1])
393                         {
394                                 $r .= "</ul><h3>" . htmlspecialchars( $articles_start_char[$index] ) . "</h3>\n<ul>";
395                         }
396
397                         $r .= "<li>{$articles[$index]}</li>";
398                 }
399                 $r .= '</ul>';
400                 return $r;
401         }
402
403         /**
404          * @param Title  $title
405          * @param string $first
406          * @param string $last
407          * @param int    $limit
408          * @param array  $query - additional query options to pass
409          * @return string
410          * @private
411          */
412         function pagingLinks( $title, $first, $last, $limit, $query = array() ) {
413                 global $wgUser, $wgLang;
414                 $sk = $this->getSkin();
415                 $limitText = $wgLang->formatNum( $limit );
416
417                 $prevLink = htmlspecialchars( wfMsg( 'prevn', $limitText ) );
418                 if( $first != '' ) {
419                         $prevLink = $sk->makeLinkObj( $title, $prevLink,
420                                 wfArrayToCGI( $query + array( 'until' => $first ) ) );
421                 }
422                 $nextLink = htmlspecialchars( wfMsg( 'nextn', $limitText ) );
423                 if( $last != '' ) {
424                         $nextLink = $sk->makeLinkObj( $title, $nextLink,
425                                 wfArrayToCGI( $query + array( 'from' => $last ) ) );
426                 }
427
428                 return "($prevLink) ($nextLink)";
429         }
430 }
431
432
433