229 $services = MediaWikiServices::getInstance();
234 $id =
$title->getArticleID();
235 $config = $this->context->getConfig();
236 $linkRenderer = $services->getLinkRenderer();
240 $props = PageProps::getInstance()->getAllProperties(
$title );
241 $pageProperties = $props[$id] ?? [];
245 $pageInfo[
'header-basic'] = [];
248 $displayTitle = $pageProperties[
'displaytitle'] ??
$title->getPrefixedText();
250 $pageInfo[
'header-basic'][] = [
251 $this->
msg(
'pageinfo-display-title' ), $displayTitle
255 $redirectTarget = $this->
getWikiPage()->getRedirectTarget();
256 if ( $redirectTarget !==
null ) {
257 $pageInfo[
'header-basic'][] = [
258 $this->
msg(
'pageinfo-redirectsto' ),
259 $linkRenderer->makeLink( $redirectTarget ) .
260 $this->
msg(
'word-separator' )->escaped() .
261 $this->
msg(
'parentheses' )->rawParams( $linkRenderer->makeLink(
263 $this->msg(
'pageinfo-redirectsto-info' )->text(),
265 [
'action' =>
'info' ]
271 $sortKey = $pageProperties[
'defaultsort'] ??
$title->getCategorySortkey();
273 $sortKey = htmlspecialchars( $sortKey );
274 $pageInfo[
'header-basic'][] = [ $this->
msg(
'pageinfo-default-sort' ), $sortKey ];
277 $pageInfo[
'header-basic'][] = [
278 $this->
msg(
'pageinfo-length' ),
$lang->formatNum(
$title->getLength() )
282 $pageNamespace =
$title->getNsText();
283 if ( $pageNamespace ) {
284 $pageInfo[
'header-basic'][] = [ $this->
msg(
'pageinfo-namespace' ), $pageNamespace ];
288 $pageInfo[
'header-basic'][] = [ $this->
msg(
'pageinfo-article-id' ), $id ];
291 $pageLang =
$title->getPageLanguage()->getCode();
293 $pageLangHtml = $pageLang .
' - ' .
294 $services->getLanguageNameUtils()->getLanguageName( $pageLang,
$lang->getCode() );
296 if ( $config->get(
'PageLanguageUseDB' )
297 && $this->getContext()->getAuthority()->probablyCan(
'pagelang',
$title )
299 $pageLangHtml .=
' ' . $this->
msg(
'parentheses' )->rawParams( $linkRenderer->makeLink(
301 $this->msg(
'pageinfo-language-change' )->text()
305 $pageInfo[
'header-basic'][] = [
306 $this->
msg(
'pageinfo-language' )->escaped(),
311 $modelHtml = htmlspecialchars( ContentHandler::getLocalizedName(
$title->getContentModel() ) );
314 $modelHtml .=
' ' . $this->
msg(
'parentheses' )->rawParams( $linkRenderer->makeLink(
316 $this->msg(
'pageinfo-content-model-change' )->text()
320 $pageInfo[
'header-basic'][] = [
321 $this->
msg(
'pageinfo-content-model' ),
327 if ( $pageUser && $pageUser->getId() && !$pageUser->isHidden() ) {
328 $pageInfo[
'header-basic'][] = [
329 $this->
msg(
'pageinfo-user-id' ),
337 if ( isset( $pageProperties[
'noindex'] ) ) {
338 $pOutput->setIndexPolicy(
'noindex' );
340 if ( isset( $pageProperties[
'index'] ) ) {
341 $pOutput->setIndexPolicy(
'index' );
345 $policy = $this->
getArticle()->getRobotPolicy(
'view', $pOutput );
346 $pageInfo[
'header-basic'][] = [
348 $this->
msg(
'pageinfo-robot-policy' ),
349 $this->
msg(
"pageinfo-robot-${policy['index']}" )
352 $unwatchedPageThreshold = $config->get(
'UnwatchedPageThreshold' );
354 ( $unwatchedPageThreshold !==
false &&
355 $pageCounts[
'watchers'] >= $unwatchedPageThreshold )
358 $pageInfo[
'header-basic'][] = [
359 $this->
msg(
'pageinfo-watchers' ),
360 $lang->formatNum( $pageCounts[
'watchers'] )
363 $config->get(
'ShowUpdatedMarker' ) &&
364 isset( $pageCounts[
'visitingWatchers'] )
366 $minToDisclose = $config->get(
'UnwatchedPageSecret' );
367 if ( $pageCounts[
'visitingWatchers'] > $minToDisclose ||
369 $pageInfo[
'header-basic'][] = [
370 $this->
msg(
'pageinfo-visiting-watchers' ),
371 $lang->formatNum( $pageCounts[
'visitingWatchers'] )
374 $pageInfo[
'header-basic'][] = [
375 $this->
msg(
'pageinfo-visiting-watchers' ),
376 $this->
msg(
'pageinfo-few-visiting-watchers' )
380 } elseif ( $unwatchedPageThreshold !==
false ) {
381 $pageInfo[
'header-basic'][] = [
382 $this->
msg(
'pageinfo-watchers' ),
383 $this->
msg(
'pageinfo-few-watchers' )->numParams( $unwatchedPageThreshold )
389 $pageInfo[
'header-basic'][] = [
390 $linkRenderer->makeLink(
392 $this->
msg(
'pageinfo-redirects-name' )->text(),
400 $this->
msg(
'pageinfo-redirects-value' )
401 ->numParams( count(
$title->getRedirectsHere() ) )
406 $pageInfo[
'header-basic'][] = [
407 $this->
msg(
'pageinfo-contentpage' ),
408 $this->
msg(
'pageinfo-contentpage-yes' )
413 if ( $services->getNamespaceInfo()->hasSubpages(
$title->getNamespace() ) ) {
415 'Prefixindex',
$title->getPrefixedText() .
'/' );
416 $pageInfo[
'header-basic'][] = [
417 $linkRenderer->makeLink(
419 $this->
msg(
'pageinfo-subpages-name' )->text()
421 $this->
msg(
'pageinfo-subpages-value' )
423 $pageCounts[
'subpages'][
'total'],
424 $pageCounts[
'subpages'][
'redirects'],
425 $pageCounts[
'subpages'][
'nonredirects'] )
430 $category = Category::newFromTitle(
$title );
434 $allCount = (int)$category->getPageCount();
435 $subcatCount = (int)$category->getSubcatCount();
436 $fileCount = (int)$category->getFileCount();
437 $pagesCount = $allCount - $subcatCount - $fileCount;
439 $pageInfo[
'category-info'] = [
441 $this->
msg(
'pageinfo-category-total' ),
442 $lang->formatNum( $allCount )
445 $this->
msg(
'pageinfo-category-pages' ),
446 $lang->formatNum( $pagesCount )
449 $this->
msg(
'pageinfo-category-subcats' ),
450 $lang->formatNum( $subcatCount )
453 $this->
msg(
'pageinfo-category-files' ),
454 $lang->formatNum( $fileCount )
461 $fileObj = $services->getRepoGroup()->findFile(
$title );
462 if ( $fileObj !==
false ) {
464 $output = Wikimedia\base_convert( $fileObj->getSha1(), 36, 16, 40 );
465 $pageInfo[
'header-basic'][] = [
466 $this->
msg(
'pageinfo-file-hash' ),
473 $pageInfo[
'header-restrictions'] = [];
476 if (
$title->isCascadeProtected() ) {
478 $sources =
$title->getCascadeProtectionSources()[0];
480 foreach ( $sources as $sourceTitle ) {
481 $cascadingFrom .= Html::rawElement(
482 'li', [], $linkRenderer->makeKnownLink( $sourceTitle ) );
485 $cascadingFrom = Html::rawElement(
'ul', [], $cascadingFrom );
486 $pageInfo[
'header-restrictions'][] = [
487 $this->
msg(
'pageinfo-protect-cascading-from' ),
493 if (
$title->areRestrictionsCascading() ) {
494 $pageInfo[
'header-restrictions'][] = [
495 $this->
msg(
'pageinfo-protect-cascading' ),
496 $this->
msg(
'pageinfo-protect-cascading-yes' )
501 foreach (
$title->getRestrictionTypes() as $restrictionType ) {
502 $protectionLevel = implode(
', ',
$title->getRestrictions( $restrictionType ) );
504 if ( $protectionLevel ==
'' ) {
506 $message = $this->
msg(
'protect-default' )->escaped();
510 $message = $this->
msg(
"protect-level-$protectionLevel" );
511 if ( $message->isDisabled() ) {
513 $message = $this->
msg(
"protect-fallback", $protectionLevel )->parse();
515 $message = $message->escaped();
518 $expiry =
$title->getRestrictionExpiry( $restrictionType );
519 $formattedexpiry = $this->
msg(
'parentheses',
520 $lang->formatExpiry( $expiry ) )->escaped();
521 $message .= $this->
msg(
'word-separator' )->escaped() . $formattedexpiry;
525 $pageInfo[
'header-restrictions'][] = [
526 $this->
msg(
"restriction-$restrictionType" ), $message
530 $pageInfo[
'header-restrictions'][] = [
532 $linkRenderer->makeKnownLink(
534 $this->
msg(
'pageinfo-view-protect-log' )->text(),
536 [
'type' =>
'protect',
'page' =>
$title->getPrefixedText() ]
545 $pageInfo[
'header-edits'] = [];
547 $firstRev = MediaWikiServices::getInstance()
548 ->getRevisionLookup()
549 ->getFirstRevision( $this->
getTitle() );
550 $lastRev = $this->
getWikiPage()->getRevisionRecord();
551 $linkBatchFactory = $services->getLinkBatchFactory();
552 $batch = $linkBatchFactory->newLinkBatch();
554 $firstRevUser = $firstRev->getUser( RevisionRecord::FOR_THIS_USER, $user );
555 if ( $firstRevUser ) {
556 $batch->add(
NS_USER, $firstRevUser->getName() );
562 $lastRevUser = $lastRev->getUser( RevisionRecord::FOR_THIS_USER, $user );
563 if ( $lastRevUser ) {
564 $batch->add(
NS_USER, $lastRevUser->getName() );
573 $pageInfo[
'header-edits'][] = [
574 $this->
msg(
'pageinfo-firstuser' ),
579 $pageInfo[
'header-edits'][] = [
580 $this->
msg(
'pageinfo-firsttime' ),
581 $linkRenderer->makeKnownLink(
583 $lang->userTimeAndDate( $firstRev->getTimestamp(), $user ),
585 [
'oldid' => $firstRev->getId() ]
592 $pageInfo[
'header-edits'][] = [
593 $this->
msg(
'pageinfo-lastuser' ),
598 $pageInfo[
'header-edits'][] = [
599 $this->
msg(
'pageinfo-lasttime' ),
600 $linkRenderer->makeKnownLink(
602 $lang->userTimeAndDate( $this->getWikiPage()->getTimestamp(), $user ),
604 [
'oldid' => $this->getWikiPage()->getLatest() ]
610 $pageInfo[
'header-edits'][] = [
611 $this->
msg(
'pageinfo-edits' ),
$lang->formatNum( $pageCounts[
'edits'] )
615 if ( $pageCounts[
'authors'] > 0 ) {
616 $pageInfo[
'header-edits'][] = [
617 $this->
msg(
'pageinfo-authors' ),
$lang->formatNum( $pageCounts[
'authors'] )
622 $pageInfo[
'header-edits'][] = [
623 $this->
msg(
'pageinfo-recent-edits',
624 $lang->formatDuration( $config->get(
'RCMaxAge' ) ) ),
625 $lang->formatNum( $pageCounts[
'recent_edits'] )
629 $pageInfo[
'header-edits'][] = [
630 $this->
msg(
'pageinfo-recent-authors' ),
631 $lang->formatNum( $pageCounts[
'recent_authors'] )
635 $magicWords = $services->getMagicWordFactory()->getDoubleUnderscoreArray();
641 $localizedWords = $services->getContentLanguage()->getMagicWords();
644 foreach ( $pageProperties as $property => $value ) {
645 if ( in_array( $property, $wordIDs ) ) {
646 $listItems[] = Html::element(
'li', [], $localizedWords[$property][1] );
650 $localizedList = Html::rawElement(
'ul', [], implode(
'', $listItems ) );
651 $hiddenCategories = $this->
getWikiPage()->getHiddenCategories();
654 count( $listItems ) > 0 ||
655 count( $hiddenCategories ) > 0 ||
656 $pageCounts[
'transclusion'][
'from'] > 0 ||
657 $pageCounts[
'transclusion'][
'to'] > 0
659 $options = [
'LIMIT' => $config->get(
'PageInfoTransclusionLimit' ) ];
660 $transcludedTemplates =
$title->getTemplateLinksFrom( $options );
661 if ( $config->get(
'MiserMode' ) ) {
662 $transcludedTargets = [];
664 $transcludedTargets =
$title->getTemplateLinksTo( $options );
668 $pageInfo[
'header-properties'] = [];
671 if ( count( $listItems ) > 0 ) {
672 $pageInfo[
'header-properties'][] = [
673 $this->
msg(
'pageinfo-magic-words' )->numParams( count( $listItems ) ),
679 if ( count( $hiddenCategories ) > 0 ) {
680 $pageInfo[
'header-properties'][] = [
681 $this->
msg(
'pageinfo-hidden-categories' )
682 ->numParams( count( $hiddenCategories ) ),
688 if ( $pageCounts[
'transclusion'][
'from'] > 0 ) {
689 if ( $pageCounts[
'transclusion'][
'from'] > count( $transcludedTemplates ) ) {
690 $more = $this->
msg(
'morenotlisted' )->escaped();
700 $pageInfo[
'header-properties'][] = [
701 $this->
msg(
'pageinfo-templates' )
702 ->numParams( $pageCounts[
'transclusion'][
'from'] ),
703 $templateListFormatter->format( $transcludedTemplates,
false, $more )
707 if ( !$config->get(
'MiserMode' ) && $pageCounts[
'transclusion'][
'to'] > 0 ) {
708 if ( $pageCounts[
'transclusion'][
'to'] > count( $transcludedTargets ) ) {
709 $more = $linkRenderer->makeLink(
711 $this->
msg(
'moredotdotdot' )->text(),
713 [
'hidelinks' => 1,
'hideredirs' => 1 ]
724 $pageInfo[
'header-properties'][] = [
725 $this->
msg(
'pageinfo-transclusions' )
726 ->numParams( $pageCounts[
'transclusion'][
'to'] ),
727 $templateListFormatter->format( $transcludedTargets,
false, $more )