23use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
27 use ProtectedHookAccessorTrait;
98 'mediawiki.action.view.categoryPage.styles'
103 $this->cat = Category::newFromTitle(
$title );
105 $this->collation = Collation::singleton();
106 unset( $this->query[
'title'] );
115 $this->showGallery = $this->
getConfig()->get(
'CategoryMagicGallery' )
141 $r = $this->
msg(
'category-empty' )->parseAsBlock();
146 'class' =>
'mw-category-generated',
147 'lang' =>
$lang->getHtmlCode(),
148 'dir' =>
$lang->getDir()
150 # put a div around the headings which are in the user language
151 $r = Html::openElement(
'div', $attribs ) . $r .
'</div>';
157 $this->articles = [];
158 $this->articles_start_char = [];
159 $this->children = [];
160 $this->children_start_char = [];
161 if ( $this->showGallery ) {
163 $mode = $this->
getRequest()->getVal(
'gallerymode',
null );
165 $this->gallery = ImageGalleryBase::factory( $mode, $this->
getContext() );
166 }
catch ( Exception $e ) {
168 $this->gallery = ImageGalleryBase::factory(
false, $this->
getContext() );
171 $this->gallery->setHideBadImages();
173 $this->imgsNoGallery = [];
174 $this->imgsNoGallery_start_char = [];
195 $this->children_start_char[] =
201 $this->getHookRunner()->onCategoryViewer__generateLink(
$type,
$title, $html, $link );
202 if ( $link ===
null ) {
203 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
204 if ( $html !==
null ) {
207 $link = $linkRenderer->makeLink(
$title, $html );
210 $link =
'<span class="redirect-in-category">' . $link .
'</span>';
234 $firstChar = $this->collation->getFirstLetter( $word );
236 return MediaWikiServices::getInstance()->getContentLanguage()->convert( $firstChar );
247 if ( $this->showGallery ) {
248 $flip = $this->flip[
'file'];
250 $this->gallery->insert(
$title );
252 $this->gallery->add(
$title );
255 $this->imgsNoGallery[] = $this->
generateLink(
'image', $title, $isRedirect );
257 $this->imgsNoGallery_start_char[] = MediaWikiServices::getInstance()->
258 getContentLanguage()->convert( $this->collation->getFirstLetter( $sortkey ) );
269 public function addPage(
$title, $sortkey, $pageLength, $isRedirect =
false ) {
272 $this->articles_start_char[] = MediaWikiServices::getInstance()->
273 getContentLanguage()->convert( $this->collation->getFirstLetter( $sortkey ) );
277 if ( $this->flip[
'subcat'] ) {
278 $this->children = array_reverse( $this->children );
279 $this->children_start_char = array_reverse( $this->children_start_char );
281 if ( $this->flip[
'page'] ) {
282 $this->articles = array_reverse( $this->articles );
283 $this->articles_start_char = array_reverse( $this->articles_start_char );
285 if ( !$this->showGallery && $this->flip[
'file'] ) {
286 $this->imgsNoGallery = array_reverse( $this->imgsNoGallery );
287 $this->imgsNoGallery_start_char = array_reverse( $this->imgsNoGallery_start_char );
305 $this->flip = [
'page' =>
false,
'subcat' =>
false,
'file' => false ];
307 foreach ( [
'page',
'subcat',
'file' ] as
$type ) {
308 # Get the sortkeys for start/end, if applicable. Note that if
309 # the collation in the database differs from the one
310 # set in $wgCategoryCollation, pagination might go totally haywire.
311 $extraConds = [
'cl_type' =>
$type ];
312 if ( isset( $this->from[
$type] ) ) {
313 $extraConds[] =
'cl_sortkey >= '
314 .
$dbr->addQuotes( $this->collation->getSortKey( $this->from[
$type] ) );
315 } elseif ( isset( $this->until[
$type] ) ) {
316 $extraConds[] =
'cl_sortkey < '
317 .
$dbr->addQuotes( $this->collation->getSortKey( $this->until[
$type] ) );
318 $this->flip[
$type] =
true;
322 [
'page',
'categorylinks',
'category' ],
324 LinkCache::getSelectFields(),
338 array_merge( [
'cl_to' => $this->title->getDBkey() ], $extraConds ),
341 'USE INDEX' => [
'categorylinks' =>
'cl_sortkey' ],
342 'LIMIT' => $this->limit + 1,
343 'ORDER BY' => $this->flip[
$type] ?
'cl_sortkey DESC' :
'cl_sortkey',
346 'categorylinks' => [
'JOIN',
'cl_from = page_id' ],
347 'category' => [
'LEFT JOIN', [
348 'cat_title = page_title',
354 $this->getHookRunner()->onCategoryViewer__doCategoryQuery(
$type,
$res );
355 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
358 foreach (
$res as $row ) {
359 $title = Title::newFromRow( $row );
360 $linkCache->addGoodLinkObjFromRow(
$title, $row );
362 if ( $row->cl_collation ===
'' ) {
366 $humanSortkey = $row->cl_sortkey;
371 if ( ++$count > $this->limit ) {
372 # We've reached the one extra which shows that there
373 # are additional pages to be had. Stop here...
374 $this->nextPage[
$type] = $humanSortkey;
377 if ( $count == $this->limit ) {
378 $this->prevPage[
$type] = $humanSortkey;
385 $this->
addImage(
$title, $humanSortkey, $row->page_len, $row->page_is_redirect );
387 $this->
addPage(
$title, $humanSortkey, $row->page_len, $row->page_is_redirect );
400 :
"<br style=\"clear:both;\"/>\n" . $r;
407 # Don't show subcategories section if there are none.
409 $rescnt = count( $this->children );
410 $dbcnt = $this->cat->getSubcatCount();
415 # Showing subcategories
416 $r .=
"<div id=\"mw-subcategories\">\n";
417 $r .=
'<h2>' . $this->
msg(
'subcategories' )->parse() .
"</h2>\n";
420 $r .= $this->
formatList( $this->children, $this->children_start_char );
431 $name = $this->
getOutput()->getUnprefixedDisplayTitle();
432 # Don't show articles section if there are none.
435 # @todo FIXME: Here and in the other two sections: we don't need to bother
436 # with this rigmarole if the entire category contents fit on one page
437 # and have already been retrieved. We can just use $rescnt in that
438 # case and save a query and some logic.
439 $dbcnt = $this->cat->getPageCount() - $this->cat->getSubcatCount()
440 - $this->cat->getFileCount();
441 $rescnt = count( $this->articles );
446 $r =
"<div id=\"mw-pages\">\n";
447 $r .=
'<h2>' . $this->
msg(
'category_header' )->rawParams( $name )->parse() .
"</h2>\n";
450 $r .= $this->
formatList( $this->articles, $this->articles_start_char );
461 $name = $this->
getOutput()->getUnprefixedDisplayTitle();
463 $rescnt = $this->showGallery ? $this->gallery->count() : count( $this->imgsNoGallery );
464 $dbcnt = $this->cat->getFileCount();
469 $r .=
"<div id=\"mw-category-media\">\n";
471 $this->
msg(
'category-media-header' )->rawParams( $name )->parse() .
475 if ( $this->showGallery ) {
476 $r .= $this->gallery->toHTML();
478 $r .= $this->
formatList( $this->imgsNoGallery, $this->imgsNoGallery_start_char );
494 if ( isset( $this->until[
$type] ) ) {
499 if ( $this->nextPage[
$type] !==
null ) {
506 } elseif ( $this->nextPage[
$type] !==
null || isset( $this->from[
$type] ) ) {
539 $pageLang = $this->title->getPageLanguage();
540 $attribs = [
'lang' => $pageLang->getHtmlCode(),
'dir' => $pageLang->getDir(),
541 'class' =>
'mw-content-' . $pageLang->getDir() ];
542 $list = Html::rawElement(
'div', $attribs, $list );
564 $ret = Html::openElement(
'div', [
'class' =>
'mw-category' ] );
568 # Kind of like array_flip() here, but we keep duplicates in an
569 # array instead of dropping them.
570 foreach ( $columns as $article => $char ) {
571 if ( !isset( $colContents[$char] ) ) {
572 $colContents[$char] = [];
574 $colContents[$char][] = $article;
577 foreach ( $colContents as $char =>
$articles ) {
578 # Change space to non-breaking space to keep headers aligned
579 $h3char = $char ===
' ' ?
"\u{00A0}" : htmlspecialchars( $char );
581 $ret .=
'<div class="mw-category-group"><h3>' . $h3char;
585 $ret .= implode(
"</li>\n<li>",
$articles );
586 $ret .=
'</li></ul></div>';
590 $ret .= Html::closeElement(
'div' );
604 $r .=
'<ul><li>' .
$articles[0] .
'</li>';
606 for ( $index = 1; $index < $articleCount; $index++ ) {
611 $r .=
"<li>{$articles[$index]}</li>";
627 $prevLink = $this->
msg(
'prev-page' )->escaped();
629 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
630 if ( $first !=
'' ) {
632 $prevQuery[
"{$type}until"] = $first;
633 unset( $prevQuery[
"{$type}from"] );
634 $prevLink = $linkRenderer->makeKnownLink(
642 $nextLink = $this->
msg(
'next-page' )->escaped();
646 $lastQuery[
"{$type}from"] = $last;
647 unset( $lastQuery[
"{$type}until"] );
648 $nextLink = $linkRenderer->makeKnownLink(
656 return $this->
msg(
'categoryviewer-pagedlinks' )->rawParams( $prevLink, $nextLink )->escaped();
669 switch ( $section ) {
671 $fragment =
'mw-pages';
674 $fragment =
'mw-subcategories';
677 $fragment =
'mw-category-media';
681 " Invalid section $section." );
713 if (
$type ===
'article' ) {
714 $pagingType =
'page';
719 $fromOrUntil =
false;
720 if ( isset( $this->from[$pagingType] ) || isset( $this->until[$pagingType] ) ) {
724 if ( $dbcnt == $rescnt ||
725 ( ( $rescnt == $this->limit || $fromOrUntil ) && $dbcnt > $rescnt )
729 } elseif ( $rescnt < $this->limit && !$fromOrUntil ) {
736 return $this->
msg(
"category-$type-count-limited" )->numParams( $rescnt )->parseAsBlock();
739 return $this->
msg(
"category-$type-count" )->numParams( $rescnt, $totalcnt )->parseAsBlock();
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
getCountMessage( $rescnt, $dbcnt, $type)
What to do if the category table conflicts with the number of results returned? This function says wh...
array $articles_start_char
array $children_start_char
pagingLinks( $first, $last, $type='')
Create paging links, as a helper method to getSectionPagingLinks().
getSectionPagingLinks( $type)
Get the paging links for a section (subcats/pages/files), to go at the top and bottom of the output.
Category $cat
Category object for this page.
__construct( $title, IContextSource $context, $from=[], $until=[], $query=[])
addPage( $title, $sortkey, $pageLength, $isRedirect=false)
Add a miscellaneous page.
ImageGalleryBase $gallery
addSubcategoryObject(Category $cat, $sortkey, $pageLength)
Add a subcategory to the internal lists, using a Category object.
formatList( $articles, $articles_start_char, $cutoff=6)
Format a list of articles chunked by letter, either as a bullet list or a columnar format,...
addImage(Title $title, $sortkey, $pageLength, $isRedirect=false)
Add a page in the image namespace.
static columnList( $articles, $articles_start_char)
Format a list of articles chunked by letter in a three-column list, ordered vertically.
getSubcategorySortChar( $title, $sortkey)
Get the character to be used for sorting subcategories.
getHTML()
Format the category data list.
addFragmentToTitle( $title, $section)
Takes a title, and adds the fragment identifier that corresponds to the correct segment of the catego...
array $imgsNoGallery_start_char
generateLink( $type, Title $title, $isRedirect, $html=null)
array $query
The original query array, to be used in generating paging links.
static shortList( $articles, $articles_start_char)
Format a list of articles chunked by letter in a bullet list.
Category objects are immutable, strictly speaking.
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
getContext()
Get the base IContextSource object.
setContext(IContextSource $context)
Marks HTML that shouldn't be escaped.
Represents a title within MediaWiki.
getNamespace()
Get the namespace index, i.e.
getDBkey()
Get the main part with underscores.
getText()
Get the text form (spaces not underscores) of the main part.
isRedirect( $flags=0)
Is this an article that is a redirect page? Uses link cache, adding it if necessary.
getPrefixedText()
Get the prefixed title with spaces.
getCategorySortkey( $prefix='')
Returns the raw sort key to be used for categories, with the specified prefix.
Interface for objects which can provide a MediaWiki context on request.
getConfig()
Get the site configuration.
if(!isset( $args[0])) $lang