MediaWiki REL1_37
CategoryViewer.php
Go to the documentation of this file.
1<?php
23use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
28
30 use ProtectedHookAccessorTrait;
32
34 public $limit;
35
37 public $from;
38
40 public $until;
41
43 public $articles;
44
47
49 public $children;
50
53
56
59
62
64 public $nextPage;
65
67 protected $prevPage;
68
70 public $flip;
71
73 protected $page;
74
76 public $collation;
77
79 public $gallery;
80
82 private $cat;
83
85 private $query;
86
89
100 array $until = [], array $query = []
101 ) {
102 $this->page = $page;
103
105 'title',
106 '1.37',
107 function (): Title {
108 return Title::castFromPageIdentity( $this->page );
109 },
110 function ( PageIdentity $page ) {
111 $this->page = $page;
112 }
113 );
114
115 $this->setContext( $context );
116 $this->getOutput()->addModuleStyles( [
117 'mediawiki.action.view.categoryPage.styles'
118 ] );
119 $this->from = $from;
120 $this->until = $until;
121 $this->limit = $context->getConfig()->get( 'CategoryPagingLimit' );
122 $this->cat = Category::newFromTitle( $page );
123 $this->query = $query;
124 $this->collation = MediaWikiServices::getInstance()->getCollationFactory()->getCategoryCollation();
125 $this->languageConverter = MediaWikiServices::getInstance()
126 ->getLanguageConverterFactory()->getLanguageConverter();
127 unset( $this->query['title'] );
128 }
129
135 public function getHTML() {
136 $this->showGallery = $this->getConfig()->get( 'CategoryMagicGallery' )
137 && !$this->getOutput()->mNoGallery;
138
139 $this->clearCategoryState();
140 $this->doCategoryQuery();
141 $this->finaliseCategoryState();
142
143 $r = $this->getSubcategorySection() .
144 $this->getPagesSection() .
145 $this->getImageSection();
146
147 if ( $r == '' ) {
148 // If there is no category content to display, only
149 // show the top part of the navigation links.
150 // @todo FIXME: Cannot be completely suppressed because it
151 // is unknown if 'until' or 'from' makes this
152 // give 0 results.
153 $r = $this->getCategoryTop();
154 } else {
155 $r = $this->getCategoryTop() .
156 $r .
157 $this->getCategoryBottom();
158 }
159
160 // Give a proper message if category is empty
161 if ( $r == '' ) {
162 $r = $this->msg( 'category-empty' )->parseAsBlock();
163 }
164
165 $lang = $this->getLanguage();
166 $attribs = [
167 'class' => 'mw-category-generated',
168 'lang' => $lang->getHtmlCode(),
169 'dir' => $lang->getDir()
170 ];
171 # put a div around the headings which are in the user language
172 $r = Html::rawElement( 'div', $attribs, $r );
173
174 return $r;
175 }
176
177 protected function clearCategoryState() {
178 $this->articles = [];
179 $this->articles_start_char = [];
180 $this->children = [];
181 $this->children_start_char = [];
182 if ( $this->showGallery ) {
183 // Note that null for mode is taken to mean use default.
184 $mode = $this->getRequest()->getVal( 'gallerymode', null );
185 try {
186 $this->gallery = ImageGalleryBase::factory( $mode, $this->getContext() );
187 } catch ( Exception $e ) {
188 // User specified something invalid, fallback to default.
189 $this->gallery = ImageGalleryBase::factory( false, $this->getContext() );
190 }
191
192 $this->gallery->setHideBadImages();
193 } else {
194 $this->imgsNoGallery = [];
195 $this->imgsNoGallery_start_char = [];
196 }
197 }
198
205 public function addSubcategoryObject( Category $cat, $sortkey, $pageLength ) {
206 // Subcategory; strip the 'Category' namespace from the link text.
207 $pageRecord = MediaWikiServices::getInstance()->getPageStore()
208 ->getPageByReference( $cat->getPage() );
209
210 $this->children[] = $this->generateLink(
211 'subcat',
212 $pageRecord,
213 $pageRecord->isRedirect(),
214 htmlspecialchars( str_replace( '_', ' ', $pageRecord->getDBkey() ) )
215 );
216
217 $this->children_start_char[] =
218 $this->getSubcategorySortChar( $cat->getPage(), $sortkey );
219 }
220
232 private function generateLink(
233 string $type, PageReference $page, bool $isRedirect, ?string $html = null
234 ): string {
235 $link = null;
236 $legacyTitle = MediaWikiServices::getInstance()->getTitleFactory()
237 ->castFromPageReference( $page );
238 $this->getHookRunner()->onCategoryViewer__generateLink( $type, $legacyTitle, $html, $link );
239 if ( $link === null ) {
240 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
241 if ( $html !== null ) {
242 $html = new HtmlArmor( $html );
243 }
244 $link = $linkRenderer->makeLink( $page, $html );
245 }
246 if ( $isRedirect ) {
247 $link = Html::rawElement(
248 'span',
249 [ 'class' => 'redirect-in-category' ],
250 $link
251 );
252 }
253
254 return $link;
255 }
256
268 public function getSubcategorySortChar( PageIdentity $page, string $sortkey ): string {
269 $titleText = MediaWikiServices::getInstance()->getTitleFormatter()
270 ->getPrefixedText( $page );
271 if ( $titleText === $sortkey ) {
272 $word = $page->getDBkey();
273 } else {
274 $word = $sortkey;
275 }
276
277 $firstChar = $this->collation->getFirstLetter( $word );
278
279 return $this->languageConverter->convert( $firstChar );
280 }
281
289 public function addImage(
290 PageReference $page, string $sortkey, int $pageLength, bool $isRedirect = false
291 ): void {
292 $title = MediaWikiServices::getInstance()->getTitleFactory()
293 ->castFromPageReference( $page );
294 if ( $this->showGallery ) {
295 $flip = $this->flip['file'];
296 if ( $flip ) {
297 $this->gallery->insert( $title );
298 } else {
299 $this->gallery->add( $title );
300 }
301 } else {
302 $this->imgsNoGallery[] = $this->generateLink( 'image', $page, $isRedirect );
303
304 $this->imgsNoGallery_start_char[] =
305 $this->languageConverter->convert( $this->collation->getFirstLetter( $sortkey ) );
306 }
307 }
308
316 public function addPage(
317 PageReference $page, string $sortkey, int $pageLength, bool $isRedirect = false
318 ): void {
319 $this->articles[] = $this->generateLink( 'page', $page, $isRedirect );
320
321 $this->articles_start_char[] =
322 $this->languageConverter->convert( $this->collation->getFirstLetter( $sortkey ) );
323 }
324
325 protected function finaliseCategoryState() {
326 if ( $this->flip['subcat'] ) {
327 $this->children = array_reverse( $this->children );
328 $this->children_start_char = array_reverse( $this->children_start_char );
329 }
330 if ( $this->flip['page'] ) {
331 $this->articles = array_reverse( $this->articles );
332 $this->articles_start_char = array_reverse( $this->articles_start_char );
333 }
334 if ( !$this->showGallery && $this->flip['file'] ) {
335 $this->imgsNoGallery = array_reverse( $this->imgsNoGallery );
336 $this->imgsNoGallery_start_char = array_reverse( $this->imgsNoGallery_start_char );
337 }
338 }
339
340 protected function doCategoryQuery() {
341 $dbr = wfGetDB( DB_REPLICA, 'category' );
342
343 $this->nextPage = [
344 'page' => null,
345 'subcat' => null,
346 'file' => null,
347 ];
348 $this->prevPage = [
349 'page' => null,
350 'subcat' => null,
351 'file' => null,
352 ];
353
354 $this->flip = [ 'page' => false, 'subcat' => false, 'file' => false ];
355
356 foreach ( [ 'page', 'subcat', 'file' ] as $type ) {
357 # Get the sortkeys for start/end, if applicable. Note that if
358 # the collation in the database differs from the one
359 # set in $wgCategoryCollation, pagination might go totally haywire.
360 $extraConds = [ 'cl_type' => $type ];
361 if ( isset( $this->from[$type] ) ) {
362 $extraConds[] = 'cl_sortkey >= '
363 . $dbr->addQuotes( $this->collation->getSortKey( $this->from[$type] ) );
364 } elseif ( isset( $this->until[$type] ) ) {
365 $extraConds[] = 'cl_sortkey < '
366 . $dbr->addQuotes( $this->collation->getSortKey( $this->until[$type] ) );
367 $this->flip[$type] = true;
368 }
369
370 $res = $dbr->select(
371 [ 'page', 'categorylinks', 'category' ],
372 array_merge(
373 LinkCache::getSelectFields(),
374 [
375 'page_namespace',
376 'page_title',
377 'cl_sortkey',
378 'cat_id',
379 'cat_title',
380 'cat_subcats',
381 'cat_pages',
382 'cat_files',
383 'cl_sortkey_prefix',
384 'cl_collation'
385 ]
386 ),
387 array_merge( [ 'cl_to' => $this->page->getDBkey() ], $extraConds ),
388 __METHOD__,
389 [
390 'USE INDEX' => [ 'categorylinks' => 'cl_sortkey' ],
391 'LIMIT' => $this->limit + 1,
392 'ORDER BY' => $this->flip[$type] ? 'cl_sortkey DESC' : 'cl_sortkey',
393 ],
394 [
395 'categorylinks' => [ 'JOIN', 'cl_from = page_id' ],
396 'category' => [ 'LEFT JOIN', [
397 'cat_title = page_title',
398 'page_namespace' => NS_CATEGORY
399 ] ]
400 ]
401 );
402
403 $this->getHookRunner()->onCategoryViewer__doCategoryQuery( $type, $res );
404 $linkCache = MediaWikiServices::getInstance()->getLinkCache();
405
406 $count = 0;
407 foreach ( $res as $row ) {
408 $title = Title::newFromRow( $row );
409 $linkCache->addGoodLinkObjFromRow( $title, $row );
410
411 if ( $row->cl_collation === '' ) {
412 // Hack to make sure that while updating from 1.16 schema
413 // and db is inconsistent, that the sky doesn't fall.
414 // See r83544. Could perhaps be removed in a couple decades...
415 $humanSortkey = $row->cl_sortkey;
416 } else {
417 $humanSortkey = $title->getCategorySortkey( $row->cl_sortkey_prefix );
418 }
419
420 if ( ++$count > $this->limit ) {
421 # We've reached the one extra which shows that there
422 # are additional pages to be had. Stop here...
423 $this->nextPage[$type] = $humanSortkey;
424 break;
425 }
426 if ( $count == $this->limit ) {
427 $this->prevPage[$type] = $humanSortkey;
428 }
429
430 if ( $title->getNamespace() === NS_CATEGORY ) {
431 $cat = Category::newFromRow( $row, $title );
432 $this->addSubcategoryObject( $cat, $humanSortkey, $row->page_len );
433 } elseif ( $title->getNamespace() === NS_FILE ) {
434 $this->addImage( $title, $humanSortkey, $row->page_len, $row->page_is_redirect );
435 } else {
436 $this->addPage( $title, $humanSortkey, $row->page_len, $row->page_is_redirect );
437 }
438 }
439 }
440 }
441
445 protected function getCategoryTop() {
446 $r = $this->getCategoryBottom();
447 return $r === ''
448 ? $r
449 : "<br style=\"clear:both;\"/>\n" . $r;
450 }
451
455 protected function getSubcategorySection() {
456 # Don't show subcategories section if there are none.
457 $r = '';
458 $rescnt = count( $this->children );
459 $dbcnt = $this->cat->getSubcatCount();
460 // This function should be called even if the result isn't used, it has side-effects
461 $countmsg = $this->getCountMessage( $rescnt, $dbcnt, 'subcat' );
462
463 if ( $rescnt > 0 ) {
464 # Showing subcategories
465 $r .= Html::openElement( 'div', [ 'id' => 'mw-subcategories' ] ) . "\n";
466 $r .= Html::rawElement( 'h2', [], $this->msg( 'subcategories' )->parse() ) . "\n";
467 $r .= $countmsg;
468 $r .= $this->getSectionPagingLinks( 'subcat' );
469 $r .= $this->formatList( $this->children, $this->children_start_char );
470 $r .= $this->getSectionPagingLinks( 'subcat' );
471 $r .= "\n" . Html::closeElement( 'div' );
472 }
473 return $r;
474 }
475
479 protected function getPagesSection() {
480 $name = $this->getOutput()->getUnprefixedDisplayTitle();
481 # Don't show articles section if there are none.
482 $r = '';
483
484 # @todo FIXME: Here and in the other two sections: we don't need to bother
485 # with this rigmarole if the entire category contents fit on one page
486 # and have already been retrieved. We can just use $rescnt in that
487 # case and save a query and some logic.
488 $dbcnt = $this->cat->getPageCount() - $this->cat->getSubcatCount()
489 - $this->cat->getFileCount();
490 $rescnt = count( $this->articles );
491 // This function should be called even if the result isn't used, it has side-effects
492 $countmsg = $this->getCountMessage( $rescnt, $dbcnt, 'article' );
493
494 if ( $rescnt > 0 ) {
495 $r .= Html::openElement( 'div', [ 'id' => 'mw-pages' ] ) . "\n";
496 $r .= Html::rawElement(
497 'h2',
498 [],
499 $this->msg( 'category_header' )->rawParams( $name )->parse()
500 ) . "\n";
501 $r .= $countmsg;
502 $r .= $this->getSectionPagingLinks( 'page' );
503 $r .= $this->formatList( $this->articles, $this->articles_start_char );
504 $r .= $this->getSectionPagingLinks( 'page' );
505 $r .= "\n" . Html::closeElement( 'div' );
506 }
507 return $r;
508 }
509
513 protected function getImageSection() {
514 $name = $this->getOutput()->getUnprefixedDisplayTitle();
515 $r = '';
516 $rescnt = $this->showGallery ? $this->gallery->count() : count( $this->imgsNoGallery );
517 $dbcnt = $this->cat->getFileCount();
518 // This function should be called even if the result isn't used, it has side-effects
519 $countmsg = $this->getCountMessage( $rescnt, $dbcnt, 'file' );
520
521 if ( $rescnt > 0 ) {
522 $r .= Html::openElement( 'div', [ 'id' => 'mw-category-media' ] ) . "\n";
523 $r .= Html::rawElement(
524 'h2',
525 [],
526 $this->msg( 'category-media-header' )->rawParams( $name )->parse()
527 ) . "\n";
528 $r .= $countmsg;
529 $r .= $this->getSectionPagingLinks( 'file' );
530 if ( $this->showGallery ) {
531 $r .= $this->gallery->toHTML();
532 } else {
533 $r .= $this->formatList( $this->imgsNoGallery, $this->imgsNoGallery_start_char );
534 }
535 $r .= $this->getSectionPagingLinks( 'file' );
536 $r .= "\n" . Html::closeElement( 'div' );
537 }
538 return $r;
539 }
540
548 private function getSectionPagingLinks( $type ) {
549 if ( isset( $this->until[$type] ) ) {
550 // The new value for the until parameter should be pointing to the first
551 // result displayed on the page which is the second last result retrieved
552 // from the database.The next link should have a from parameter pointing
553 // to the until parameter of the current page.
554 if ( $this->nextPage[$type] !== null ) {
555 return $this->pagingLinks( $this->prevPage[$type], $this->until[$type], $type );
556 } else {
557 // If the nextPage variable is null, it means that we have reached the first page
558 // and therefore the previous link should be disabled.
559 return $this->pagingLinks( null, $this->until[$type], $type );
560 }
561 } elseif ( $this->nextPage[$type] !== null || isset( $this->from[$type] ) ) {
562 return $this->pagingLinks( $this->from[$type], $this->nextPage[$type], $type );
563 } else {
564 return '';
565 }
566 }
567
571 protected function getCategoryBottom() {
572 return '';
573 }
574
585 private function formatList( $articles, $articles_start_char, $cutoff = 6 ) {
586 $list = '';
587 if ( count( $articles ) > $cutoff ) {
588 $list = self::columnList( $articles, $articles_start_char );
589 } elseif ( count( $articles ) > 0 ) {
590 // for short lists of articles in categories.
591 $list = self::shortList( $articles, $articles_start_char );
592 }
593
594 $pageLang = MediaWikiServices::getInstance()->getTitleFactory()
595 ->castFromPageIdentity( $this->page )
596 ->getPageLanguage();
597 $attribs = [ 'lang' => $pageLang->getHtmlCode(), 'dir' => $pageLang->getDir(),
598 'class' => 'mw-content-' . $pageLang->getDir() ];
599 $list = Html::rawElement( 'div', $attribs, $list );
600
601 return $list;
602 }
603
618 public static function columnList( $articles, $articles_start_char ) {
619 $columns = array_combine( $articles, $articles_start_char );
620
621 $ret = Html::openElement( 'div', [ 'class' => 'mw-category' ] );
622
623 $colContents = [];
624
625 # Kind of like array_flip() here, but we keep duplicates in an
626 # array instead of dropping them.
627 foreach ( $columns as $article => $char ) {
628 if ( !isset( $colContents[$char] ) ) {
629 $colContents[$char] = [];
630 }
631 $colContents[$char][] = $article;
632 }
633
634 foreach ( $colContents as $char => $articles ) {
635 # Change space to non-breaking space to keep headers aligned
636 $h3char = $char === ' ' ? "\u{00A0}" : htmlspecialchars( $char );
637
638 $ret .= Html::openELement( 'div', [ 'class' => 'mw-category-group' ] );
639 $ret .= Html::rawElement( 'h3', [], $h3char ) . "\n";
640 $ret .= Html::openElement( 'ul' );
641 $ret .= implode(
642 "\n",
643 array_map(
644 static function ( $article ) {
645 return Html::rawElement( 'li', [], $article );
646 },
647 $articles
648 )
649 );
650 $ret .= Html::closeElement( 'ul' ) . Html::closeElement( 'div' );
651
652 }
653
654 $ret .= Html::closeElement( 'div' );
655 return $ret;
656 }
657
666 public static function shortList( $articles, $articles_start_char ) {
667 $r = '<h3>' . htmlspecialchars( $articles_start_char[0] ) . "</h3>\n";
668 $r .= '<ul><li>' . $articles[0] . '</li>';
669 $articleCount = count( $articles );
670 for ( $index = 1; $index < $articleCount; $index++ ) {
671 if ( $articles_start_char[$index] != $articles_start_char[$index - 1] ) {
672 $r .= "</ul><h3>" . htmlspecialchars( $articles_start_char[$index] ) . "</h3>\n<ul>";
673 }
674
675 $r .= "<li>{$articles[$index]}</li>";
676 }
677 $r .= '</ul>';
678 return $r;
679 }
680
690 private function pagingLinks( $first, $last, $type = '' ) {
691 $prevLink = $this->msg( 'prev-page' )->escaped();
692
693 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
694 if ( $first != '' ) {
695 $prevQuery = $this->query;
696 $prevQuery["{$type}until"] = $first;
697 unset( $prevQuery["{$type}from"] );
698 $prevLink = $linkRenderer->makeKnownLink(
699 $this->addFragmentToTitle( $this->page, $type ),
700 new HtmlArmor( $prevLink ),
701 [],
702 $prevQuery
703 );
704 }
705
706 $nextLink = $this->msg( 'next-page' )->escaped();
707
708 if ( $last != '' ) {
709 $lastQuery = $this->query;
710 $lastQuery["{$type}from"] = $last;
711 unset( $lastQuery["{$type}until"] );
712 $nextLink = $linkRenderer->makeKnownLink(
713 $this->addFragmentToTitle( $this->page, $type ),
714 new HtmlArmor( $nextLink ),
715 [],
716 $lastQuery
717 );
718 }
719
720 return $this->msg( 'categoryviewer-pagedlinks' )->rawParams( $prevLink, $nextLink )->escaped();
721 }
722
732 private function addFragmentToTitle( PageReference $page, string $section ): LinkTarget {
733 switch ( $section ) {
734 case 'page':
735 $fragment = 'mw-pages';
736 break;
737 case 'subcat':
738 $fragment = 'mw-subcategories';
739 break;
740 case 'file':
741 $fragment = 'mw-category-media';
742 break;
743 default:
744 throw new MWException( __METHOD__ .
745 " Invalid section $section." );
746 }
747
748 return new TitleValue( $page->getNamespace(),
749 $page->getDBkey(), $fragment );
750 }
751
762 private function getCountMessage( $rescnt, $dbcnt, $type ) {
763 // There are three cases:
764 // 1) The category table figure seems sane. It might be wrong, but
765 // we can't do anything about it if we don't recalculate it on ev-
766 // ery category view.
767 // 2) The category table figure isn't sane, like it's smaller than the
768 // number of actual results, *but* the number of results is less
769 // than $this->limit and there's no offset. In this case we still
770 // know the right figure.
771 // 3) We have no idea.
772
773 // Check if there's a "from" or "until" for anything
774
775 // This is a little ugly, but we seem to use different names
776 // for the paging types then for the messages.
777 if ( $type === 'article' ) {
778 $pagingType = 'page';
779 } else {
780 $pagingType = $type;
781 }
782
783 $fromOrUntil = false;
784 if ( isset( $this->from[$pagingType] ) || isset( $this->until[$pagingType] ) ) {
785 $fromOrUntil = true;
786 }
787
788 if ( $dbcnt == $rescnt ||
789 ( ( $rescnt == $this->limit || $fromOrUntil ) && $dbcnt > $rescnt )
790 ) {
791 // Case 1: seems sane.
792 $totalcnt = $dbcnt;
793 } elseif ( $rescnt < $this->limit && !$fromOrUntil ) {
794 // Case 2: not sane, but salvageable. Use the number of results.
795 $totalcnt = $rescnt;
796 } else {
797 // Case 3: hopeless. Don't give a total count at all.
798 // Messages: category-subcat-count-limited, category-article-count-limited,
799 // category-file-count-limited
800 return $this->msg( "category-$type-count-limited" )->numParams( $rescnt )->parseAsBlock();
801 }
802 // Messages: category-subcat-count, category-article-count, category-file-count
803 return $this->msg( "category-$type-count" )->numParams( $rescnt, $totalcnt )->parseAsBlock();
804 }
805}
const NS_FILE
Definition Defines.php:70
const NS_CATEGORY
Definition Defines.php:78
deprecatePublicPropertyFallback(string $property, string $version, callable $getter, ?callable $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...
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.
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
static columnList( $articles, $articles_start_char)
Format a list of articles chunked by letter in a three-column list, ordered vertically.
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
Category objects are immutable, strictly speaking.
Definition Category.php:33
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
MediaWiki exception.
MediaWikiServices is the service locator for the application scope of MediaWiki.
Represents a page (or page fragment) title within MediaWiki.
Represents a title within MediaWiki.
Definition Title.php:48
Interface for objects which can provide a MediaWiki context on request.
getConfig()
Get the site configuration.
The shared interface for all language converters.
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.
const DB_REPLICA
Definition defines.php:25
if(!isset( $args[0])) $lang