MediaWiki master
CategoryViewer.php
Go to the documentation of this file.
1<?php
9namespace MediaWiki\Category;
10
11use InvalidArgumentException;
15use MediaWiki\Debug\DeprecationHelper;
19use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
34
36 use ProtectedHookAccessorTrait;
37 use DeprecationHelper;
38
39 public readonly int $limit;
41 public array $articles = [];
43 public array $articles_start_char = [];
45 public array $children = [];
47 public array $children_start_char = [];
48 public bool $showGallery;
50 public array $imgsNoGallery_start_char = [];
52 public array $imgsNoGallery = [];
54 public array $nextPage = [];
56 protected array $prevPage = [];
58 public array $flip = [];
59
66 public readonly bool $sortByTimestamp;
67
68 public readonly Collation $collation;
70
72 private readonly Category $cat;
73
74 private readonly ILanguageConverter $languageConverter;
75
85 public function __construct(
86 protected PageIdentity $page,
87 IContextSource $context,
88 public readonly array $from = [],
89 public readonly array $until = [],
90 private array $query = [],
91 ) {
92 $this->deprecatePublicPropertyFallback(
93 'title',
94 '1.37',
95 fn (): Title => Title::newFromPageIdentity( $this->page ),
96 fn ( PageIdentity $page ) => $this->page = $page
97 );
98
99 $this->setContext( $context );
100 $this->getOutput()->addModuleStyles( [
101 'mediawiki.action.styles',
102 ] );
103 $this->limit = $context->getConfig()->get( MainConfigNames::CategoryPagingLimit );
104 $this->sortByTimestamp = $context->getRequest()->getVal( 'cldsort' ) === 'timestamp';
105 $this->cat = Category::newFromTitle( $page );
106
107 $services = MediaWikiServices::getInstance();
108 $this->collation = $services->getCollationFactory()->getCategoryCollation();
109 $this->languageConverter = $services->getLanguageConverterFactory()->getLanguageConverter();
110
111 unset( $this->query['title'] );
112 }
113
119 public function getHTML() {
120 $this->showGallery = $this->getConfig()->get( MainConfigNames::CategoryMagicGallery )
121 && !$this->getOutput()->getOutputFlag( ParserOutputFlags::NO_GALLERY );
122
123 $this->clearCategoryState();
124 $this->doCategoryQuery();
125 $this->finaliseCategoryState();
126
127 $html = $this->getSubcategorySection() .
128 $this->getPagesSection() .
129 $this->getImageSection();
130
131 if ( $html === '' ) {
132 $html = $this->msg( 'category-empty' )->parseAsBlock();
133 }
134
135 # put a div around the headings which are in the user language
136 $lang = $this->getLanguage();
137 return Html::rawElement( 'div', [
138 'class' => 'mw-category-generated',
139 'lang' => $lang->getHtmlCode(),
140 'dir' => $lang->getDir()
141 ], $html );
142 }
143
144 protected function clearCategoryState() {
145 $this->articles = [];
146 $this->articles_start_char = [];
147 $this->children = [];
148 $this->children_start_char = [];
149 if ( $this->showGallery ) {
150 // Note that null for mode is taken to mean use default.
151 $mode = $this->getRequest()->getVal( 'gallerymode', null );
152 try {
153 $this->gallery = ImageGalleryBase::factory( $mode, $this->getContext() );
155 // User specified something invalid, fallback to default.
156 $this->gallery = ImageGalleryBase::factory( false, $this->getContext() );
157 }
158
159 $this->gallery->setHideBadImages();
160 } else {
161 $this->imgsNoGallery = [];
162 $this->imgsNoGallery_start_char = [];
163 }
164 }
165
172 public function addSubcategoryObject( Category $cat, string $sortkey, int $pageLength ): void {
173 $page = $cat->getPage();
174 if ( !$page ) {
175 return;
176 }
177
178 // Subcategory; strip the 'Category' namespace from the link text.
179 $pageRecord = MediaWikiServices::getInstance()->getPageStore()
180 ->getPageByReference( $page );
181 if ( !$pageRecord ) {
182 return;
183 }
184
185 $this->children[] = $this->generateLink(
186 'subcat',
187 $pageRecord,
188 $pageRecord->isRedirect(),
189 htmlspecialchars( str_replace( '_', ' ', $pageRecord->getDBkey() ) )
190 );
191
192 $this->children_start_char[] = $this->getStartKey( $sortkey );
193 }
194
206 private function getStartKey( string $sortkey ): string {
207 if ( $this->sortByTimestamp ) {
208 return $sortkey;
209 }
210 return $this->languageConverter->convert( $this->collation->getFirstLetter( $sortkey ) );
211 }
212
224 private function generateLink(
225 string $type,
226 PageReference $page,
227 bool $isRedirect,
228 ?string $html = null
229 ): string {
230 $link = null;
231 $legacyTitle = MediaWikiServices::getInstance()->getTitleFactory()
232 ->newFromPageReference( $page );
233 // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
234 $this->getHookRunner()->onCategoryViewer__generateLink( $type, $legacyTitle, $html, $link );
235 if ( $link === null ) {
236 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
237 if ( $html !== null ) {
238 $html = new HtmlArmor( $html );
239 }
240 $link = $linkRenderer->makeLink( $page, $html );
241 }
242 if ( $isRedirect ) {
243 $link = Html::rawElement(
244 'span',
245 [ 'class' => 'redirect-in-category' ],
246 $link
247 );
248 }
249
250 return $link;
251 }
252
260 public function addImage(
261 PageReference $page,
262 string $sortkey,
263 int $pageLength,
264 bool $isRedirect = false
265 ): void {
266 $title = MediaWikiServices::getInstance()->getTitleFactory()
267 ->newFromPageReference( $page );
268 if ( $this->showGallery ) {
269 $flip = $this->flip['file'];
270 if ( $flip ) {
271 $this->gallery->insert( $title, '', '', '', [], ImageGalleryBase::LOADING_LAZY );
272 } else {
273 $this->gallery->add( $title, '', '', '', [], ImageGalleryBase::LOADING_LAZY );
274 }
275 } else {
276 $this->imgsNoGallery[] = $this->generateLink( 'image', $page, $isRedirect );
277
278 $this->imgsNoGallery_start_char[] = $this->getStartKey( $sortkey );
279 }
280 }
281
289 public function addPage(
290 PageReference $page,
291 string $sortkey,
292 int $pageLength,
293 bool $isRedirect = false
294 ): void {
295 $this->articles[] = $this->generateLink( 'page', $page, $isRedirect );
296
297 $this->articles_start_char[] = $this->getStartKey( $sortkey );
298 }
299
300 protected function finaliseCategoryState() {
301 if ( $this->flip['subcat'] ) {
302 $this->children = array_reverse( $this->children );
303 $this->children_start_char = array_reverse( $this->children_start_char );
304 }
305 if ( $this->flip['page'] ) {
306 $this->articles = array_reverse( $this->articles );
307 $this->articles_start_char = array_reverse( $this->articles_start_char );
308 }
309 if ( !$this->showGallery && $this->flip['file'] ) {
310 $this->imgsNoGallery = array_reverse( $this->imgsNoGallery );
311 $this->imgsNoGallery_start_char = array_reverse( $this->imgsNoGallery_start_char );
312 }
313 }
314
315 protected function doCategoryQuery() {
316 $connProvider = MediaWikiServices::getInstance()->getConnectionProvider();
317 $dbr = $connProvider->getReplicaDatabase();
318 $categoryLinksDbr = $connProvider->getReplicaDatabase( CategoryLinksTable::VIRTUAL_DOMAIN );
319
320 $this->nextPage = [
321 'page' => null,
322 'subcat' => null,
323 'file' => null,
324 ];
325 $this->prevPage = [
326 'page' => null,
327 'subcat' => null,
328 'file' => null,
329 ];
330
331 $this->flip = [ 'page' => false, 'subcat' => false, 'file' => false ];
332
333 // The DB column driving ordering and pagination for this view: either the
334 // collation-based cl_sortkey (default), or cl_timestamp when the caller
335 // asked for members ordered by when they were added to the category.
336 $sortField = $this->sortByTimestamp ? 'cl_timestamp' : 'cl_sortkey';
337
338 foreach ( [ 'page', 'subcat', 'file' ] as $type ) {
339 # Get the sort values for start/end, if applicable. Note that if
340 # the collation in the database differs from the one
341 # set in $wgCategoryCollation, pagination might go totally haywire.
342 $extraConds = [ 'cl_type' => $type ];
343 if ( isset( $this->from[$type] ) ) {
344 $extraConds[] = $categoryLinksDbr->expr(
345 $sortField,
346 '>=',
347 $this->sortByTimestamp
348 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
349 ? $categoryLinksDbr->timestamp( $this->from[$type] )
350 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
351 : $this->collation->getSortKey( $this->from[$type] )
352 );
353 } elseif ( isset( $this->until[$type] ) ) {
354 $extraConds[] = $categoryLinksDbr->expr(
355 $sortField,
356 '<',
357 $this->sortByTimestamp
358 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
359 ? $categoryLinksDbr->timestamp( $this->until[$type] )
360 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
361 : $this->collation->getSortKey( $this->until[$type] )
362 );
363 $this->flip[$type] = true;
364 }
365
366 $queryBuilder = $categoryLinksDbr->newSelectQueryBuilder();
367 $queryBuilder->select( array_merge(
368 LinkCache::getSelectFields(),
369 [
370 'cl_sortkey',
371 'cl_sortkey_prefix',
372 'cl_timestamp',
373 'collation_name',
374 ]
375 ) )
376 ->from( 'page' )
377 ->andWhere( $extraConds );
378
379 if ( $this->flip[$type] ) {
380 $queryBuilder->orderBy( $sortField, SelectQueryBuilder::SORT_DESC );
381 } else {
382 $queryBuilder->orderBy( $sortField );
383 }
384
385 $queryBuilder
386 ->join( 'categorylinks', null, [ 'cl_from = page_id' ] )
387 ->join( 'linktarget', null, 'cl_target_id = lt_id' )
388 ->straightJoin( 'collation', null, 'cl_collation_id = collation_id' )
389 ->where( [ 'lt_title' => $this->page->getDBkey(), 'lt_namespace' => NS_CATEGORY ] )
390 ->useIndex( [ 'categorylinks' => $this->sortByTimestamp ? 'cl_timestamp_id' : 'cl_sortkey_id' ] )
391 ->limit( $this->limit + 1 );
392
393 $res = $queryBuilder->caller( __METHOD__ )->fetchResultSet();
394
395 $categoryTitles = [];
396 $pageRows = [];
397
398 foreach ( $res as $row ) {
399 $pageRows[] = $row;
400 if ( (int)$row->page_namespace === NS_CATEGORY ) {
401 $categoryTitles[] = $row->page_title;
402 }
403 }
404
405 $categoryFields = [ 'cat_id', 'cat_title', 'cat_subcats', 'cat_pages', 'cat_files' ];
406
407 $categoryData = [];
408 if ( $categoryTitles !== [] ) {
409 $categoryRes = $dbr->newSelectQueryBuilder()
410 ->select( $categoryFields )
411 ->from( 'category' )
412 ->where( [ 'cat_title' => $categoryTitles ] )
413 ->caller( __METHOD__ )
414 ->fetchResultSet();
415
416 foreach ( $categoryRes as $catRow ) {
417 $categoryData[$catRow->cat_title] = $catRow;
418 }
419 }
420
421 foreach ( $pageRows as $row ) {
422 if ( (int)$row->page_namespace === NS_CATEGORY ) {
423 $catRow = $categoryData[$row->page_title] ?? null;
424 foreach ( $categoryFields as $field ) {
425 $row->$field = $catRow->$field ?? null;
426 }
427 }
428 }
429
430 // Convert modified pageRows back to result wrapper for hook
431 $res = new FakeResultWrapper( $pageRows );
432 $this->getHookRunner()->onCategoryViewer__doCategoryQuery( $type, $res );
433 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
434
435 $count = 0;
436 foreach ( $pageRows as $row ) {
437 $title = Title::newFromRow( $row );
438 $linkCache->addGoodLinkObjFromRow( $title, $row );
439
440 if ( $this->sortByTimestamp ) {
441 // Raw DB value, used verbatim for from/until pagination.
442 $pageCursor = $row->cl_timestamp;
443 // Group entries by the day they were added to the category.
444 $groupLabel = $this->getLanguage()->userDate( $row->cl_timestamp, $this->getUser() );
445 } else {
446 $pageCursor = $title->getCategorySortkey( $row->cl_sortkey_prefix );
447 $groupLabel = $pageCursor;
448 }
449
450 if ( ++$count > $this->limit ) {
451 # We've reached the one extra which shows that there
452 # are additional pages to be had. Stop here...
453 $this->nextPage[$type] = $pageCursor;
454 break;
455 }
456 if ( $count == $this->limit ) {
457 $this->prevPage[$type] = $pageCursor;
458 }
459
460 if ( $title->getNamespace() === NS_CATEGORY ) {
461 $cat = Category::newFromRow( $row, $title );
462 $this->addSubcategoryObject( $cat, $groupLabel, $row->page_len );
463 } elseif ( $title->getNamespace() === NS_FILE ) {
464 $this->addImage( $title, $groupLabel, $row->page_len, $row->page_is_redirect );
465 } else {
466 $this->addPage( $title, $groupLabel, $row->page_len, $row->page_is_redirect );
467 }
468 }
469 }
470 }
471
475 protected function getSubcategorySection() {
476 # Don't show subcategories section if there are none.
477 $html = '';
478 $localCount = count( $this->children );
479 $databaseCount = $this->cat->getSubcatCount();
480 // This function should be called even if the result isn't used, it has side-effects
481 $countMessage = $this->getCountMessage( $localCount, $databaseCount, 'subcat' );
482
483 if ( $localCount > 0 ) {
484 $html .= Html::openElement( 'div', [ 'id' => 'mw-subcategories' ] ) . "\n";
485 $html .= Html::rawElement( 'h2', [], $this->msg( 'subcategories' )->parse() ) . "\n";
486 $html .= $countMessage;
487 $html .= $this->getSectionPagingLinks( 'subcat' );
488 $html .= $this->formatList( $this->children, $this->children_start_char );
489 $html .= $this->getSectionPagingLinks( 'subcat' );
490 $html .= "\n" . Html::closeElement( 'div' );
491 }
492 return $html;
493 }
494
498 protected function getPagesSection() {
499 $name = $this->getOutput()->getUnprefixedDisplayTitle();
500 # Don't show articles section if there are none.
501 $html = '';
502
503 # @todo FIXME: Here and in the other two sections: we don't need to bother
504 # with this rigmarole if the entire category contents fit on one page
505 # and have already been retrieved. We can just use $rescnt in that
506 # case and save a query and some logic.
507 $databaseCount = $this->cat->getPageCount( Category::COUNT_CONTENT_PAGES );
508 $localCount = count( $this->articles );
509 // This function should be called even if the result isn't used, it has side-effects
510 $countMessage = $this->getCountMessage( $localCount, $databaseCount, 'page' );
511
512 if ( $localCount > 0 ) {
513 $html .= Html::openElement( 'div', [ 'id' => 'mw-pages' ] ) . "\n";
514 $html .= Html::rawElement(
515 'h2',
516 [],
517 $this->msg( 'category_header' )->rawParams( $name )->parse()
518 ) . "\n";
519 $html .= $countMessage;
520 $html .= $this->getSectionPagingLinks( 'page' );
521 $html .= $this->formatList( $this->articles, $this->articles_start_char );
522 $html .= $this->getSectionPagingLinks( 'page' );
523 $html .= "\n" . Html::closeElement( 'div' );
524 }
525 return $html;
526 }
527
531 protected function getImageSection() {
532 $name = $this->getOutput()->getUnprefixedDisplayTitle();
533 $html = '';
534 $localCount = $this->showGallery ?
535 $this->gallery->count() :
536 count( $this->imgsNoGallery );
537 $databaseCount = $this->cat->getFileCount();
538 // This function should be called even if the result isn't used, it has side-effects
539 $countMessage = $this->getCountMessage( $localCount, $databaseCount, 'file' );
540
541 if ( $localCount > 0 ) {
542 $html .= Html::openElement( 'div', [ 'id' => 'mw-category-media' ] ) . "\n";
543 $html .= Html::rawElement(
544 'h2',
545 [],
546 $this->msg( 'category-media-header' )->rawParams( $name )->parse()
547 ) . "\n";
548 $html .= $countMessage;
549 $html .= $this->getSectionPagingLinks( 'file' );
550 if ( $this->showGallery ) {
551 $html .= $this->gallery->toHTML();
552 } else {
553 $html .= $this->formatList( $this->imgsNoGallery, $this->imgsNoGallery_start_char );
554 }
555 $html .= $this->getSectionPagingLinks( 'file' );
556 $html .= "\n" . Html::closeElement( 'div' );
557 }
558 return $html;
559 }
560
568 private function getSectionPagingLinks( string $type ): string {
569 if ( isset( $this->until[$type] ) ) {
570 // The new value for the until parameter should be pointing to the first
571 // result displayed on the page which is the second last result retrieved
572 // from the database.The next link should have a from parameter pointing
573 // to the until parameter of the current page.
574 if ( $this->nextPage[$type] !== null ) {
575 return $this->pagingLinks(
576 $this->prevPage[$type] ?? '',
577 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
578 $this->until[$type],
579 $type
580 );
581 }
582
583 // If the nextPage variable is null, it means that we have reached the first page
584 // and therefore the previous link should be disabled.
585 return $this->pagingLinks(
586 '',
587 // @phan-suppress-next-line PhanTypeMismatchArgumentNullable
588 $this->until[$type],
589 $type
590 );
591 } elseif ( $this->nextPage[$type] !== null || isset( $this->from[$type] ) ) {
592 return $this->pagingLinks(
593 $this->from[$type] ?? '',
594 $this->nextPage[$type] ?? '',
595 $type
596 );
597 }
598
599 return '';
600 }
601
612 private function formatList( array $articles, array $articles_start_char, int $cutoff = 6 ): string {
613 $list = '';
614 if ( count( $articles ) > $cutoff ) {
615 $list = self::columnList( $articles, $articles_start_char );
616 } elseif ( count( $articles ) > 0 ) {
617 // for short lists of articles in categories.
618 $list = self::shortList( $articles, $articles_start_char );
619 }
620
621 $pageLang = MediaWikiServices::getInstance()->getTitleFactory()
622 ->newFromPageIdentity( $this->page )
623 ->getPageLanguage();
624 $attribs = [ 'lang' => $pageLang->getHtmlCode(), 'dir' => $pageLang->getDir(),
625 'class' => 'mw-content-' . $pageLang->getDir() ];
626 $list = Html::rawElement( 'div', $attribs, $list );
627
628 return $list;
629 }
630
641 public static function columnList(
642 $articles,
643 $articles_start_char,
644 $cssClasses = 'mw-category mw-category-columns'
645 ) {
646 $columns = array_combine( $articles, $articles_start_char );
647
648 $ret = Html::openElement( 'div', [ 'class' => $cssClasses ] );
649
650 $colContents = [];
651
652 # Kind of like array_flip() here, but we keep duplicates in an
653 # array instead of dropping them.
654 foreach ( $columns as $article => $char ) {
655 $colContents[$char][] = $article;
656 }
657
658 foreach ( $colContents as $char => $articles ) {
659 # Change space to non-breaking space to keep headers aligned
660 $h3char = $char === ' ' ? "\u{00A0}" : htmlspecialchars( $char );
661
662 $ret .= Html::openElement( 'div', [ 'class' => 'mw-category-group' ] );
663 $ret .= Html::rawElement( 'h3', [], $h3char ) . "\n";
664 $ret .= Html::openElement( 'ul' );
665 $ret .= implode(
666 "\n",
667 array_map(
668 static fn ( $article ) => Html::rawElement( 'li', [], $article ),
669 $articles
670 )
671 );
672 $ret .= Html::closeElement( 'ul' ) . Html::closeElement( 'div' );
673
674 }
675
676 $ret .= Html::closeElement( 'div' );
677 return $ret;
678 }
679
688 public static function shortList( $articles, $articles_start_char ) {
689 return self::columnList( $articles, $articles_start_char, 'mw-category' );
690 }
691
700 private function pagingLinks( string $first, string $last, string $type = '' ): string {
701 $prevLink = $this->msg( 'prev-page' )->escaped();
702
703 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
704 if ( $first != '' ) {
705 $prevQuery = $this->query;
706 $prevQuery["{$type}until"] = $first;
707 unset( $prevQuery["{$type}from"] );
708 $prevLink = $linkRenderer->makeKnownLink(
709 $this->addFragmentToTitle( $this->page, $type ),
710 new HtmlArmor( $prevLink ),
711 [],
712 $prevQuery
713 );
714 }
715
716 $nextLink = $this->msg( 'next-page' )->escaped();
717
718 if ( $last != '' ) {
719 $lastQuery = $this->query;
720 $lastQuery["{$type}from"] = $last;
721 unset( $lastQuery["{$type}until"] );
722 $nextLink = $linkRenderer->makeKnownLink(
723 $this->addFragmentToTitle( $this->page, $type ),
724 new HtmlArmor( $nextLink ),
725 [],
726 $lastQuery
727 );
728 }
729
730 return $this->msg( 'categoryviewer-pagedlinks' )->rawParams( $prevLink, $nextLink )->escaped();
731 }
732
741 private function addFragmentToTitle( PageReference $page, string $section ): LinkTarget {
742 $fragment = match ( $section ) {
743 'page' => 'mw-pages',
744 'subcat' => 'mw-subcategories',
745 'file' => 'mw-category-media',
746 default => throw new InvalidArgumentException( __METHOD__ . " Invalid section $section." )
747 };
748 return new TitleValue( $page->getNamespace(), $page->getDBkey(), $fragment );
749 }
750
761 private function getCountMessage( int $localCount, int $databaseCount, string $type ): string {
762 // There are three cases:
763 // 1) The category table figure seems good. It might be wrong, but
764 // we can't do anything about it if we don't recalculate it on ev-
765 // ery category view.
766 // 2) The category table figure isn't good, like it's smaller than the
767 // number of actual results, *but* the number of results is less
768 // than $this->limit and there's no offset. In this case we still
769 // know the right figure.
770 // 3) We have no idea.
771
772 // Check if there's a "from" or "until" for anything
773
774 // This is a little ugly, but we seem to use different names
775 // for the paging types then for the messages.
776 $msgType = $type === 'page' ? 'article' : $type;
777
778 $fromOrUntil = false;
779 if ( isset( $this->from[$type] ) || isset( $this->until[$type] ) ) {
780 $fromOrUntil = true;
781 }
782
783 if ( $databaseCount == $localCount ||
784 ( ( $localCount == $this->limit || $fromOrUntil ) && $databaseCount > $localCount )
785 ) {
786 // Case 1: seems good.
787 $totalCount = $databaseCount;
788 } elseif ( $localCount < $this->limit && !$fromOrUntil ) {
789 // Case 2: not good, but salvageable. Use the number of results.
790 $totalCount = $localCount;
791 } else {
792 // Case 3: hopeless. Don't give a total count at all.
793 // Messages: category-subcat-count-limited, category-article-count-limited,
794 // category-file-count-limited
795 return $this->msg( "category-$msgType-count-limited" )->numParams( $localCount )->parseAsBlock();
796 }
797 // Messages: category-subcat-count, category-article-count, category-file-count
798 return $this->msg( "category-$msgType-count" )->numParams( $localCount, $totalCount )->parseAsBlock();
799 }
800}
const NS_FILE
Definition Defines.php:57
const NS_CATEGORY
Definition Defines.php:65
if(!defined('MW_SETUP_CALLBACK'))
Definition WebStart.php:71
getHTML()
Format the category data list.
static columnList( $articles, $articles_start_char, $cssClasses='mw-category mw-category-columns')
Format a list of articles chunked by letter in a three-column list, ordered vertically.
array array< 'page'| 'subcat'| 'file', bool > $flip
Sorting order for each type.
static shortList( $articles, $articles_start_char)
Format a list of articles chunked by letter in a bullet list.
addImage(PageReference $page, string $sortkey, int $pageLength, bool $isRedirect=false)
Add a page in the image namespace.
__construct(protected PageIdentity $page, IContextSource $context, public readonly array $from=[], public readonly array $until=[], private array $query=[],)
readonly bool $sortByTimestamp
Whether category members should be ordered (and paginated) by cl_timestamp – i.e.
array array< 'page'| 'subcat'| 'file',?string > $prevPage
addPage(PageReference $page, string $sortkey, int $pageLength, bool $isRedirect=false)
Add a miscellaneous page.
array array< 'page'| 'subcat'| 'file',?string > $nextPage
addSubcategoryObject(Category $cat, string $sortkey, int $pageLength)
Add a subcategory to the internal lists, using a Category object.
Category objects are immutable, strictly speaking.
Definition Category.php:30
static newFromTitle(PageIdentity $page)
Factory function.
Definition Category.php:174
The simplest way of implementing IContextSource is to hold a RequestContext as a member variable and ...
setContext(IContextSource $context)
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
getContext()
Get the base IContextSource object.
Class for exceptions thrown by ImageGalleryBase::factory().
static factory( $mode=false, ?IContextSource $context=null)
Get a new image gallery.
This class is a collection of static functions that serve two purposes:
Definition Html.php:44
A class containing constants representing the names of configuration variables.
const CategoryPagingLimit
Name constant for the CategoryPagingLimit setting, for use with Config::get()
const CategoryMagicGallery
Name constant for the CategoryMagicGallery setting, for use with Config::get()
Service locator for MediaWiki core services.
static getInstance()
Returns the global default instance of the top level service locator.
Page existence and metadata cache.
Definition LinkCache.php:53
Represents the target of a wiki link.
Represents a title within MediaWiki.
Definition Title.php:69
Marks HTML that shouldn't be escaped.
Definition HtmlArmor.php:18
Overloads the relevant methods of the real ResultWrapper so it doesn't go anywhere near an actual dat...
Build SELECT queries with a fluent interface.
Interface for objects which can provide a MediaWiki context on request.
getConfig()
Get the site configuration.
The shared interface for all language converters.
Represents the target of a wiki link.
Interface for objects (potentially) representing an editable wiki page.
Interface for objects (potentially) representing a page that can be viewable and linked to on a wiki.
getNamespace()
Returns the page's namespace number.
getDBkey()
Get the page title in DB key form.
msg( $key,... $params)