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