MediaWiki  master
CategoryViewer.php
Go to the documentation of this file.
1 <?php
23 namespace MediaWiki\Category;
24 
25 use Collation;
26 use ContextSource;
28 use HtmlArmor;
29 use IContextSource;
33 use LinkCache;
34 use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
43 use MWException;
45 
47  use ProtectedHookAccessorTrait;
49 
51  public $limit;
52 
54  public $from;
55 
57  public $until;
58 
60  public $articles;
61 
64 
66  public $children;
67 
70 
72  public $showGallery;
73 
76 
79 
81  public $nextPage;
82 
84  protected $prevPage;
85 
87  public $flip;
88 
90  protected $page;
91 
93  public $collation;
94 
96  public $gallery;
97 
99  private $cat;
100 
102  private $query;
103 
105  private $languageConverter;
106 
116  public function __construct( PageIdentity $page, IContextSource $context, array $from = [],
117  array $until = [], array $query = []
118  ) {
119  $this->page = $page;
120 
122  'title',
123  '1.37',
124  function (): Title {
125  return Title::newFromPageIdentity( $this->page );
126  },
127  function ( PageIdentity $page ) {
128  $this->page = $page;
129  }
130  );
131 
132  $this->setContext( $context );
133  $this->getOutput()->addModuleStyles( [
134  'mediawiki.action.styles',
135  ] );
136  $this->from = $from;
137  $this->until = $until;
138  $this->limit = $context->getConfig()->get( MainConfigNames::CategoryPagingLimit );
139  $this->cat = Category::newFromTitle( $page );
140  $this->query = $query;
141  $this->collation = MediaWikiServices::getInstance()->getCollationFactory()->getCategoryCollation();
142  $this->languageConverter = MediaWikiServices::getInstance()
143  ->getLanguageConverterFactory()->getLanguageConverter();
144  unset( $this->query['title'] );
145  }
146 
152  public function getHTML() {
153  $this->showGallery = $this->getConfig()->get( MainConfigNames::CategoryMagicGallery )
154  && !$this->getOutput()->mNoGallery;
155 
156  $this->clearCategoryState();
157  $this->doCategoryQuery();
158  $this->finaliseCategoryState();
159 
160  $r = $this->getSubcategorySection() .
161  $this->getPagesSection() .
162  $this->getImageSection();
163 
164  if ( $r == '' ) {
165  // If there is no category content to display, only
166  // show the top part of the navigation links.
167  // @todo FIXME: Cannot be completely suppressed because it
168  // is unknown if 'until' or 'from' makes this
169  // give 0 results.
170  $r = $this->getCategoryTop();
171  } else {
172  $r = $this->getCategoryTop() .
173  $r .
174  $this->getCategoryBottom();
175  }
176 
177  // Give a proper message if category is empty
178  if ( $r == '' ) {
179  $r = $this->msg( 'category-empty' )->parseAsBlock();
180  }
181 
182  $lang = $this->getLanguage();
183  $attribs = [
184  'class' => 'mw-category-generated',
185  'lang' => $lang->getHtmlCode(),
186  'dir' => $lang->getDir()
187  ];
188  # put a div around the headings which are in the user language
189  $r = Html::rawElement( 'div', $attribs, $r );
190 
191  return $r;
192  }
193 
194  protected function clearCategoryState() {
195  $this->articles = [];
196  $this->articles_start_char = [];
197  $this->children = [];
198  $this->children_start_char = [];
199  if ( $this->showGallery ) {
200  // Note that null for mode is taken to mean use default.
201  $mode = $this->getRequest()->getVal( 'gallerymode', null );
202  try {
203  $this->gallery = ImageGalleryBase::factory( $mode, $this->getContext() );
204  } catch ( ImageGalleryClassNotFoundException $e ) {
205  // User specified something invalid, fallback to default.
206  $this->gallery = ImageGalleryBase::factory( false, $this->getContext() );
207  }
208 
209  $this->gallery->setHideBadImages();
210  } else {
211  $this->imgsNoGallery = [];
212  $this->imgsNoGallery_start_char = [];
213  }
214  }
215 
222  public function addSubcategoryObject( Category $cat, $sortkey, $pageLength ) {
223  $page = $cat->getPage();
224  if ( !$page ) {
225  return;
226  }
227 
228  // Subcategory; strip the 'Category' namespace from the link text.
229  $pageRecord = MediaWikiServices::getInstance()->getPageStore()
230  ->getPageByReference( $page );
231  if ( !$pageRecord ) {
232  return;
233  }
234 
235  $this->children[] = $this->generateLink(
236  'subcat',
237  $pageRecord,
238  $pageRecord->isRedirect(),
239  htmlspecialchars( str_replace( '_', ' ', $pageRecord->getDBkey() ) )
240  );
241 
242  $this->children_start_char[] =
243  $this->getSubcategorySortChar( $page, $sortkey );
244  }
245 
257  private function generateLink(
258  string $type, PageReference $page, bool $isRedirect, ?string $html = null
259  ): string {
260  $link = null;
261  $legacyTitle = MediaWikiServices::getInstance()->getTitleFactory()
262  ->newFromPageReference( $page );
263  // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
264  $this->getHookRunner()->onCategoryViewer__generateLink( $type, $legacyTitle, $html, $link );
265  if ( $link === null ) {
266  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
267  if ( $html !== null ) {
268  $html = new HtmlArmor( $html );
269  }
270  $link = $linkRenderer->makeLink( $page, $html );
271  }
272  if ( $isRedirect ) {
273  $link = Html::rawElement(
274  'span',
275  [ 'class' => 'redirect-in-category' ],
276  $link
277  );
278  }
279 
280  return $link;
281  }
282 
294  public function getSubcategorySortChar( PageIdentity $page, string $sortkey ): string {
295  $titleText = MediaWikiServices::getInstance()->getTitleFormatter()
296  ->getPrefixedText( $page );
297  if ( $titleText === $sortkey ) {
298  $word = $page->getDBkey();
299  } else {
300  $word = $sortkey;
301  }
302 
303  $firstChar = $this->collation->getFirstLetter( $word );
304 
305  return $this->languageConverter->convert( $firstChar );
306  }
307 
315  public function addImage(
316  PageReference $page, string $sortkey, int $pageLength, bool $isRedirect = false
317  ): void {
318  $title = MediaWikiServices::getInstance()->getTitleFactory()
319  ->newFromPageReference( $page );
320  if ( $this->showGallery ) {
321  $flip = $this->flip['file'];
322  if ( $flip ) {
323  $this->gallery->insert( $title, '', '', '', [], ImageGalleryBase::LOADING_LAZY );
324  } else {
325  $this->gallery->add( $title, '', '', '', [], ImageGalleryBase::LOADING_LAZY );
326  }
327  } else {
328  $this->imgsNoGallery[] = $this->generateLink( 'image', $page, $isRedirect );
329 
330  $this->imgsNoGallery_start_char[] =
331  $this->languageConverter->convert( $this->collation->getFirstLetter( $sortkey ) );
332  }
333  }
334 
342  public function addPage(
343  PageReference $page,
344  string $sortkey,
345  int $pageLength,
346  bool $isRedirect = false
347  ): void {
348  $this->articles[] = $this->generateLink( 'page', $page, $isRedirect );
349 
350  $this->articles_start_char[] =
351  $this->languageConverter->convert( $this->collation->getFirstLetter( $sortkey ) );
352  }
353 
354  protected function finaliseCategoryState() {
355  if ( $this->flip['subcat'] ) {
356  $this->children = array_reverse( $this->children );
357  $this->children_start_char = array_reverse( $this->children_start_char );
358  }
359  if ( $this->flip['page'] ) {
360  $this->articles = array_reverse( $this->articles );
361  $this->articles_start_char = array_reverse( $this->articles_start_char );
362  }
363  if ( !$this->showGallery && $this->flip['file'] ) {
364  $this->imgsNoGallery = array_reverse( $this->imgsNoGallery );
365  $this->imgsNoGallery_start_char = array_reverse( $this->imgsNoGallery_start_char );
366  }
367  }
368 
369  protected function doCategoryQuery() {
370  $dbr = wfGetDB( DB_REPLICA, 'category' );
371 
372  $this->nextPage = [
373  'page' => null,
374  'subcat' => null,
375  'file' => null,
376  ];
377  $this->prevPage = [
378  'page' => null,
379  'subcat' => null,
380  'file' => null,
381  ];
382 
383  $this->flip = [ 'page' => false, 'subcat' => false, 'file' => false ];
384 
385  foreach ( [ 'page', 'subcat', 'file' ] as $type ) {
386  # Get the sortkeys for start/end, if applicable. Note that if
387  # the collation in the database differs from the one
388  # set in $wgCategoryCollation, pagination might go totally haywire.
389  $extraConds = [ 'cl_type' => $type ];
390  if ( isset( $this->from[$type] ) ) {
391  $extraConds[] = 'cl_sortkey >= '
392  . $dbr->addQuotes( $this->collation->getSortKey( $this->from[$type] ) );
393  } elseif ( isset( $this->until[$type] ) ) {
394  $extraConds[] = 'cl_sortkey < '
395  . $dbr->addQuotes( $this->collation->getSortKey( $this->until[$type] ) );
396  $this->flip[$type] = true;
397  }
398 
399  $queryBuilder = $dbr->newSelectQueryBuilder();
400  $queryBuilder->select( array_merge(
402  [
403  'cl_sortkey',
404  'cat_id',
405  'cat_title',
406  'cat_subcats',
407  'cat_pages',
408  'cat_files',
409  'cl_sortkey_prefix',
410  'cl_collation'
411  ]
412  ) )
413  ->from( 'page' )
414  ->where( [ 'cl_to' => $this->page->getDBkey() ] )
415  ->andWhere( $extraConds )
416  ->useIndex( [ 'categorylinks' => 'cl_sortkey' ] );
417 
418  if ( $this->flip[$type] ) {
419  $queryBuilder->orderBy( 'cl_sortkey', SelectQueryBuilder::SORT_DESC );
420  } else {
421  $queryBuilder->orderBy( 'cl_sortkey' );
422  }
423 
424  $queryBuilder
425  ->join( 'categorylinks', null, [ 'cl_from = page_id' ] )
426  ->leftJoin( 'category', null, [
427  'cat_title = page_title',
428  'page_namespace' => NS_CATEGORY
429  ] )
430  ->limit( $this->limit + 1 )
431  ->caller( __METHOD__ );
432 
433  $res = $queryBuilder->fetchResultSet();
434 
435  $this->getHookRunner()->onCategoryViewer__doCategoryQuery( $type, $res );
436  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
437 
438  $count = 0;
439  foreach ( $res as $row ) {
440  $title = Title::newFromRow( $row );
441  $linkCache->addGoodLinkObjFromRow( $title, $row );
442 
443  if ( $row->cl_collation === '' ) {
444  // Hack to make sure that while updating from 1.16 schema
445  // and db is inconsistent, that the sky doesn't fall.
446  // See r83544. Could perhaps be removed in a couple decades...
447  $humanSortkey = $row->cl_sortkey;
448  } else {
449  $humanSortkey = $title->getCategorySortkey( $row->cl_sortkey_prefix );
450  }
451 
452  if ( ++$count > $this->limit ) {
453  # We've reached the one extra which shows that there
454  # are additional pages to be had. Stop here...
455  $this->nextPage[$type] = $humanSortkey;
456  break;
457  }
458  if ( $count == $this->limit ) {
459  $this->prevPage[$type] = $humanSortkey;
460  }
461 
462  if ( $title->getNamespace() === NS_CATEGORY ) {
463  $cat = Category::newFromRow( $row, $title );
464  $this->addSubcategoryObject( $cat, $humanSortkey, $row->page_len );
465  } elseif ( $title->getNamespace() === NS_FILE ) {
466  $this->addImage( $title, $humanSortkey, $row->page_len, $row->page_is_redirect );
467  } else {
468  $this->addPage( $title, $humanSortkey, $row->page_len, $row->page_is_redirect );
469  }
470  }
471  }
472  }
473 
477  protected function getCategoryTop() {
478  $r = $this->getCategoryBottom();
479  return $r === ''
480  ? $r
481  : "<br style=\"clear:both;\"/>\n" . $r;
482  }
483 
487  protected function getSubcategorySection() {
488  # Don't show subcategories section if there are none.
489  $r = '';
490  $rescnt = count( $this->children );
491  $dbcnt = $this->cat->getSubcatCount();
492  // This function should be called even if the result isn't used, it has side-effects
493  $countmsg = $this->getCountMessage( $rescnt, $dbcnt, 'subcat' );
494 
495  if ( $rescnt > 0 ) {
496  # Showing subcategories
497  $r .= Html::openElement( 'div', [ 'id' => 'mw-subcategories' ] ) . "\n";
498  $r .= Html::rawElement( 'h2', [], $this->msg( 'subcategories' )->parse() ) . "\n";
499  $r .= $countmsg;
500  $r .= $this->getSectionPagingLinks( 'subcat' );
501  $r .= $this->formatList( $this->children, $this->children_start_char );
502  $r .= $this->getSectionPagingLinks( 'subcat' );
503  $r .= "\n" . Html::closeElement( 'div' );
504  }
505  return $r;
506  }
507 
511  protected function getPagesSection() {
512  $name = $this->getOutput()->getUnprefixedDisplayTitle();
513  # Don't show articles section if there are none.
514  $r = '';
515 
516  # @todo FIXME: Here and in the other two sections: we don't need to bother
517  # with this rigmarole if the entire category contents fit on one page
518  # and have already been retrieved. We can just use $rescnt in that
519  # case and save a query and some logic.
520  $dbcnt = $this->cat->getPageCount( Category::COUNT_CONTENT_PAGES );
521  $rescnt = count( $this->articles );
522  // This function should be called even if the result isn't used, it has side-effects
523  $countmsg = $this->getCountMessage( $rescnt, $dbcnt, 'article' );
524 
525  if ( $rescnt > 0 ) {
526  $r .= Html::openElement( 'div', [ 'id' => 'mw-pages' ] ) . "\n";
527  $r .= Html::rawElement(
528  'h2',
529  [],
530  $this->msg( 'category_header' )->rawParams( $name )->parse()
531  ) . "\n";
532  $r .= $countmsg;
533  $r .= $this->getSectionPagingLinks( 'page' );
534  $r .= $this->formatList( $this->articles, $this->articles_start_char );
535  $r .= $this->getSectionPagingLinks( 'page' );
536  $r .= "\n" . Html::closeElement( 'div' );
537  }
538  return $r;
539  }
540 
544  protected function getImageSection() {
545  $name = $this->getOutput()->getUnprefixedDisplayTitle();
546  $r = '';
547  $rescnt = $this->showGallery ? $this->gallery->count() : count( $this->imgsNoGallery );
548  $dbcnt = $this->cat->getFileCount();
549  // This function should be called even if the result isn't used, it has side-effects
550  $countmsg = $this->getCountMessage( $rescnt, $dbcnt, 'file' );
551 
552  if ( $rescnt > 0 ) {
553  $r .= Html::openElement( 'div', [ 'id' => 'mw-category-media' ] ) . "\n";
554  $r .= Html::rawElement(
555  'h2',
556  [],
557  $this->msg( 'category-media-header' )->rawParams( $name )->parse()
558  ) . "\n";
559  $r .= $countmsg;
560  $r .= $this->getSectionPagingLinks( 'file' );
561  if ( $this->showGallery ) {
562  $r .= $this->gallery->toHTML();
563  } else {
564  $r .= $this->formatList( $this->imgsNoGallery, $this->imgsNoGallery_start_char );
565  }
566  $r .= $this->getSectionPagingLinks( 'file' );
567  $r .= "\n" . Html::closeElement( 'div' );
568  }
569  return $r;
570  }
571 
579  private function getSectionPagingLinks( $type ) {
580  if ( isset( $this->until[$type] ) ) {
581  // The new value for the until parameter should be pointing to the first
582  // result displayed on the page which is the second last result retrieved
583  // from the database.The next link should have a from parameter pointing
584  // to the until parameter of the current page.
585  if ( $this->nextPage[$type] !== null ) {
586  return $this->pagingLinks(
587  $this->prevPage[$type] ?? '',
588  $this->until[$type],
589  $type
590  );
591  }
592 
593  // If the nextPage variable is null, it means that we have reached the first page
594  // and therefore the previous link should be disabled.
595  return $this->pagingLinks(
596  '',
597  $this->until[$type],
598  $type
599  );
600  } elseif ( $this->nextPage[$type] !== null || isset( $this->from[$type] ) ) {
601  return $this->pagingLinks(
602  $this->from[$type] ?? '',
603  $this->nextPage[$type],
604  $type
605  );
606  }
607 
608  return '';
609  }
610 
614  protected function getCategoryBottom() {
615  return '';
616  }
617 
628  private function formatList( $articles, $articles_start_char, $cutoff = 6 ) {
629  $list = '';
630  if ( count( $articles ) > $cutoff ) {
631  $list = self::columnList( $articles, $articles_start_char );
632  } elseif ( count( $articles ) > 0 ) {
633  // for short lists of articles in categories.
634  $list = self::shortList( $articles, $articles_start_char );
635  }
636 
637  $pageLang = MediaWikiServices::getInstance()->getTitleFactory()
638  ->newFromPageIdentity( $this->page )
639  ->getPageLanguage();
640  $attribs = [ 'lang' => $pageLang->getHtmlCode(), 'dir' => $pageLang->getDir(),
641  'class' => 'mw-content-' . $pageLang->getDir() ];
642  $list = Html::rawElement( 'div', $attribs, $list );
643 
644  return $list;
645  }
646 
657  public static function columnList(
658  $articles,
659  $articles_start_char,
660  $cssClasses = 'mw-category mw-category-columns'
661  ) {
662  $columns = array_combine( $articles, $articles_start_char );
663 
664  $ret = Html::openElement( 'div', [ 'class' => $cssClasses ] );
665 
666  $colContents = [];
667 
668  # Kind of like array_flip() here, but we keep duplicates in an
669  # array instead of dropping them.
670  foreach ( $columns as $article => $char ) {
671  $colContents[$char][] = $article;
672  }
673 
674  foreach ( $colContents as $char => $articles ) {
675  # Change space to non-breaking space to keep headers aligned
676  $h3char = $char === ' ' ? "\u{00A0}" : htmlspecialchars( $char );
677 
678  $ret .= Html::openElement( 'div', [ 'class' => 'mw-category-group' ] );
679  $ret .= Html::rawElement( 'h3', [], $h3char ) . "\n";
680  $ret .= Html::openElement( 'ul' );
681  $ret .= implode(
682  "\n",
683  array_map(
684  static function ( $article ) {
685  return Html::rawElement( 'li', [], $article );
686  },
687  $articles
688  )
689  );
690  $ret .= Html::closeElement( 'ul' ) . Html::closeElement( 'div' );
691 
692  }
693 
694  $ret .= Html::closeElement( 'div' );
695  return $ret;
696  }
697 
706  public static function shortList( $articles, $articles_start_char ) {
707  return self::columnList( $articles, $articles_start_char, 'mw-category' );
708  }
709 
719  private function pagingLinks( $first, $last, $type = '' ) {
720  $prevLink = $this->msg( 'prev-page' )->escaped();
721 
722  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
723  if ( $first != '' ) {
724  $prevQuery = $this->query;
725  $prevQuery["{$type}until"] = $first;
726  unset( $prevQuery["{$type}from"] );
727  $prevLink = $linkRenderer->makeKnownLink(
728  $this->addFragmentToTitle( $this->page, $type ),
729  new HtmlArmor( $prevLink ),
730  [],
731  $prevQuery
732  );
733  }
734 
735  $nextLink = $this->msg( 'next-page' )->escaped();
736 
737  if ( $last != '' ) {
738  $lastQuery = $this->query;
739  $lastQuery["{$type}from"] = $last;
740  unset( $lastQuery["{$type}until"] );
741  $nextLink = $linkRenderer->makeKnownLink(
742  $this->addFragmentToTitle( $this->page, $type ),
743  new HtmlArmor( $nextLink ),
744  [],
745  $lastQuery
746  );
747  }
748 
749  return $this->msg( 'categoryviewer-pagedlinks' )->rawParams( $prevLink, $nextLink )->escaped();
750  }
751 
761  private function addFragmentToTitle( PageReference $page, string $section ): LinkTarget {
762  switch ( $section ) {
763  case 'page':
764  $fragment = 'mw-pages';
765  break;
766  case 'subcat':
767  $fragment = 'mw-subcategories';
768  break;
769  case 'file':
770  $fragment = 'mw-category-media';
771  break;
772  default:
773  throw new MWException( __METHOD__ .
774  " Invalid section $section." );
775  }
776 
777  return new TitleValue( $page->getNamespace(),
778  $page->getDBkey(), $fragment );
779  }
780 
791  private function getCountMessage( $rescnt, $dbcnt, $type ) {
792  // There are three cases:
793  // 1) The category table figure seems good. It might be wrong, but
794  // we can't do anything about it if we don't recalculate it on ev-
795  // ery category view.
796  // 2) The category table figure isn't good, like it's smaller than the
797  // number of actual results, *but* the number of results is less
798  // than $this->limit and there's no offset. In this case we still
799  // know the right figure.
800  // 3) We have no idea.
801 
802  // Check if there's a "from" or "until" for anything
803 
804  // This is a little ugly, but we seem to use different names
805  // for the paging types then for the messages.
806  if ( $type === 'article' ) {
807  $pagingType = 'page';
808  } else {
809  $pagingType = $type;
810  }
811 
812  $fromOrUntil = false;
813  if ( isset( $this->from[$pagingType] ) || isset( $this->until[$pagingType] ) ) {
814  $fromOrUntil = true;
815  }
816 
817  if ( $dbcnt == $rescnt ||
818  ( ( $rescnt == $this->limit || $fromOrUntil ) && $dbcnt > $rescnt )
819  ) {
820  // Case 1: seems good.
821  $totalcnt = $dbcnt;
822  } elseif ( $rescnt < $this->limit && !$fromOrUntil ) {
823  // Case 2: not good, but salvageable. Use the number of results.
824  $totalcnt = $rescnt;
825  } else {
826  // Case 3: hopeless. Don't give a total count at all.
827  // Messages: category-subcat-count-limited, category-article-count-limited,
828  // category-file-count-limited
829  return $this->msg( "category-$type-count-limited" )->numParams( $rescnt )->parseAsBlock();
830  }
831  // Messages: category-subcat-count, category-article-count, category-file-count
832  return $this->msg( "category-$type-count" )->numParams( $rescnt, $totalcnt )->parseAsBlock();
833  }
834 }
835 
839 class_alias( CategoryViewer::class, 'CategoryViewer' );
const NS_FILE
Definition: Defines.php:70
const NS_CATEGORY
Definition: Defines.php:78
deprecatePublicPropertyFallback(string $property, string $version, $getter, $setter=null, $class=null, $component=null)
Mark a removed public property as deprecated and provide fallback getter and setter callables.
trait DeprecationHelper
Use this trait in classes which have properties for which public access is deprecated or implementati...
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
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.
Definition: HtmlArmor.php:30
Image gallery.
static factory( $mode=false, IContextSource $context=null)
Get a new image gallery.
Class for exceptions thrown by ImageGalleryBase::factory().
Cache for article titles (prefixed DB keys) and ids linked from one source.
Definition: LinkCache.php:45
static getSelectFields()
Fields that LinkCache needs to select.
Definition: LinkCache.php:344
MediaWiki exception.
Definition: MWException.php:33
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.
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.
getSubcategorySortChar(PageIdentity $page, string $sortkey)
Get the character to be used for sorting subcategories.
addSubcategoryObject(Category $cat, $sortkey, $pageLength)
Add a subcategory to the internal lists, using a Category object.
__construct(PageIdentity $page, IContextSource $context, array $from=[], array $until=[], array $query=[])
addPage(PageReference $page, string $sortkey, int $pageLength, bool $isRedirect=false)
Add a miscellaneous page.
Category objects are immutable, strictly speaking.
Definition: Category.php:41
static newFromTitle(PageIdentity $page)
Factory function.
Definition: Category.php:176
This class is a collection of static functions that serve two purposes:
Definition: Html.php:57
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:235
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.
Represents the target of a wiki link.
Definition: TitleValue.php:44
Represents a title within MediaWiki.
Definition: Title.php:76
static newFromPageIdentity(PageIdentity $pageIdentity)
Return a Title for a given PageIdentity.
Definition: Title.php:329
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.
Definition: LinkTarget.php:30
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.
This program is free software; you can redistribute it and/or modify it under the terms of the GNU Ge...
const DB_REPLICA
Definition: defines.php:26