MediaWiki  master
CategoryViewer.php
Go to the documentation of this file.
1 <?php
23 use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
29 
31  use ProtectedHookAccessorTrait;
33 
35  public $limit;
36 
38  public $from;
39 
41  public $until;
42 
44  public $articles;
45 
48 
50  public $children;
51 
54 
56  public $showGallery;
57 
60 
63 
65  public $nextPage;
66 
68  protected $prevPage;
69 
71  public $flip;
72 
74  protected $page;
75 
77  public $collation;
78 
80  public $gallery;
81 
83  private $cat;
84 
86  private $query;
87 
90 
101  array $until = [], array $query = []
102  ) {
103  $this->page = $page;
104 
106  'title',
107  '1.37',
108  function (): Title {
109  // @phan-suppress-next-line PhanTypeMismatchReturnNullable castFrom does not return null here
110  return Title::castFromPageIdentity( $this->page );
111  },
112  function ( PageIdentity $page ) {
113  $this->page = $page;
114  }
115  );
116 
117  $this->setContext( $context );
118  $this->getOutput()->addModuleStyles( [
119  'mediawiki.action.styles',
120  ] );
121  $this->from = $from;
122  $this->until = $until;
123  $this->limit = $context->getConfig()->get( MainConfigNames::CategoryPagingLimit );
124  $this->cat = Category::newFromTitle( $page );
125  $this->query = $query;
126  $this->collation = MediaWikiServices::getInstance()->getCollationFactory()->getCategoryCollation();
127  $this->languageConverter = MediaWikiServices::getInstance()
128  ->getLanguageConverterFactory()->getLanguageConverter();
129  unset( $this->query['title'] );
130  }
131 
137  public function getHTML() {
138  $this->showGallery = $this->getConfig()->get( MainConfigNames::CategoryMagicGallery )
139  && !$this->getOutput()->mNoGallery;
140 
141  $this->clearCategoryState();
142  $this->doCategoryQuery();
143  $this->finaliseCategoryState();
144 
145  $r = $this->getSubcategorySection() .
146  $this->getPagesSection() .
147  $this->getImageSection();
148 
149  if ( $r == '' ) {
150  // If there is no category content to display, only
151  // show the top part of the navigation links.
152  // @todo FIXME: Cannot be completely suppressed because it
153  // is unknown if 'until' or 'from' makes this
154  // give 0 results.
155  $r = $this->getCategoryTop();
156  } else {
157  $r = $this->getCategoryTop() .
158  $r .
159  $this->getCategoryBottom();
160  }
161 
162  // Give a proper message if category is empty
163  if ( $r == '' ) {
164  $r = $this->msg( 'category-empty' )->parseAsBlock();
165  }
166 
167  $lang = $this->getLanguage();
168  $attribs = [
169  'class' => 'mw-category-generated',
170  'lang' => $lang->getHtmlCode(),
171  'dir' => $lang->getDir()
172  ];
173  # put a div around the headings which are in the user language
174  $r = Html::rawElement( 'div', $attribs, $r );
175 
176  return $r;
177  }
178 
179  protected function clearCategoryState() {
180  $this->articles = [];
181  $this->articles_start_char = [];
182  $this->children = [];
183  $this->children_start_char = [];
184  if ( $this->showGallery ) {
185  // Note that null for mode is taken to mean use default.
186  $mode = $this->getRequest()->getVal( 'gallerymode', null );
187  try {
188  $this->gallery = ImageGalleryBase::factory( $mode, $this->getContext() );
189  } catch ( ImageGalleryClassNotFoundException $e ) {
190  // User specified something invalid, fallback to default.
191  $this->gallery = ImageGalleryBase::factory( false, $this->getContext() );
192  }
193 
194  $this->gallery->setHideBadImages();
195  } else {
196  $this->imgsNoGallery = [];
197  $this->imgsNoGallery_start_char = [];
198  }
199  }
200 
207  public function addSubcategoryObject( Category $cat, $sortkey, $pageLength ) {
208  $page = $cat->getPage();
209  if ( !$page ) {
210  return;
211  }
212 
213  // Subcategory; strip the 'Category' namespace from the link text.
214  $pageRecord = MediaWikiServices::getInstance()->getPageStore()
215  ->getPageByReference( $page );
216  if ( !$pageRecord ) {
217  return;
218  }
219 
220  $this->children[] = $this->generateLink(
221  'subcat',
222  $pageRecord,
223  $pageRecord->isRedirect(),
224  htmlspecialchars( str_replace( '_', ' ', $pageRecord->getDBkey() ) )
225  );
226 
227  $this->children_start_char[] =
228  $this->getSubcategorySortChar( $page, $sortkey );
229  }
230 
242  private function generateLink(
243  string $type, PageReference $page, bool $isRedirect, ?string $html = null
244  ): string {
245  $link = null;
246  $legacyTitle = MediaWikiServices::getInstance()->getTitleFactory()
247  ->castFromPageReference( $page );
248  // @phan-suppress-next-line PhanTypeMismatchArgument castFrom does not return null here
249  $this->getHookRunner()->onCategoryViewer__generateLink( $type, $legacyTitle, $html, $link );
250  if ( $link === null ) {
251  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
252  if ( $html !== null ) {
253  $html = new HtmlArmor( $html );
254  }
255  $link = $linkRenderer->makeLink( $page, $html );
256  }
257  if ( $isRedirect ) {
258  $link = Html::rawElement(
259  'span',
260  [ 'class' => 'redirect-in-category' ],
261  $link
262  );
263  }
264 
265  return $link;
266  }
267 
279  public function getSubcategorySortChar( PageIdentity $page, string $sortkey ): string {
280  $titleText = MediaWikiServices::getInstance()->getTitleFormatter()
281  ->getPrefixedText( $page );
282  if ( $titleText === $sortkey ) {
283  $word = $page->getDBkey();
284  } else {
285  $word = $sortkey;
286  }
287 
288  $firstChar = $this->collation->getFirstLetter( $word );
289 
290  return $this->languageConverter->convert( $firstChar );
291  }
292 
300  public function addImage(
301  PageReference $page, string $sortkey, int $pageLength, bool $isRedirect = false
302  ): void {
303  $title = MediaWikiServices::getInstance()->getTitleFactory()
304  ->castFromPageReference( $page );
305  if ( $this->showGallery ) {
306  $flip = $this->flip['file'];
307  if ( $flip ) {
308  // @phan-suppress-next-line PhanTypeMismatchArgumentNullable castFrom does not return null here
309  $this->gallery->insert( $title );
310  } else {
311  // @phan-suppress-next-line PhanTypeMismatchArgumentNullable castFrom does not return null here
312  $this->gallery->add( $title );
313  }
314  } else {
315  $this->imgsNoGallery[] = $this->generateLink( 'image', $page, $isRedirect );
316 
317  $this->imgsNoGallery_start_char[] =
318  $this->languageConverter->convert( $this->collation->getFirstLetter( $sortkey ) );
319  }
320  }
321 
329  public function addPage(
330  PageReference $page, string $sortkey, int $pageLength, bool $isRedirect = false
331  ): void {
332  $this->articles[] = $this->generateLink( 'page', $page, $isRedirect );
333 
334  $this->articles_start_char[] =
335  $this->languageConverter->convert( $this->collation->getFirstLetter( $sortkey ) );
336  }
337 
338  protected function finaliseCategoryState() {
339  if ( $this->flip['subcat'] ) {
340  $this->children = array_reverse( $this->children );
341  $this->children_start_char = array_reverse( $this->children_start_char );
342  }
343  if ( $this->flip['page'] ) {
344  $this->articles = array_reverse( $this->articles );
345  $this->articles_start_char = array_reverse( $this->articles_start_char );
346  }
347  if ( !$this->showGallery && $this->flip['file'] ) {
348  $this->imgsNoGallery = array_reverse( $this->imgsNoGallery );
349  $this->imgsNoGallery_start_char = array_reverse( $this->imgsNoGallery_start_char );
350  }
351  }
352 
353  protected function doCategoryQuery() {
354  $dbr = wfGetDB( DB_REPLICA, 'category' );
355 
356  $this->nextPage = [
357  'page' => null,
358  'subcat' => null,
359  'file' => null,
360  ];
361  $this->prevPage = [
362  'page' => null,
363  'subcat' => null,
364  'file' => null,
365  ];
366 
367  $this->flip = [ 'page' => false, 'subcat' => false, 'file' => false ];
368 
369  foreach ( [ 'page', 'subcat', 'file' ] as $type ) {
370  # Get the sortkeys for start/end, if applicable. Note that if
371  # the collation in the database differs from the one
372  # set in $wgCategoryCollation, pagination might go totally haywire.
373  $extraConds = [ 'cl_type' => $type ];
374  if ( isset( $this->from[$type] ) ) {
375  $extraConds[] = 'cl_sortkey >= '
376  . $dbr->addQuotes( $this->collation->getSortKey( $this->from[$type] ) );
377  } elseif ( isset( $this->until[$type] ) ) {
378  $extraConds[] = 'cl_sortkey < '
379  . $dbr->addQuotes( $this->collation->getSortKey( $this->until[$type] ) );
380  $this->flip[$type] = true;
381  }
382 
383  $res = $dbr->select(
384  [ 'page', 'categorylinks', 'category' ],
385  array_merge(
387  [
388  'page_namespace',
389  'page_title',
390  'cl_sortkey',
391  'cat_id',
392  'cat_title',
393  'cat_subcats',
394  'cat_pages',
395  'cat_files',
396  'cl_sortkey_prefix',
397  'cl_collation'
398  ]
399  ),
400  array_merge( [ 'cl_to' => $this->page->getDBkey() ], $extraConds ),
401  __METHOD__,
402  [
403  'USE INDEX' => [ 'categorylinks' => 'cl_sortkey' ],
404  'LIMIT' => $this->limit + 1,
405  'ORDER BY' => $this->flip[$type] ? 'cl_sortkey DESC' : 'cl_sortkey',
406  ],
407  [
408  'categorylinks' => [ 'JOIN', 'cl_from = page_id' ],
409  'category' => [ 'LEFT JOIN', [
410  'cat_title = page_title',
411  'page_namespace' => NS_CATEGORY
412  ] ]
413  ]
414  );
415 
416  $this->getHookRunner()->onCategoryViewer__doCategoryQuery( $type, $res );
417  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
418 
419  $count = 0;
420  foreach ( $res as $row ) {
421  $title = Title::newFromRow( $row );
422  $linkCache->addGoodLinkObjFromRow( $title, $row );
423 
424  if ( $row->cl_collation === '' ) {
425  // Hack to make sure that while updating from 1.16 schema
426  // and db is inconsistent, that the sky doesn't fall.
427  // See r83544. Could perhaps be removed in a couple decades...
428  $humanSortkey = $row->cl_sortkey;
429  } else {
430  $humanSortkey = $title->getCategorySortkey( $row->cl_sortkey_prefix );
431  }
432 
433  if ( ++$count > $this->limit ) {
434  # We've reached the one extra which shows that there
435  # are additional pages to be had. Stop here...
436  $this->nextPage[$type] = $humanSortkey;
437  break;
438  }
439  if ( $count == $this->limit ) {
440  $this->prevPage[$type] = $humanSortkey;
441  }
442 
443  if ( $title->getNamespace() === NS_CATEGORY ) {
444  $cat = Category::newFromRow( $row, $title );
445  $this->addSubcategoryObject( $cat, $humanSortkey, $row->page_len );
446  } elseif ( $title->getNamespace() === NS_FILE ) {
447  $this->addImage( $title, $humanSortkey, $row->page_len, $row->page_is_redirect );
448  } else {
449  $this->addPage( $title, $humanSortkey, $row->page_len, $row->page_is_redirect );
450  }
451  }
452  }
453  }
454 
458  protected function getCategoryTop() {
459  $r = $this->getCategoryBottom();
460  return $r === ''
461  ? $r
462  : "<br style=\"clear:both;\"/>\n" . $r;
463  }
464 
468  protected function getSubcategorySection() {
469  # Don't show subcategories section if there are none.
470  $r = '';
471  $rescnt = count( $this->children );
472  $dbcnt = $this->cat->getSubcatCount();
473  // This function should be called even if the result isn't used, it has side-effects
474  $countmsg = $this->getCountMessage( $rescnt, $dbcnt, 'subcat' );
475 
476  if ( $rescnt > 0 ) {
477  # Showing subcategories
478  $r .= Html::openElement( 'div', [ 'id' => 'mw-subcategories' ] ) . "\n";
479  $r .= Html::rawElement( 'h2', [], $this->msg( 'subcategories' )->parse() ) . "\n";
480  $r .= $countmsg;
481  $r .= $this->getSectionPagingLinks( 'subcat' );
482  $r .= $this->formatList( $this->children, $this->children_start_char );
483  $r .= $this->getSectionPagingLinks( 'subcat' );
484  $r .= "\n" . Html::closeElement( 'div' );
485  }
486  return $r;
487  }
488 
492  protected function getPagesSection() {
493  $name = $this->getOutput()->getUnprefixedDisplayTitle();
494  # Don't show articles section if there are none.
495  $r = '';
496 
497  # @todo FIXME: Here and in the other two sections: we don't need to bother
498  # with this rigmarole if the entire category contents fit on one page
499  # and have already been retrieved. We can just use $rescnt in that
500  # case and save a query and some logic.
501  $dbcnt = $this->cat->getPageCount( Category::COUNT_CONTENT_PAGES );
502  $rescnt = count( $this->articles );
503  // This function should be called even if the result isn't used, it has side-effects
504  $countmsg = $this->getCountMessage( $rescnt, $dbcnt, 'article' );
505 
506  if ( $rescnt > 0 ) {
507  $r .= Html::openElement( 'div', [ 'id' => 'mw-pages' ] ) . "\n";
508  $r .= Html::rawElement(
509  'h2',
510  [],
511  $this->msg( 'category_header' )->rawParams( $name )->parse()
512  ) . "\n";
513  $r .= $countmsg;
514  $r .= $this->getSectionPagingLinks( 'page' );
515  $r .= $this->formatList( $this->articles, $this->articles_start_char );
516  $r .= $this->getSectionPagingLinks( 'page' );
517  $r .= "\n" . Html::closeElement( 'div' );
518  }
519  return $r;
520  }
521 
525  protected function getImageSection() {
526  $name = $this->getOutput()->getUnprefixedDisplayTitle();
527  $r = '';
528  $rescnt = $this->showGallery ? $this->gallery->count() : count( $this->imgsNoGallery );
529  $dbcnt = $this->cat->getFileCount();
530  // This function should be called even if the result isn't used, it has side-effects
531  $countmsg = $this->getCountMessage( $rescnt, $dbcnt, 'file' );
532 
533  if ( $rescnt > 0 ) {
534  $r .= Html::openElement( 'div', [ 'id' => 'mw-category-media' ] ) . "\n";
535  $r .= Html::rawElement(
536  'h2',
537  [],
538  $this->msg( 'category-media-header' )->rawParams( $name )->parse()
539  ) . "\n";
540  $r .= $countmsg;
541  $r .= $this->getSectionPagingLinks( 'file' );
542  if ( $this->showGallery ) {
543  $r .= $this->gallery->toHTML();
544  } else {
545  $r .= $this->formatList( $this->imgsNoGallery, $this->imgsNoGallery_start_char );
546  }
547  $r .= $this->getSectionPagingLinks( 'file' );
548  $r .= "\n" . Html::closeElement( 'div' );
549  }
550  return $r;
551  }
552 
560  private function getSectionPagingLinks( $type ) {
561  if ( isset( $this->until[$type] ) ) {
562  // The new value for the until parameter should be pointing to the first
563  // result displayed on the page which is the second last result retrieved
564  // from the database.The next link should have a from parameter pointing
565  // to the until parameter of the current page.
566  if ( $this->nextPage[$type] !== null ) {
567  return $this->pagingLinks( $this->prevPage[$type], $this->until[$type], $type );
568  } else {
569  // If the nextPage variable is null, it means that we have reached the first page
570  // and therefore the previous link should be disabled.
571  return $this->pagingLinks( '', $this->until[$type], $type );
572  }
573  } elseif ( $this->nextPage[$type] !== null || isset( $this->from[$type] ) ) {
574  return $this->pagingLinks( $this->from[$type], $this->nextPage[$type], $type );
575  } else {
576  return '';
577  }
578  }
579 
583  protected function getCategoryBottom() {
584  return '';
585  }
586 
597  private function formatList( $articles, $articles_start_char, $cutoff = 6 ) {
598  $list = '';
599  if ( count( $articles ) > $cutoff ) {
600  $list = self::columnList( $articles, $articles_start_char );
601  } elseif ( count( $articles ) > 0 ) {
602  // for short lists of articles in categories.
603  $list = self::shortList( $articles, $articles_start_char );
604  }
605 
606  $pageLang = MediaWikiServices::getInstance()->getTitleFactory()
607  ->castFromPageIdentity( $this->page )
608  ->getPageLanguage();
609  $attribs = [ 'lang' => $pageLang->getHtmlCode(), 'dir' => $pageLang->getDir(),
610  'class' => 'mw-content-' . $pageLang->getDir() ];
611  $list = Html::rawElement( 'div', $attribs, $list );
612 
613  return $list;
614  }
615 
626  public static function columnList(
627  $articles,
628  $articles_start_char,
629  $cssClasses = 'mw-category mw-category-columns'
630  ) {
631  $columns = array_combine( $articles, $articles_start_char );
632 
633  $ret = Html::openElement( 'div', [ 'class' => $cssClasses ] );
634 
635  $colContents = [];
636 
637  # Kind of like array_flip() here, but we keep duplicates in an
638  # array instead of dropping them.
639  foreach ( $columns as $article => $char ) {
640  if ( !isset( $colContents[$char] ) ) {
641  $colContents[$char] = [];
642  }
643  $colContents[$char][] = $article;
644  }
645 
646  foreach ( $colContents as $char => $articles ) {
647  # Change space to non-breaking space to keep headers aligned
648  $h3char = $char === ' ' ? "\u{00A0}" : htmlspecialchars( $char );
649 
650  $ret .= Html::openElement( 'div', [ 'class' => 'mw-category-group' ] );
651  $ret .= Html::rawElement( 'h3', [], $h3char ) . "\n";
652  $ret .= Html::openElement( 'ul' );
653  $ret .= implode(
654  "\n",
655  array_map(
656  static function ( $article ) {
657  return Html::rawElement( 'li', [], $article );
658  },
659  $articles
660  )
661  );
662  $ret .= Html::closeElement( 'ul' ) . Html::closeElement( 'div' );
663 
664  }
665 
666  $ret .= Html::closeElement( 'div' );
667  return $ret;
668  }
669 
678  public static function shortList( $articles, $articles_start_char ) {
679  return self::columnList( $articles, $articles_start_char, 'mw-category' );
680  }
681 
691  private function pagingLinks( $first, $last, $type = '' ) {
692  $prevLink = $this->msg( 'prev-page' )->escaped();
693 
694  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
695  if ( $first != '' ) {
696  $prevQuery = $this->query;
697  $prevQuery["{$type}until"] = $first;
698  unset( $prevQuery["{$type}from"] );
699  $prevLink = $linkRenderer->makeKnownLink(
700  $this->addFragmentToTitle( $this->page, $type ),
701  new HtmlArmor( $prevLink ),
702  [],
703  $prevQuery
704  );
705  }
706 
707  $nextLink = $this->msg( 'next-page' )->escaped();
708 
709  if ( $last != '' ) {
710  $lastQuery = $this->query;
711  $lastQuery["{$type}from"] = $last;
712  unset( $lastQuery["{$type}until"] );
713  $nextLink = $linkRenderer->makeKnownLink(
714  $this->addFragmentToTitle( $this->page, $type ),
715  new HtmlArmor( $nextLink ),
716  [],
717  $lastQuery
718  );
719  }
720 
721  return $this->msg( 'categoryviewer-pagedlinks' )->rawParams( $prevLink, $nextLink )->escaped();
722  }
723 
733  private function addFragmentToTitle( PageReference $page, string $section ): LinkTarget {
734  switch ( $section ) {
735  case 'page':
736  $fragment = 'mw-pages';
737  break;
738  case 'subcat':
739  $fragment = 'mw-subcategories';
740  break;
741  case 'file':
742  $fragment = 'mw-category-media';
743  break;
744  default:
745  throw new MWException( __METHOD__ .
746  " Invalid section $section." );
747  }
748 
749  return new TitleValue( $page->getNamespace(),
750  $page->getDBkey(), $fragment );
751  }
752 
763  private function getCountMessage( $rescnt, $dbcnt, $type ) {
764  // There are three cases:
765  // 1) The category table figure seems good. It might be wrong, but
766  // we can't do anything about it if we don't recalculate it on ev-
767  // ery category view.
768  // 2) The category table figure isn't good, like it's smaller than the
769  // number of actual results, *but* the number of results is less
770  // than $this->limit and there's no offset. In this case we still
771  // know the right figure.
772  // 3) We have no idea.
773 
774  // Check if there's a "from" or "until" for anything
775 
776  // This is a little ugly, but we seem to use different names
777  // for the paging types then for the messages.
778  if ( $type === 'article' ) {
779  $pagingType = 'page';
780  } else {
781  $pagingType = $type;
782  }
783 
784  $fromOrUntil = false;
785  if ( isset( $this->from[$pagingType] ) || isset( $this->until[$pagingType] ) ) {
786  $fromOrUntil = true;
787  }
788 
789  if ( $dbcnt == $rescnt ||
790  ( ( $rescnt == $this->limit || $fromOrUntil ) && $dbcnt > $rescnt )
791  ) {
792  // Case 1: seems good.
793  $totalcnt = $dbcnt;
794  } elseif ( $rescnt < $this->limit && !$fromOrUntil ) {
795  // Case 2: not good, but salvageable. Use the number of results.
796  $totalcnt = $rescnt;
797  } else {
798  // Case 3: hopeless. Don't give a total count at all.
799  // Messages: category-subcat-count-limited, category-article-count-limited,
800  // category-file-count-limited
801  return $this->msg( "category-$type-count-limited" )->numParams( $rescnt )->parseAsBlock();
802  }
803  // Messages: category-subcat-count, category-article-count, category-file-count
804  return $this->msg( "category-$type-count" )->numParams( $rescnt, $totalcnt )->parseAsBlock();
805  }
806 }
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.
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().
Collation $collation
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.
addFragmentToTitle(PageReference $page, string $section)
Takes a title, and adds the fragment identifier that corresponds to the correct segment of the catego...
__construct(PageIdentity $page, IContextSource $context, array $from=[], array $until=[], array $query=[])
generateLink(string $type, PageReference $page, bool $isRedirect, ?string $html=null)
getSubcategorySortChar(PageIdentity $page, string $sortkey)
Get the character to be used for sorting subcategories.
ImageGalleryBase $gallery
addImage(PageReference $page, string $sortkey, int $pageLength, bool $isRedirect=false)
Add a page in the image namespace.
addSubcategoryObject(Category $cat, $sortkey, $pageLength)
Add a subcategory to the internal lists, using a Category object.
string[] $articles
addPage(PageReference $page, string $sortkey, int $pageLength, bool $isRedirect=false)
Add a miscellaneous page.
formatList( $articles, $articles_start_char, $cutoff=6)
Format a list of articles chunked by letter, either as a bullet list or a columnar format,...
ILanguageConverter $languageConverter
getHTML()
Format the category data list.
array $imgsNoGallery_start_char
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.
PageIdentity $page
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.
Category objects are immutable, strictly speaking.
Definition: Category.php:33
static newFromTitle(PageIdentity $page)
Factory function.
Definition: Category.php:170
static newFromRow(stdClass $row, ?PageIdentity $page=null)
Factory function, for constructing a Category object from a result set.
Definition: Category.php:201
const COUNT_CONTENT_PAGES
Definition: Category.php:59
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()
IContextSource $context
getContext()
Get the base IContextSource object.
setContext(IContextSource $context)
Marks HTML that shouldn't be escaped.
Definition: HtmlArmor.php:30
static rawElement( $element, $attribs=[], $contents='')
Returns an HTML element in a string.
Definition: Html.php:214
static openElement( $element, $attribs=[])
Identical to rawElement(), but has no third parameter and omits the end tag (and the self-closing '/'...
Definition: Html.php:256
static closeElement( $element)
Returns "</$element>".
Definition: Html.php:320
static factory( $mode=false, IContextSource $context=null)
Get a new image gallery.
Class for exceptions thrown by ImageGalleryBase::factory().
static getSelectFields()
Fields that LinkCache needs to select.
Definition: LinkCache.php:357
MediaWiki exception.
Definition: MWException.php:29
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
Represents a page (or page fragment) title within MediaWiki.
Definition: TitleValue.php:40
Represents a title within MediaWiki.
Definition: Title.php:49
static castFromPageIdentity(?PageIdentity $pageIdentity)
Return a Title for a given PageIdentity.
Definition: Title.php:319
static newFromRow( $row)
Make a Title object from a DB row.
Definition: Title.php:573
Interface for objects which can provide a MediaWiki context on request.
getConfig()
Get the site configuration.
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.
getDBkey()
Get the page title in DB key form.
getNamespace()
Returns the page's namespace number.
const DB_REPLICA
Definition: defines.php:26
if(!isset( $args[0])) $lang