In WantedQueryPage:
[lhc/web/wiklou.git] / includes / CategoryPage.php
1 <?php
2 /**
3 * Class for viewing MediaWiki category description pages.
4 * Modelled after ImagePage.php.
5 *
6 * @file
7 */
8
9 if ( !defined( 'MEDIAWIKI' ) )
10 die( 1 );
11
12 /**
13 * Special handling for category description pages, showing pages,
14 * subcategories and file that belong to the category
15 */
16 class CategoryPage extends Article {
17 # Subclasses can change this to override the viewer class.
18 protected $mCategoryViewerClass = 'CategoryViewer';
19
20 protected function newPage( Title $title ) {
21 // Overload mPage with a category-specific page
22 return new WikiCategoryPage( $title );
23 }
24
25 /**
26 * Constructor from a page id
27 * @param $id Int article ID to load
28 */
29 public static function newFromID( $id ) {
30 $t = Title::newFromID( $id );
31 # @todo FIXME: Doesn't inherit right
32 return $t == null ? null : new self( $t );
33 # return $t == null ? null : new static( $t ); // PHP 5.3
34 }
35
36 function view() {
37 $request = $this->getContext()->getRequest();
38 $diff = $request->getVal( 'diff' );
39 $diffOnly = $request->getBool( 'diffonly',
40 $this->getContext()->getUser()->getOption( 'diffonly' ) );
41
42 if ( isset( $diff ) && $diffOnly ) {
43 return parent::view();
44 }
45
46 if ( !wfRunHooks( 'CategoryPageView', array( &$this ) ) ) {
47 return;
48 }
49
50 if ( NS_CATEGORY == $this->mTitle->getNamespace() ) {
51 $this->openShowCategory();
52 }
53
54 parent::view();
55
56 if ( NS_CATEGORY == $this->mTitle->getNamespace() ) {
57 $this->closeShowCategory();
58 }
59 }
60
61 function openShowCategory() {
62 # For overloading
63 }
64
65 function closeShowCategory() {
66 // Use these as defaults for back compat --catrope
67 $request = $this->getContext()->getRequest();
68 $oldFrom = $request->getVal( 'from' );
69 $oldUntil = $request->getVal( 'until' );
70
71 $reqArray = $request->getValues();
72
73 $from = $until = array();
74 foreach ( array( 'page', 'subcat', 'file' ) as $type ) {
75 $from[$type] = $request->getVal( "{$type}from", $oldFrom );
76 $until[$type] = $request->getVal( "{$type}until", $oldUntil );
77
78 // Do not want old-style from/until propagating in nav links.
79 if ( !isset( $reqArray["{$type}from"] ) && isset( $reqArray["from"] ) ) {
80 $reqArray["{$type}from"] = $reqArray["from"];
81 }
82 if ( !isset( $reqArray["{$type}to"] ) && isset( $reqArray["to"] ) ) {
83 $reqArray["{$type}to"] = $reqArray["to"];
84 }
85 }
86
87 unset( $reqArray["from"] );
88 unset( $reqArray["to"] );
89
90 $viewer = new $this->mCategoryViewerClass( $this->mTitle, $this->getContext(), $from, $until, $reqArray );
91 $this->getContext()->getOutput()->addHTML( $viewer->getHTML() );
92 }
93 }
94
95 class CategoryViewer extends ContextSource {
96 var $limit, $from, $until,
97 $articles, $articles_start_char,
98 $children, $children_start_char,
99 $showGallery, $imgsNoGalley,
100 $imgsNoGallery_start_char,
101 $imgsNoGallery;
102
103 /**
104 * @var
105 */
106 var $nextPage;
107
108 /**
109 * @var Array
110 */
111 var $flip;
112
113 /**
114 * @var Title
115 */
116 var $title;
117
118 /**
119 * @var Collation
120 */
121 var $collation;
122
123 /**
124 * @var ImageGallery
125 */
126 var $gallery;
127
128 /**
129 * Category object for this page
130 * @var Category
131 */
132 private $cat;
133
134 /**
135 * The original query array, to be used in generating paging links.
136 * @var array
137 */
138 private $query;
139
140 /**
141 * Constructor
142 *
143 * @since 1.19 $context is a second, required parameter
144 * @param $title Title
145 * @param $context IContextSource
146 * @param $from String
147 * @param $until String
148 * @param $query Array
149 */
150 function __construct( $title, IContextSource $context, $from = '', $until = '', $query = array() ) {
151 global $wgCategoryPagingLimit;
152 $this->title = $title;
153 $this->setContext( $context );
154 $this->from = $from;
155 $this->until = $until;
156 $this->limit = $wgCategoryPagingLimit;
157 $this->cat = Category::newFromTitle( $title );
158 $this->query = $query;
159 $this->collation = Collation::singleton();
160 unset( $this->query['title'] );
161 }
162
163 /**
164 * Format the category data list.
165 *
166 * @return string HTML output
167 */
168 public function getHTML() {
169 global $wgCategoryMagicGallery;
170 wfProfileIn( __METHOD__ );
171
172 $this->showGallery = $wgCategoryMagicGallery && !$this->getOutput()->mNoGallery;
173
174 $this->clearCategoryState();
175 $this->doCategoryQuery();
176 $this->finaliseCategoryState();
177
178 $r = $this->getSubcategorySection() .
179 $this->getPagesSection() .
180 $this->getImageSection();
181
182 if ( $r == '' ) {
183 // If there is no category content to display, only
184 // show the top part of the navigation links.
185 // @todo FIXME: Cannot be completely suppressed because it
186 // is unknown if 'until' or 'from' makes this
187 // give 0 results.
188 $r = $r . $this->getCategoryTop();
189 } else {
190 $r = $this->getCategoryTop() .
191 $r .
192 $this->getCategoryBottom();
193 }
194
195 // Give a proper message if category is empty
196 if ( $r == '' ) {
197 $r = wfMsgExt( 'category-empty', array( 'parse' ) );
198 }
199
200 $lang = $this->getLang();
201 $langAttribs = array( 'lang' => $lang->getCode(), 'dir' => $lang->getDir() );
202 # put a div around the headings which are in the user language
203 $r = Html::openElement( 'div', $langAttribs ) . $r . '</div>';
204
205 wfProfileOut( __METHOD__ );
206 return $r;
207 }
208
209 function clearCategoryState() {
210 $this->articles = array();
211 $this->articles_start_char = array();
212 $this->children = array();
213 $this->children_start_char = array();
214 if ( $this->showGallery ) {
215 $this->gallery = new ImageGallery();
216 $this->gallery->setHideBadImages();
217 } else {
218 $this->imgsNoGallery = array();
219 $this->imgsNoGallery_start_char = array();
220 }
221 }
222
223 /**
224 * Add a subcategory to the internal lists, using a Category object
225 */
226 function addSubcategoryObject( Category $cat, $sortkey, $pageLength ) {
227 // Subcategory; strip the 'Category' namespace from the link text.
228 $title = $cat->getTitle();
229
230 $link = Linker::link( $title, htmlspecialchars( $title->getText() ) );
231 if ( $title->isRedirect() ) {
232 // This didn't used to add redirect-in-category, but might
233 // as well be consistent with the rest of the sections
234 // on a category page.
235 $link = '<span class="redirect-in-category">' . $link . '</span>';
236 }
237 $this->children[] = $link;
238
239 $this->children_start_char[] =
240 $this->getSubcategorySortChar( $cat->getTitle(), $sortkey );
241 }
242
243 /**
244 * Add a subcategory to the internal lists, using a title object
245 * @deprecated since 1.17 kept for compatibility, please use addSubcategoryObject instead
246 */
247 function addSubcategory( Title $title, $sortkey, $pageLength ) {
248 $this->addSubcategoryObject( Category::newFromTitle( $title ), $sortkey, $pageLength );
249 }
250
251 /**
252 * Get the character to be used for sorting subcategories.
253 * If there's a link from Category:A to Category:B, the sortkey of the resulting
254 * entry in the categorylinks table is Category:A, not A, which it SHOULD be.
255 * Workaround: If sortkey == "Category:".$title, than use $title for sorting,
256 * else use sortkey...
257 *
258 * @param Title $title
259 * @param string $sortkey The human-readable sortkey (before transforming to icu or whatever).
260 */
261 function getSubcategorySortChar( $title, $sortkey ) {
262 global $wgContLang;
263
264 if ( $title->getPrefixedText() == $sortkey ) {
265 $word = $title->getDBkey();
266 } else {
267 $word = $sortkey;
268 }
269
270 $firstChar = $this->collation->getFirstLetter( $word );
271
272 return $wgContLang->convert( $firstChar );
273 }
274
275 /**
276 * Add a page in the image namespace
277 */
278 function addImage( Title $title, $sortkey, $pageLength, $isRedirect = false ) {
279 global $wgContLang;
280 if ( $this->showGallery ) {
281 $flip = $this->flip['file'];
282 if ( $flip ) {
283 $this->gallery->insert( $title );
284 } else {
285 $this->gallery->add( $title );
286 }
287 } else {
288 $link = Linker::link( $title );
289 if ( $isRedirect ) {
290 // This seems kind of pointless given 'mw-redirect' class,
291 // but keeping for back-compatibility with user css.
292 $link = '<span class="redirect-in-category">' . $link . '</span>';
293 }
294 $this->imgsNoGallery[] = $link;
295
296 $this->imgsNoGallery_start_char[] = $wgContLang->convert(
297 $this->collation->getFirstLetter( $sortkey ) );
298 }
299 }
300
301 /**
302 * Add a miscellaneous page
303 */
304 function addPage( $title, $sortkey, $pageLength, $isRedirect = false ) {
305 global $wgContLang;
306
307 $link = Linker::link( $title );
308 if ( $isRedirect ) {
309 // This seems kind of pointless given 'mw-redirect' class,
310 // but keeping for back-compatiability with user css.
311 $link = '<span class="redirect-in-category">' . $link . '</span>';
312 }
313 $this->articles[] = $link;
314
315 $this->articles_start_char[] = $wgContLang->convert(
316 $this->collation->getFirstLetter( $sortkey ) );
317 }
318
319 function finaliseCategoryState() {
320 if ( $this->flip['subcat'] ) {
321 $this->children = array_reverse( $this->children );
322 $this->children_start_char = array_reverse( $this->children_start_char );
323 }
324 if ( $this->flip['page'] ) {
325 $this->articles = array_reverse( $this->articles );
326 $this->articles_start_char = array_reverse( $this->articles_start_char );
327 }
328 if ( !$this->showGallery && $this->flip['file'] ) {
329 $this->imgsNoGallery = array_reverse( $this->imgsNoGallery );
330 $this->imgsNoGallery_start_char = array_reverse( $this->imgsNoGallery_start_char );
331 }
332 }
333
334 function doCategoryQuery() {
335 $dbr = wfGetDB( DB_SLAVE, 'category' );
336
337 $this->nextPage = array(
338 'page' => null,
339 'subcat' => null,
340 'file' => null,
341 );
342 $this->flip = array( 'page' => false, 'subcat' => false, 'file' => false );
343
344 foreach ( array( 'page', 'subcat', 'file' ) as $type ) {
345 # Get the sortkeys for start/end, if applicable. Note that if
346 # the collation in the database differs from the one
347 # set in $wgCategoryCollation, pagination might go totally haywire.
348 $extraConds = array( 'cl_type' => $type );
349 if ( $this->from[$type] !== null ) {
350 $extraConds[] = 'cl_sortkey >= '
351 . $dbr->addQuotes( $this->collation->getSortKey( $this->from[$type] ) );
352 } elseif ( $this->until[$type] !== null ) {
353 $extraConds[] = 'cl_sortkey < '
354 . $dbr->addQuotes( $this->collation->getSortKey( $this->until[$type] ) );
355 $this->flip[$type] = true;
356 }
357
358 $res = $dbr->select(
359 array( 'page', 'categorylinks', 'category' ),
360 array( 'page_id', 'page_title', 'page_namespace', 'page_len',
361 'page_is_redirect', 'cl_sortkey', 'cat_id', 'cat_title',
362 'cat_subcats', 'cat_pages', 'cat_files',
363 'cl_sortkey_prefix', 'cl_collation' ),
364 array_merge( array( 'cl_to' => $this->title->getDBkey() ), $extraConds ),
365 __METHOD__,
366 array(
367 'USE INDEX' => array( 'categorylinks' => 'cl_sortkey' ),
368 'LIMIT' => $this->limit + 1,
369 'ORDER BY' => $this->flip[$type] ? 'cl_sortkey DESC' : 'cl_sortkey',
370 ),
371 array(
372 'categorylinks' => array( 'INNER JOIN', 'cl_from = page_id' ),
373 'category' => array( 'LEFT JOIN', 'cat_title = page_title AND page_namespace = ' . NS_CATEGORY )
374 )
375 );
376
377 $count = 0;
378 foreach ( $res as $row ) {
379 $title = Title::newFromRow( $row );
380 if ( $row->cl_collation === '' ) {
381 // Hack to make sure that while updating from 1.16 schema
382 // and db is inconsistent, that the sky doesn't fall.
383 // See r83544. Could perhaps be removed in a couple decades...
384 $humanSortkey = $row->cl_sortkey;
385 } else {
386 $humanSortkey = $title->getCategorySortkey( $row->cl_sortkey_prefix );
387 }
388
389 if ( ++$count > $this->limit ) {
390 # We've reached the one extra which shows that there
391 # are additional pages to be had. Stop here...
392 $this->nextPage[$type] = $humanSortkey;
393 break;
394 }
395
396 if ( $title->getNamespace() == NS_CATEGORY ) {
397 $cat = Category::newFromRow( $row, $title );
398 $this->addSubcategoryObject( $cat, $humanSortkey, $row->page_len );
399 } elseif ( $title->getNamespace() == NS_FILE ) {
400 $this->addImage( $title, $humanSortkey, $row->page_len, $row->page_is_redirect );
401 } else {
402 $this->addPage( $title, $humanSortkey, $row->page_len, $row->page_is_redirect );
403 }
404 }
405 }
406 }
407
408 function getCategoryTop() {
409 $r = $this->getCategoryBottom();
410 return $r === ''
411 ? $r
412 : "<br style=\"clear:both;\"/>\n" . $r;
413 }
414
415 function getSubcategorySection() {
416 # Don't show subcategories section if there are none.
417 $r = '';
418 $rescnt = count( $this->children );
419 $dbcnt = $this->cat->getSubcatCount();
420 $countmsg = $this->getCountMessage( $rescnt, $dbcnt, 'subcat' );
421
422 if ( $rescnt > 0 ) {
423 # Showing subcategories
424 $r .= "<div id=\"mw-subcategories\">\n";
425 $r .= '<h2>' . wfMsg( 'subcategories' ) . "</h2>\n";
426 $r .= $countmsg;
427 $r .= $this->getSectionPagingLinks( 'subcat' );
428 $r .= $this->formatList( $this->children, $this->children_start_char );
429 $r .= $this->getSectionPagingLinks( 'subcat' );
430 $r .= "\n</div>";
431 }
432 return $r;
433 }
434
435 function getPagesSection() {
436 $ti = htmlspecialchars( $this->title->getText() );
437 # Don't show articles section if there are none.
438 $r = '';
439
440 # @todo FIXME: Here and in the other two sections: we don't need to bother
441 # with this rigamarole if the entire category contents fit on one page
442 # and have already been retrieved. We can just use $rescnt in that
443 # case and save a query and some logic.
444 $dbcnt = $this->cat->getPageCount() - $this->cat->getSubcatCount()
445 - $this->cat->getFileCount();
446 $rescnt = count( $this->articles );
447 $countmsg = $this->getCountMessage( $rescnt, $dbcnt, 'article' );
448
449 if ( $rescnt > 0 ) {
450 $r = "<div id=\"mw-pages\">\n";
451 $r .= '<h2>' . wfMsg( 'category_header', $ti ) . "</h2>\n";
452 $r .= $countmsg;
453 $r .= $this->getSectionPagingLinks( 'page' );
454 $r .= $this->formatList( $this->articles, $this->articles_start_char );
455 $r .= $this->getSectionPagingLinks( 'page' );
456 $r .= "\n</div>";
457 }
458 return $r;
459 }
460
461 function getImageSection() {
462 $r = '';
463 $rescnt = $this->showGallery ? $this->gallery->count() : count( $this->imgsNoGallery );
464 if ( $rescnt > 0 ) {
465 $dbcnt = $this->cat->getFileCount();
466 $countmsg = $this->getCountMessage( $rescnt, $dbcnt, 'file' );
467
468 $r .= "<div id=\"mw-category-media\">\n";
469 $r .= '<h2>' . wfMsg( 'category-media-header', htmlspecialchars( $this->title->getText() ) ) . "</h2>\n";
470 $r .= $countmsg;
471 $r .= $this->getSectionPagingLinks( 'file' );
472 if ( $this->showGallery ) {
473 $r .= $this->gallery->toHTML();
474 } else {
475 $r .= $this->formatList( $this->imgsNoGallery, $this->imgsNoGallery_start_char );
476 }
477 $r .= $this->getSectionPagingLinks( 'file' );
478 $r .= "\n</div>";
479 }
480 return $r;
481 }
482
483 /**
484 * Get the paging links for a section (subcats/pages/files), to go at the top and bottom
485 * of the output.
486 *
487 * @param $type String: 'page', 'subcat', or 'file'
488 * @return String: HTML output, possibly empty if there are no other pages
489 */
490 private function getSectionPagingLinks( $type ) {
491 if ( $this->until[$type] !== null ) {
492 return $this->pagingLinks( $this->nextPage[$type], $this->until[$type], $type );
493 } elseif ( $this->nextPage[$type] !== null || $this->from[$type] !== null ) {
494 return $this->pagingLinks( $this->from[$type], $this->nextPage[$type], $type );
495 } else {
496 return '';
497 }
498 }
499
500 function getCategoryBottom() {
501 return '';
502 }
503
504 /**
505 * Format a list of articles chunked by letter, either as a
506 * bullet list or a columnar format, depending on the length.
507 *
508 * @param $articles Array
509 * @param $articles_start_char Array
510 * @param $cutoff Int
511 * @return String
512 * @private
513 */
514 function formatList( $articles, $articles_start_char, $cutoff = 6 ) {
515 $list = '';
516 if ( count ( $articles ) > $cutoff ) {
517 $list = self::columnList( $articles, $articles_start_char );
518 } elseif ( count( $articles ) > 0 ) {
519 // for short lists of articles in categories.
520 $list = self::shortList( $articles, $articles_start_char );
521 }
522
523 $pageLang = $this->title->getPageLanguage();
524 $attribs = array( 'lang' => $pageLang->getCode(), 'dir' => $pageLang->getDir(),
525 'class' => 'mw-content-'.$pageLang->getDir() );
526 $list = Html::rawElement( 'div', $attribs, $list );
527
528 return $list;
529 }
530
531 /**
532 * Format a list of articles chunked by letter in a three-column
533 * list, ordered vertically.
534 *
535 * TODO: Take the headers into account when creating columns, so they're
536 * more visually equal.
537 *
538 * More distant TODO: Scrap this and use CSS columns, whenever IE finally
539 * supports those.
540 *
541 * @param $articles Array
542 * @param $articles_start_char Array
543 * @return String
544 * @private
545 */
546 static function columnList( $articles, $articles_start_char ) {
547 $columns = array_combine( $articles, $articles_start_char );
548 # Split into three columns
549 $columns = array_chunk( $columns, ceil( count( $columns ) / 3 ), true /* preserve keys */ );
550
551 $ret = '<table width="100%"><tr valign="top"><td>';
552 $prevchar = null;
553
554 foreach ( $columns as $column ) {
555 $colContents = array();
556
557 # Kind of like array_flip() here, but we keep duplicates in an
558 # array instead of dropping them.
559 foreach ( $column as $article => $char ) {
560 if ( !isset( $colContents[$char] ) ) {
561 $colContents[$char] = array();
562 }
563 $colContents[$char][] = $article;
564 }
565
566 $first = true;
567 foreach ( $colContents as $char => $articles ) {
568 $ret .= '<h3>' . htmlspecialchars( $char );
569 if ( $first && $char === $prevchar ) {
570 # We're continuing a previous chunk at the top of a new
571 # column, so add " cont." after the letter.
572 $ret .= ' ' . wfMsgHtml( 'listingcontinuesabbrev' );
573 }
574 $ret .= "</h3>\n";
575
576 $ret .= '<ul><li>';
577 $ret .= implode( "</li>\n<li>", $articles );
578 $ret .= '</li></ul>';
579
580 $first = false;
581 $prevchar = $char;
582 }
583
584 $ret .= "</td>\n<td>";
585 }
586
587 $ret .= '</td></tr></table>';
588 return $ret;
589 }
590
591 /**
592 * Format a list of articles chunked by letter in a bullet list.
593 * @param $articles Array
594 * @param $articles_start_char Array
595 * @return String
596 * @private
597 */
598 static function shortList( $articles, $articles_start_char ) {
599 $r = '<h3>' . htmlspecialchars( $articles_start_char[0] ) . "</h3>\n";
600 $r .= '<ul><li>' . $articles[0] . '</li>';
601 for ( $index = 1; $index < count( $articles ); $index++ ) {
602 if ( $articles_start_char[$index] != $articles_start_char[$index - 1] ) {
603 $r .= "</ul><h3>" . htmlspecialchars( $articles_start_char[$index] ) . "</h3>\n<ul>";
604 }
605
606 $r .= "<li>{$articles[$index]}</li>";
607 }
608 $r .= '</ul>';
609 return $r;
610 }
611
612 /**
613 * Create paging links, as a helper method to getSectionPagingLinks().
614 *
615 * @param $first String The 'until' parameter for the generated URL
616 * @param $last String The 'from' parameter for the genererated URL
617 * @param $type String A prefix for parameters, 'page' or 'subcat' or
618 * 'file'
619 * @return String HTML
620 */
621 private function pagingLinks( $first, $last, $type = '' ) {
622 $prevLink = wfMessage( 'prevn' )->numParams( $this->limit )->escaped();
623
624 if ( $first != '' ) {
625 $prevQuery = $this->query;
626 $prevQuery["{$type}until"] = $first;
627 unset( $prevQuery["{$type}from"] );
628 $prevLink = Linker::linkKnown(
629 $this->addFragmentToTitle( $this->title, $type ),
630 $prevLink,
631 array(),
632 $prevQuery
633 );
634 }
635
636 $nextLink = wfMessage( 'nextn' )->numParams( $this->limit )->escaped();
637
638 if ( $last != '' ) {
639 $lastQuery = $this->query;
640 $lastQuery["{$type}from"] = $last;
641 unset( $lastQuery["{$type}until"] );
642 $nextLink = Linker::linkKnown(
643 $this->addFragmentToTitle( $this->title, $type ),
644 $nextLink,
645 array(),
646 $lastQuery
647 );
648 }
649
650 return "($prevLink) ($nextLink)";
651 }
652
653 /**
654 * Takes a title, and adds the fragment identifier that
655 * corresponds to the correct segment of the category.
656 *
657 * @param Title $title: The title (usually $this->title)
658 * @param String $section: Which section
659 */
660 private function addFragmentToTitle( $title, $section ) {
661 switch ( $section ) {
662 case 'page':
663 $fragment = 'mw-pages';
664 break;
665 case 'subcat':
666 $fragment = 'mw-subcategories';
667 break;
668 case 'file':
669 $fragment = 'mw-category-media';
670 break;
671 default:
672 throw new MWException( __METHOD__ .
673 " Invalid section $section." );
674 }
675
676 return Title::makeTitle( $title->getNamespace(),
677 $title->getDBkey(), $fragment );
678 }
679 /**
680 * What to do if the category table conflicts with the number of results
681 * returned? This function says what. Each type is considered independently
682 * of the other types.
683 *
684 * Note for grepping: uses the messages category-article-count,
685 * category-article-count-limited, category-subcat-count,
686 * category-subcat-count-limited, category-file-count,
687 * category-file-count-limited.
688 *
689 * @param $rescnt Int: The number of items returned by our database query.
690 * @param $dbcnt Int: The number of items according to the category table.
691 * @param $type String: 'subcat', 'article', or 'file'
692 * @return String: A message giving the number of items, to output to HTML.
693 */
694 private function getCountMessage( $rescnt, $dbcnt, $type ) {
695 # There are three cases:
696 # 1) The category table figure seems sane. It might be wrong, but
697 # we can't do anything about it if we don't recalculate it on ev-
698 # ery category view.
699 # 2) The category table figure isn't sane, like it's smaller than the
700 # number of actual results, *but* the number of results is less
701 # than $this->limit and there's no offset. In this case we still
702 # know the right figure.
703 # 3) We have no idea.
704
705 # Check if there's a "from" or "until" for anything
706
707 // This is a little ugly, but we seem to use different names
708 // for the paging types then for the messages.
709 if ( $type === 'article' ) {
710 $pagingType = 'page';
711 } else {
712 $pagingType = $type;
713 }
714
715 $fromOrUntil = false;
716 if ( $this->from[$pagingType] !== null || $this->until[$pagingType] !== null ) {
717 $fromOrUntil = true;
718 }
719
720 if ( $dbcnt == $rescnt || ( ( $rescnt == $this->limit || $fromOrUntil )
721 && $dbcnt > $rescnt ) ) {
722 # Case 1: seems sane.
723 $totalcnt = $dbcnt;
724 } elseif ( $rescnt < $this->limit && !$fromOrUntil ) {
725 # Case 2: not sane, but salvageable. Use the number of results.
726 # Since there are fewer than 200, we can also take this opportunity
727 # to refresh the incorrect category table entry -- which should be
728 # quick due to the small number of entries.
729 $totalcnt = $rescnt;
730 $this->cat->refreshCounts();
731 } else {
732 # Case 3: hopeless. Don't give a total count at all.
733 return wfMessage( "category-$type-count-limited" )->numParams( $rescnt )->parseAsBlock();
734 }
735 return wfMessage( "category-$type-count" )->numParams( $rescnt, $totalcnt )->parseAsBlock();
736 }
737 }