330 $config = $this->context->getConfig();
331 $linkRenderer = $this->linkRenderer;
333 $pageCounts = $this->pageCounts();
335 $props = $this->pageProps->getAllProperties( $title );
336 $pageProperties = $props[$id] ?? [];
340 $pageInfo[
'header-basic'] = [];
343 $displayTitle = $pageProperties[
'displaytitle'] ??
346 $pageInfo[
'header-basic'][] = [
347 $this->
msg(
'pageinfo-display-title' ),
352 $redirectTarget = $this->redirectLookup->getRedirectTarget( $this->
getWikiPage() );
353 if ( $redirectTarget !==
null ) {
354 $pageInfo[
'header-basic'][] = [
355 $this->
msg(
'pageinfo-redirectsto' ),
356 $linkRenderer->
makeLink( $redirectTarget ) .
357 $this->
msg(
'word-separator' )->escaped() .
358 $this->
msg(
'parentheses' )->rawParams( $linkRenderer->
makeLink(
360 $this->msg(
'pageinfo-redirectsto-info' )->text(),
362 [
'action' =>
'info' ]
370 $sortKey = htmlspecialchars( $sortKey );
371 $pageInfo[
'header-basic'][] = [ $this->
msg(
'pageinfo-default-sort' ), $sortKey ];
374 $pageInfo[
'header-basic'][] = [
375 $this->
msg(
'pageinfo-length' ),
380 $pageInfo[
'header-basic'][] = [ $this->
msg(
'pageinfo-namespace-id' ), $title->
getNamespace() ];
382 if ( $pageNamespace ) {
383 $pageInfo[
'header-basic'][] = [ $this->
msg(
'pageinfo-namespace' ), $pageNamespace ];
387 $pageInfo[
'header-basic'][] = [ $this->
msg(
'pageinfo-article-id' ), $id ];
392 $pageLangHtml = $pageLang .
' - ' .
393 $this->languageNameUtils->getLanguageName( $pageLang, $lang->getCode() );
395 if ( $config->get( MainConfigNames::PageLanguageUseDB )
396 && $this->getAuthority()->probablyCan(
'pagelang', $title )
398 $pageLangHtml .= $this->
msg(
'word-separator' )->escaped();
399 $pageLangHtml .= $this->
msg(
'parentheses' )->rawParams( $linkRenderer->
makeLink(
400 SpecialPage::getTitleValueFor(
'PageLanguage', $title->
getPrefixedText() ),
401 $this->msg(
'pageinfo-language-change' )->text()
405 $pageInfo[
'header-basic'][] = [
406 $this->
msg(
'pageinfo-language' )->escaped(),
411 $modelHtml = htmlspecialchars( ContentHandler::getLocalizedName( $title->
getContentModel() ) );
413 if ( $this->
getAuthority()->probablyCan(
'editcontentmodel', $title ) ) {
414 $modelHtml .= $this->
msg(
'word-separator' )->escaped();
415 $modelHtml .= $this->
msg(
'parentheses' )->rawParams( $linkRenderer->
makeLink(
416 SpecialPage::getTitleValueFor(
'ChangeContentModel', $title->
getPrefixedText() ),
417 $this->msg(
'pageinfo-content-model-change' )->text()
421 $pageInfo[
'header-basic'][] = [
422 $this->
msg(
'pageinfo-content-model' ),
427 $pageUser = User::newFromName( $title->
getRootText() );
428 if ( $pageUser && $pageUser->getId() && !$pageUser->isHidden() ) {
429 $pageInfo[
'header-basic'][] = [
430 $this->
msg(
'pageinfo-user-id' ),
438 if ( isset( $pageProperties[
'noindex'] ) ) {
439 $parserOutput->setIndexPolicy(
'noindex' );
441 if ( isset( $pageProperties[
'index'] ) ) {
442 $parserOutput->setIndexPolicy(
'index' );
446 $policy = $this->
getArticle()->getRobotPolicy(
'view', $parserOutput );
447 $pageInfo[
'header-basic'][] = [
449 $this->
msg(
'pageinfo-robot-policy' ),
450 $this->
msg(
"pageinfo-robot-{$policy['index']}" )
453 $unwatchedPageThreshold = $config->get( MainConfigNames::UnwatchedPageThreshold );
454 if ( $this->
getAuthority()->isAllowed(
'unwatchedpages' ) ||
455 ( $unwatchedPageThreshold !==
false &&
456 $pageCounts[
'watchers'] >= $unwatchedPageThreshold )
459 $pageInfo[
'header-basic'][] = [
460 $this->
msg(
'pageinfo-watchers' ),
461 $lang->formatNum( $pageCounts[
'watchers'] )
464 $config->get( MainConfigNames::ShowUpdatedMarker ) &&
465 isset( $pageCounts[
'visitingWatchers'] )
467 $minToDisclose = $config->get( MainConfigNames::UnwatchedPageSecret );
468 if ( $pageCounts[
'visitingWatchers'] > $minToDisclose ||
469 $this->
getAuthority()->isAllowed(
'unwatchedpages' ) ) {
470 $pageInfo[
'header-basic'][] = [
471 $this->
msg(
'pageinfo-visiting-watchers' ),
472 $lang->formatNum( $pageCounts[
'visitingWatchers'] )
475 $pageInfo[
'header-basic'][] = [
476 $this->
msg(
'pageinfo-visiting-watchers' ),
477 $this->
msg(
'pageinfo-few-visiting-watchers' )
481 } elseif ( $unwatchedPageThreshold !==
false ) {
482 $pageInfo[
'header-basic'][] = [
483 $this->
msg(
'pageinfo-watchers' ),
484 $this->
msg(
'pageinfo-few-watchers' )->numParams( $unwatchedPageThreshold )
489 $whatLinksHere = SpecialPage::getTitleFor(
'Whatlinkshere', $title->
getPrefixedText() );
490 $pageInfo[
'header-basic'][] = [
493 $this->
msg(
'pageinfo-redirects-name' )->text(),
501 $this->
msg(
'pageinfo-redirects-value' )
507 $pageInfo[
'header-basic'][] = [
508 $this->
msg(
'pageinfo-contentpage' ),
509 $this->
msg(
'pageinfo-contentpage-yes' )
514 if ( $this->namespaceInfo->hasSubpages( $title->
getNamespace() ) ) {
515 $prefixIndex = SpecialPage::getTitleFor(
519 $pageInfo[
'header-basic'][] = [
522 $this->
msg(
'pageinfo-subpages-name' )->text()
525 isset( $pageCounts[
'subpages'] )
526 ? $this->
msg(
'pageinfo-subpages-value' )->numParams(
527 $pageCounts[
'subpages'][
'total'],
528 $pageCounts[
'subpages'][
'redirects'],
529 $pageCounts[
'subpages'][
'nonredirects']
530 ) : $this->
msg(
'pageinfo-subpages-value-unknown' )->rawParams(
532 $title, $this->msg(
'purge' )->text(), [], [
'action' =>
'purge' ] )
538 $category = Category::newFromTitle( $title );
540 $allCount = $category->getMemberCount();
541 $subcatCount = $category->getSubcatCount();
542 $fileCount = $category->getFileCount();
543 $pageCount = $category->getPageCount( Category::COUNT_CONTENT_PAGES );
545 $pageInfo[
'category-info'] = [
547 $this->
msg(
'pageinfo-category-total' ),
548 $lang->formatNum( $allCount )
551 $this->
msg(
'pageinfo-category-pages' ),
552 $lang->formatNum( $pageCount )
555 $this->
msg(
'pageinfo-category-subcats' ),
556 $lang->formatNum( $subcatCount )
559 $this->
msg(
'pageinfo-category-files' ),
560 $lang->formatNum( $fileCount )
567 $fileObj = $this->repoGroup->findFile( $title );
568 if ( $fileObj !==
false ) {
570 $output = Wikimedia\base_convert( $fileObj->getSha1(), 36, 16, 40 );
571 $pageInfo[
'header-basic'][] = [
572 $this->
msg(
'pageinfo-file-hash' ),
579 $pageInfo[
'header-restrictions'] = [];
582 if ( $this->restrictionStore->isCascadeProtected( $title ) ) {
584 $sources = $this->restrictionStore->getCascadeProtectionSources( $title )[0];
586 foreach ( $sources as $sourcePageIdentity ) {
587 $cascadingFrom .= Html::rawElement(
594 $cascadingFrom = Html::rawElement(
'ul', [], $cascadingFrom );
595 $pageInfo[
'header-restrictions'][] = [
596 $this->
msg(
'pageinfo-protect-cascading-from' ),
602 if ( $this->restrictionStore->areRestrictionsCascading( $title ) ) {
603 $pageInfo[
'header-restrictions'][] = [
604 $this->
msg(
'pageinfo-protect-cascading' ),
605 $this->
msg(
'pageinfo-protect-cascading-yes' )
610 foreach ( $this->restrictionStore->listApplicableRestrictionTypes( $title ) as $restrictionType ) {
611 $protections = $this->restrictionStore->getRestrictions( $title, $restrictionType );
613 switch ( count( $protections ) ) {
617 $this->
msg(
'protect-default' )->escaped();
622 $message = $this->
msg(
'protect-level-' . $protections[0] );
623 if ( !$message->isDisabled() ) {
624 $message = $message->escaped();
631 $message = $this->
msg(
"protect-fallback", $lang->commaList( $protections ) )->parse();
634 $expiry = $this->restrictionStore->getRestrictionExpiry( $title, $restrictionType );
635 $formattedexpiry = $expiry ===
null ?
'' : $this->
msg(
637 $lang->formatExpiry( $expiry,
true,
'infinity', $user )
639 $message .= $this->
msg(
'word-separator' )->escaped() . $formattedexpiry;
643 $pageInfo[
'header-restrictions'][] = [
644 $this->
msg(
"restriction-$restrictionType" ), $message
647 $protectLog = SpecialPage::getTitleFor(
'Log' );
648 $pageInfo[
'header-restrictions'][] = [
652 $this->
msg(
'pageinfo-view-protect-log' )->text(),
663 $pageInfo[
'header-edits'] = [];
665 $firstRev = $this->revisionLookup->getFirstRevision( $this->
getTitle() );
666 $lastRev = $this->
getWikiPage()->getRevisionRecord();
667 $batch = $this->linkBatchFactory->newLinkBatch();
669 $firstRevUser = $firstRev->getUser( RevisionRecord::FOR_THIS_USER, $user );
670 if ( $firstRevUser ) {
671 $batch->add(
NS_USER, $firstRevUser->getName() );
677 $lastRevUser = $lastRev->getUser( RevisionRecord::FOR_THIS_USER, $user );
678 if ( $lastRevUser ) {
679 $batch->add(
NS_USER, $lastRevUser->getName() );
688 $firstRevUser = $firstRev->getUser( RevisionRecord::FOR_THIS_USER, $user );
692 $firstRevUserName = $firstRevUser ? $firstRevUser->getName() :
'[HIDDEN]';
693 $pageInfo[
'header-edits'][] = [
694 $this->
msg(
'pageinfo-firstuser', $firstRevUserName ),
695 Linker::revUserTools( $firstRev )
699 $pageInfo[
'header-edits'][] = [
700 $this->
msg(
'pageinfo-firsttime' ),
703 $lang->userTimeAndDate( $firstRev->getTimestamp(), $user ),
705 [
'oldid' => $firstRev->getId() ]
712 $lastRevUser = $lastRev->getUser( RevisionRecord::FOR_THIS_USER, $user );
716 $lastRevUserName = $lastRevUser ? $lastRevUser->getName() :
'[HIDDEN]';
717 $pageInfo[
'header-edits'][] = [
718 $this->
msg(
'pageinfo-lastuser', $lastRevUserName ),
719 Linker::revUserTools( $lastRev )
723 $pageInfo[
'header-edits'][] = [
724 $this->
msg(
'pageinfo-lasttime' ),
727 $lang->userTimeAndDate( $this->getWikiPage()->getTimestamp(), $user ),
729 [
'oldid' => $this->getWikiPage()->getLatest() ]
735 $pageInfo[
'header-edits'][] = [
736 $this->
msg(
'pageinfo-edits' ),
737 $lang->formatNum( $pageCounts[
'edits'] )
741 if ( $pageCounts[
'authors'] > 0 ) {
742 $pageInfo[
'header-edits'][] = [
743 $this->
msg(
'pageinfo-authors' ),
744 $lang->formatNum( $pageCounts[
'authors'] )
749 $pageInfo[
'header-edits'][] = [
751 'pageinfo-recent-edits',
752 $lang->formatDuration( $config->get( MainConfigNames::RCMaxAge ) )
754 $lang->formatNum( $pageCounts[
'recent_edits'] )
758 $pageInfo[
'header-edits'][] = [
759 $this->
msg(
'pageinfo-recent-authors' ),
760 $lang->formatNum( $pageCounts[
'recent_authors'] )
764 $magicWords = $this->magicWordFactory->getDoubleUnderscoreArray();
770 $localizedWords = $this->contentLanguage->getMagicWords();
773 foreach ( $pageProperties as $property => $value ) {
774 if ( in_array( $property, $wordIDs ) ) {
775 $listItems[] = Html::element(
'li', [], $localizedWords[$property][1] );
779 $localizedList = Html::rawElement(
'ul', [], implode(
'', $listItems ) );
780 $hiddenCategories = $this->
getWikiPage()->getHiddenCategories();
783 count( $listItems ) > 0 ||
784 count( $hiddenCategories ) > 0 ||
785 $pageCounts[
'transclusion'][
'from'] > 0 ||
786 $pageCounts[
'transclusion'][
'to'] > 0
788 $options = [
'LIMIT' => $config->get( MainConfigNames::PageInfoTransclusionLimit ) ];
790 if ( $config->get( MainConfigNames::MiserMode ) ) {
791 $transcludedTargets = [];
797 $pageInfo[
'header-properties'] = [];
800 if ( count( $listItems ) > 0 ) {
801 $pageInfo[
'header-properties'][] = [
802 $this->
msg(
'pageinfo-magic-words' )->numParams( count( $listItems ) ),
808 if ( count( $hiddenCategories ) > 0 ) {
809 $pageInfo[
'header-properties'][] = [
810 $this->
msg(
'pageinfo-hidden-categories' )
811 ->numParams( count( $hiddenCategories ) ),
812 Linker::formatHiddenCategories( $hiddenCategories )
817 if ( $pageCounts[
'transclusion'][
'from'] > 0 ) {
818 if ( $pageCounts[
'transclusion'][
'from'] > count( $transcludedTemplates ) ) {
819 $more = $this->
msg(
'morenotlisted' )->escaped();
827 $this->linkBatchFactory,
828 $this->restrictionStore
831 $pageInfo[
'header-properties'][] = [
832 $this->
msg(
'pageinfo-templates' )
833 ->numParams( $pageCounts[
'transclusion'][
'from'] ),
834 $templateListFormatter->format( $transcludedTemplates,
false, $more )
838 if ( !$config->get( MainConfigNames::MiserMode ) && $pageCounts[
'transclusion'][
'to'] > 0 ) {
839 if ( $pageCounts[
'transclusion'][
'to'] > count( $transcludedTargets ) ) {
842 $this->
msg(
'moredotdotdot' )->text(),
844 [
'hidelinks' => 1,
'hideredirs' => 1 ]
853 $this->linkBatchFactory,
854 $this->restrictionStore
857 $pageInfo[
'header-properties'][] = [
858 $this->
msg(
'pageinfo-transclusions' )
859 ->numParams( $pageCounts[
'transclusion'][
'to'] ),
860 $templateListFormatter->format( $transcludedTargets,
false, $more )