356 $id =
$title->getArticleID();
357 $config = $this->context->getConfig();
358 $linkRenderer = $this->linkRenderer;
360 $pageCounts = $this->pageCounts();
362 $props = $this->pageProps->getAllProperties(
$title );
363 $pageProperties = $props[$id] ?? [];
367 $pageInfo[
'header-basic'] = [];
370 $displayTitle = $pageProperties[
'displaytitle'] ??
371 htmlspecialchars(
$title->getPrefixedText(), ENT_NOQUOTES );
373 $pageInfo[
'header-basic'][] = [
374 $this->
msg(
'pageinfo-display-title' ),
379 $redirectTarget = $this->redirectLookup->getRedirectTarget( $this->
getWikiPage() );
380 if ( $redirectTarget !==
null ) {
381 $pageInfo[
'header-basic'][] = [
382 $this->
msg(
'pageinfo-redirectsto' ),
383 $linkRenderer->
makeLink( $redirectTarget ) .
384 $this->
msg(
'word-separator' )->escaped() .
385 $this->
msg(
'parentheses' )->rawParams( $linkRenderer->
makeLink(
387 $this->msg(
'pageinfo-redirectsto-info' )->text(),
389 [
'action' =>
'info' ]
395 $sortKey = $pageProperties[
'defaultsort'] ??
$title->getCategorySortkey();
397 $sortKey = htmlspecialchars( $sortKey );
398 $pageInfo[
'header-basic'][] = [ $this->
msg(
'pageinfo-default-sort' ), $sortKey ];
401 $pageInfo[
'header-basic'][] = [
402 $this->
msg(
'pageinfo-length' ),
407 $pageInfo[
'header-basic'][] = [ $this->
msg(
'pageinfo-namespace-id' ),
$title->getNamespace() ];
408 $pageNamespace =
$title->getNsText();
409 if ( $pageNamespace ) {
410 $pageInfo[
'header-basic'][] = [ $this->
msg(
'pageinfo-namespace' ), $pageNamespace ];
414 $pageInfo[
'header-basic'][] = [ $this->
msg(
'pageinfo-article-id' ), $id ];
417 $pageLang =
$title->getPageLanguage()->getCode();
419 $pageLangHtml = $pageLang .
' - ' .
420 $this->languageNameUtils->getLanguageName( $pageLang,
$lang->getCode() );
422 if ( $config->get( MainConfigNames::PageLanguageUseDB )
423 && $this->getAuthority()->probablyCan(
'pagelang',
$title )
425 $pageLangHtml .=
' ' . $this->
msg(
'parentheses' )->rawParams( $linkRenderer->
makeLink(
427 $this->msg(
'pageinfo-language-change' )->text()
431 $pageInfo[
'header-basic'][] = [
432 $this->
msg(
'pageinfo-language' )->escaped(),
437 $modelHtml = htmlspecialchars( ContentHandler::getLocalizedName(
$title->getContentModel() ) );
440 $modelHtml .=
' ' . $this->
msg(
'parentheses' )->rawParams( $linkRenderer->
makeLink(
442 $this->msg(
'pageinfo-content-model-change' )->text()
446 $pageInfo[
'header-basic'][] = [
447 $this->
msg(
'pageinfo-content-model' ),
453 if ( $pageUser && $pageUser->getId() && !$pageUser->isHidden() ) {
454 $pageInfo[
'header-basic'][] = [
455 $this->
msg(
'pageinfo-user-id' ),
463 if ( isset( $pageProperties[
'noindex'] ) ) {
464 $parserOutput->setIndexPolicy(
'noindex' );
466 if ( isset( $pageProperties[
'index'] ) ) {
467 $parserOutput->setIndexPolicy(
'index' );
471 $policy = $this->
getArticle()->getRobotPolicy(
'view', $parserOutput );
472 $pageInfo[
'header-basic'][] = [
474 $this->
msg(
'pageinfo-robot-policy' ),
475 $this->
msg(
"pageinfo-robot-{$policy['index']}" )
478 $unwatchedPageThreshold = $config->get( MainConfigNames::UnwatchedPageThreshold );
479 if ( $this->
getAuthority()->isAllowed(
'unwatchedpages' ) ||
480 ( $unwatchedPageThreshold !==
false &&
481 $pageCounts[
'watchers'] >= $unwatchedPageThreshold )
484 $pageInfo[
'header-basic'][] = [
485 $this->
msg(
'pageinfo-watchers' ),
486 $lang->formatNum( $pageCounts[
'watchers'] )
489 $config->get( MainConfigNames::ShowUpdatedMarker ) &&
490 isset( $pageCounts[
'visitingWatchers'] )
492 $minToDisclose = $config->get( MainConfigNames::UnwatchedPageSecret );
493 if ( $pageCounts[
'visitingWatchers'] > $minToDisclose ||
494 $this->
getAuthority()->isAllowed(
'unwatchedpages' ) ) {
495 $pageInfo[
'header-basic'][] = [
496 $this->
msg(
'pageinfo-visiting-watchers' ),
497 $lang->formatNum( $pageCounts[
'visitingWatchers'] )
500 $pageInfo[
'header-basic'][] = [
501 $this->
msg(
'pageinfo-visiting-watchers' ),
502 $this->
msg(
'pageinfo-few-visiting-watchers' )
506 } elseif ( $unwatchedPageThreshold !==
false ) {
507 $pageInfo[
'header-basic'][] = [
508 $this->
msg(
'pageinfo-watchers' ),
509 $this->
msg(
'pageinfo-few-watchers' )->numParams( $unwatchedPageThreshold )
515 $pageInfo[
'header-basic'][] = [
518 $this->
msg(
'pageinfo-redirects-name' )->text(),
526 $this->
msg(
'pageinfo-redirects-value' )
527 ->numParams( count(
$title->getRedirectsHere() ) )
532 $pageInfo[
'header-basic'][] = [
533 $this->
msg(
'pageinfo-contentpage' ),
534 $this->
msg(
'pageinfo-contentpage-yes' )
539 if ( $this->namespaceInfo->hasSubpages(
$title->getNamespace() ) ) {
542 $title->getPrefixedText() .
'/'
544 $pageInfo[
'header-basic'][] = [
547 $this->
msg(
'pageinfo-subpages-name' )->text()
549 $this->
msg(
'pageinfo-subpages-value' )
551 $pageCounts[
'subpages'][
'total'],
552 $pageCounts[
'subpages'][
'redirects'],
553 $pageCounts[
'subpages'][
'nonredirects']
559 $category = Category::newFromTitle(
$title );
561 $allCount = $category->getMemberCount();
562 $subcatCount = $category->getSubcatCount();
563 $fileCount = $category->getFileCount();
564 $pageCount = $category->getPageCount( Category::COUNT_CONTENT_PAGES );
566 $pageInfo[
'category-info'] = [
568 $this->
msg(
'pageinfo-category-total' ),
569 $lang->formatNum( $allCount )
572 $this->
msg(
'pageinfo-category-pages' ),
573 $lang->formatNum( $pageCount )
576 $this->
msg(
'pageinfo-category-subcats' ),
577 $lang->formatNum( $subcatCount )
580 $this->
msg(
'pageinfo-category-files' ),
581 $lang->formatNum( $fileCount )
588 $fileObj = $this->repoGroup->findFile(
$title );
589 if ( $fileObj !==
false ) {
591 $output = Wikimedia\base_convert( $fileObj->getSha1(), 36, 16, 40 );
592 $pageInfo[
'header-basic'][] = [
593 $this->
msg(
'pageinfo-file-hash' ),
600 $pageInfo[
'header-restrictions'] = [];
603 if ( $this->restrictionStore->isCascadeProtected(
$title ) ) {
605 $sources = $this->restrictionStore->getCascadeProtectionSources(
$title )[0];
607 foreach ( $sources as $sourcePageIdentity ) {
608 $cascadingFrom .= Html::rawElement(
615 $cascadingFrom = Html::rawElement(
'ul', [], $cascadingFrom );
616 $pageInfo[
'header-restrictions'][] = [
617 $this->
msg(
'pageinfo-protect-cascading-from' ),
623 if ( $this->restrictionStore->areRestrictionsCascading(
$title ) ) {
624 $pageInfo[
'header-restrictions'][] = [
625 $this->
msg(
'pageinfo-protect-cascading' ),
626 $this->
msg(
'pageinfo-protect-cascading-yes' )
631 foreach ( $this->restrictionStore->listApplicableRestrictionTypes(
$title ) as $restrictionType ) {
632 $protections = $this->restrictionStore->getRestrictions(
$title, $restrictionType );
634 switch ( count( $protections ) ) {
638 $this->
msg(
'protect-default' )->escaped();
643 $message = $this->
msg(
'protect-level-' . $protections[0] );
644 if ( !$message->isDisabled() ) {
645 $message = $message->escaped();
652 $message = $this->
msg(
"protect-fallback",
$lang->commaList( $protections ) )->parse();
655 $expiry = $this->restrictionStore->getRestrictionExpiry(
$title, $restrictionType );
656 $formattedexpiry = $expiry ===
null ?
'' : $this->
msg(
658 $lang->formatExpiry( $expiry,
true,
'infinity', $user )
660 $message .= $this->
msg(
'word-separator' )->escaped() . $formattedexpiry;
664 $pageInfo[
'header-restrictions'][] = [
665 $this->
msg(
"restriction-$restrictionType" ), $message
669 $pageInfo[
'header-restrictions'][] = [
673 $this->
msg(
'pageinfo-view-protect-log' )->text(),
675 [
'type' =>
'protect',
'page' =>
$title->getPrefixedText() ]
684 $pageInfo[
'header-edits'] = [];
686 $firstRev = $this->revisionLookup->getFirstRevision( $this->
getTitle() );
687 $lastRev = $this->
getWikiPage()->getRevisionRecord();
688 $batch = $this->linkBatchFactory->newLinkBatch();
690 $firstRevUser = $firstRev->getUser( RevisionRecord::FOR_THIS_USER, $user );
691 if ( $firstRevUser ) {
692 $batch->add(
NS_USER, $firstRevUser->getName() );
698 $lastRevUser = $lastRev->getUser( RevisionRecord::FOR_THIS_USER, $user );
699 if ( $lastRevUser ) {
700 $batch->add(
NS_USER, $lastRevUser->getName() );
709 $pageInfo[
'header-edits'][] = [
710 $this->
msg(
'pageinfo-firstuser' ),
711 Linker::revUserTools( $firstRev )
715 $pageInfo[
'header-edits'][] = [
716 $this->
msg(
'pageinfo-firsttime' ),
719 $lang->userTimeAndDate( $firstRev->getTimestamp(), $user ),
721 [
'oldid' => $firstRev->getId() ]
728 $pageInfo[
'header-edits'][] = [
729 $this->
msg(
'pageinfo-lastuser' ),
730 Linker::revUserTools( $lastRev )
734 $pageInfo[
'header-edits'][] = [
735 $this->
msg(
'pageinfo-lasttime' ),
738 $lang->userTimeAndDate( $this->getWikiPage()->getTimestamp(), $user ),
740 [
'oldid' => $this->getWikiPage()->getLatest() ]
746 $pageInfo[
'header-edits'][] = [
747 $this->
msg(
'pageinfo-edits' ),
748 $lang->formatNum( $pageCounts[
'edits'] )
752 if ( $pageCounts[
'authors'] > 0 ) {
753 $pageInfo[
'header-edits'][] = [
754 $this->
msg(
'pageinfo-authors' ),
755 $lang->formatNum( $pageCounts[
'authors'] )
760 $pageInfo[
'header-edits'][] = [
762 'pageinfo-recent-edits',
763 $lang->formatDuration( $config->get( MainConfigNames::RCMaxAge ) )
765 $lang->formatNum( $pageCounts[
'recent_edits'] )
769 $pageInfo[
'header-edits'][] = [
770 $this->
msg(
'pageinfo-recent-authors' ),
771 $lang->formatNum( $pageCounts[
'recent_authors'] )
775 $magicWords = $this->magicWordFactory->getDoubleUnderscoreArray();
781 $localizedWords = $this->contentLanguage->getMagicWords();
784 foreach ( $pageProperties as $property => $value ) {
785 if ( in_array( $property, $wordIDs ) ) {
786 $listItems[] = Html::element(
'li', [], $localizedWords[$property][1] );
790 $localizedList = Html::rawElement(
'ul', [], implode(
'', $listItems ) );
791 $hiddenCategories = $this->
getWikiPage()->getHiddenCategories();
794 count( $listItems ) > 0 ||
795 count( $hiddenCategories ) > 0 ||
796 $pageCounts[
'transclusion'][
'from'] > 0 ||
797 $pageCounts[
'transclusion'][
'to'] > 0
799 $options = [
'LIMIT' => $config->get( MainConfigNames::PageInfoTransclusionLimit ) ];
800 $transcludedTemplates =
$title->getTemplateLinksFrom( $options );
801 if ( $config->get( MainConfigNames::MiserMode ) ) {
802 $transcludedTargets = [];
804 $transcludedTargets =
$title->getTemplateLinksTo( $options );
808 $pageInfo[
'header-properties'] = [];
811 if ( count( $listItems ) > 0 ) {
812 $pageInfo[
'header-properties'][] = [
813 $this->
msg(
'pageinfo-magic-words' )->numParams( count( $listItems ) ),
819 if ( count( $hiddenCategories ) > 0 ) {
820 $pageInfo[
'header-properties'][] = [
821 $this->
msg(
'pageinfo-hidden-categories' )
822 ->numParams( count( $hiddenCategories ) ),
823 Linker::formatHiddenCategories( $hiddenCategories )
828 if ( $pageCounts[
'transclusion'][
'from'] > 0 ) {
829 if ( $pageCounts[
'transclusion'][
'from'] > count( $transcludedTemplates ) ) {
830 $more = $this->
msg(
'morenotlisted' )->escaped();
838 $this->linkBatchFactory,
839 $this->restrictionStore
842 $pageInfo[
'header-properties'][] = [
843 $this->
msg(
'pageinfo-templates' )
844 ->numParams( $pageCounts[
'transclusion'][
'from'] ),
845 $templateListFormatter->format( $transcludedTemplates,
false, $more )
849 if ( !$config->get( MainConfigNames::MiserMode ) && $pageCounts[
'transclusion'][
'to'] > 0 ) {
850 if ( $pageCounts[
'transclusion'][
'to'] > count( $transcludedTargets ) ) {
853 $this->
msg(
'moredotdotdot' )->text(),
855 [
'hidelinks' => 1,
'hideredirs' => 1 ]
864 $this->linkBatchFactory,
865 $this->restrictionStore
868 $pageInfo[
'header-properties'][] = [
869 $this->
msg(
'pageinfo-transclusions' )
870 ->numParams( $pageCounts[
'transclusion'][
'to'] ),
871 $templateListFormatter->format( $transcludedTargets,
false, $more )