347 $id =
$title->getArticleID();
348 $config = $this->context->getConfig();
349 $linkRenderer = $this->linkRenderer;
353 $props = $this->pageProps->getAllProperties(
$title );
354 $pageProperties = $props[$id] ?? [];
358 $pageInfo[
'header-basic'] = [];
361 $displayTitle = $pageProperties[
'displaytitle'] ??
$title->getPrefixedText();
363 $pageInfo[
'header-basic'][] = [
364 $this->
msg(
'pageinfo-display-title' ),
369 $redirectTarget = $this->
getWikiPage()->getRedirectTarget();
370 if ( $redirectTarget !==
null ) {
371 $pageInfo[
'header-basic'][] = [
372 $this->
msg(
'pageinfo-redirectsto' ),
373 $linkRenderer->
makeLink( $redirectTarget ) .
374 $this->
msg(
'word-separator' )->escaped() .
375 $this->
msg(
'parentheses' )->rawParams( $linkRenderer->
makeLink(
377 $this->msg(
'pageinfo-redirectsto-info' )->text(),
379 [
'action' =>
'info' ]
385 $sortKey = $pageProperties[
'defaultsort'] ??
$title->getCategorySortkey();
387 $sortKey = htmlspecialchars( $sortKey );
388 $pageInfo[
'header-basic'][] = [ $this->
msg(
'pageinfo-default-sort' ), $sortKey ];
391 $pageInfo[
'header-basic'][] = [
392 $this->
msg(
'pageinfo-length' ),
397 $pageNamespace =
$title->getNsText();
398 if ( $pageNamespace ) {
399 $pageInfo[
'header-basic'][] = [ $this->
msg(
'pageinfo-namespace' ), $pageNamespace ];
403 $pageInfo[
'header-basic'][] = [ $this->
msg(
'pageinfo-article-id' ), $id ];
406 $pageLang =
$title->getPageLanguage()->getCode();
408 $pageLangHtml = $pageLang .
' - ' .
409 $this->languageNameUtils->getLanguageName( $pageLang,
$lang->getCode() );
411 if ( $config->get(
'PageLanguageUseDB' )
412 && $this->getContext()->getAuthority()->probablyCan(
'pagelang',
$title )
414 $pageLangHtml .=
' ' . $this->
msg(
'parentheses' )->rawParams( $linkRenderer->
makeLink(
416 $this->msg(
'pageinfo-language-change' )->text()
420 $pageInfo[
'header-basic'][] = [
421 $this->
msg(
'pageinfo-language' )->escaped(),
426 $modelHtml = htmlspecialchars( ContentHandler::getLocalizedName(
$title->getContentModel() ) );
429 $modelHtml .=
' ' . $this->
msg(
'parentheses' )->rawParams( $linkRenderer->
makeLink(
431 $this->msg(
'pageinfo-content-model-change' )->text()
435 $pageInfo[
'header-basic'][] = [
436 $this->
msg(
'pageinfo-content-model' ),
442 if ( $pageUser && $pageUser->getId() && !$pageUser->isHidden() ) {
443 $pageInfo[
'header-basic'][] = [
444 $this->
msg(
'pageinfo-user-id' ),
452 if ( isset( $pageProperties[
'noindex'] ) ) {
453 $pOutput->setIndexPolicy(
'noindex' );
455 if ( isset( $pageProperties[
'index'] ) ) {
456 $pOutput->setIndexPolicy(
'index' );
460 $policy = $this->
getArticle()->getRobotPolicy(
'view', $pOutput );
461 $pageInfo[
'header-basic'][] = [
463 $this->
msg(
'pageinfo-robot-policy' ),
464 $this->
msg(
"pageinfo-robot-{$policy['index']}" )
467 $unwatchedPageThreshold = $config->get(
'UnwatchedPageThreshold' );
469 ( $unwatchedPageThreshold !==
false &&
470 $pageCounts[
'watchers'] >= $unwatchedPageThreshold )
473 $pageInfo[
'header-basic'][] = [
474 $this->
msg(
'pageinfo-watchers' ),
475 $lang->formatNum( $pageCounts[
'watchers'] )
478 $config->get(
'ShowUpdatedMarker' ) &&
479 isset( $pageCounts[
'visitingWatchers'] )
481 $minToDisclose = $config->get(
'UnwatchedPageSecret' );
482 if ( $pageCounts[
'visitingWatchers'] > $minToDisclose ||
484 $pageInfo[
'header-basic'][] = [
485 $this->
msg(
'pageinfo-visiting-watchers' ),
486 $lang->formatNum( $pageCounts[
'visitingWatchers'] )
489 $pageInfo[
'header-basic'][] = [
490 $this->
msg(
'pageinfo-visiting-watchers' ),
491 $this->
msg(
'pageinfo-few-visiting-watchers' )
495 } elseif ( $unwatchedPageThreshold !==
false ) {
496 $pageInfo[
'header-basic'][] = [
497 $this->
msg(
'pageinfo-watchers' ),
498 $this->
msg(
'pageinfo-few-watchers' )->numParams( $unwatchedPageThreshold )
504 $pageInfo[
'header-basic'][] = [
507 $this->
msg(
'pageinfo-redirects-name' )->text(),
515 $this->
msg(
'pageinfo-redirects-value' )
516 ->numParams( count(
$title->getRedirectsHere() ) )
521 $pageInfo[
'header-basic'][] = [
522 $this->
msg(
'pageinfo-contentpage' ),
523 $this->
msg(
'pageinfo-contentpage-yes' )
528 if ( $this->namespaceInfo->hasSubpages(
$title->getNamespace() ) ) {
531 $title->getPrefixedText() .
'/'
533 $pageInfo[
'header-basic'][] = [
536 $this->
msg(
'pageinfo-subpages-name' )->text()
538 $this->
msg(
'pageinfo-subpages-value' )
540 $pageCounts[
'subpages'][
'total'],
541 $pageCounts[
'subpages'][
'redirects'],
542 $pageCounts[
'subpages'][
'nonredirects']
548 $category = Category::newFromTitle(
$title );
552 $allCount = (int)$category->getPageCount();
553 $subcatCount = (int)$category->getSubcatCount();
554 $fileCount = (int)$category->getFileCount();
555 $pagesCount = $allCount - $subcatCount - $fileCount;
557 $pageInfo[
'category-info'] = [
559 $this->
msg(
'pageinfo-category-total' ),
560 $lang->formatNum( $allCount )
563 $this->
msg(
'pageinfo-category-pages' ),
564 $lang->formatNum( $pagesCount )
567 $this->
msg(
'pageinfo-category-subcats' ),
568 $lang->formatNum( $subcatCount )
571 $this->
msg(
'pageinfo-category-files' ),
572 $lang->formatNum( $fileCount )
579 $fileObj = $this->repoGroup->findFile(
$title );
580 if ( $fileObj !==
false ) {
582 $output = Wikimedia\base_convert( $fileObj->getSha1(), 36, 16, 40 );
583 $pageInfo[
'header-basic'][] = [
584 $this->
msg(
'pageinfo-file-hash' ),
591 $pageInfo[
'header-restrictions'] = [];
594 if (
$title->isCascadeProtected() ) {
596 $sources =
$title->getCascadeProtectionSources()[0];
598 foreach ( $sources as $sourceTitle ) {
599 $cascadingFrom .= Html::rawElement(
606 $cascadingFrom = Html::rawElement(
'ul', [], $cascadingFrom );
607 $pageInfo[
'header-restrictions'][] = [
608 $this->
msg(
'pageinfo-protect-cascading-from' ),
614 if (
$title->areRestrictionsCascading() ) {
615 $pageInfo[
'header-restrictions'][] = [
616 $this->
msg(
'pageinfo-protect-cascading' ),
617 $this->
msg(
'pageinfo-protect-cascading-yes' )
622 foreach (
$title->getRestrictionTypes() as $restrictionType ) {
623 $protections =
$title->getRestrictions( $restrictionType );
625 switch ( count( $protections ) ) {
628 if ( $message ===
null ) {
630 $message = $this->
msg(
'protect-default' )->escaped();
636 $message = $this->
msg(
'protect-level-' . $protections[0] );
637 if ( !$message->isDisabled() ) {
638 $message = $message->escaped();
645 $message = $this->
msg(
"protect-fallback",
$lang->commaList( $protections ) )->parse();
648 $expiry =
$title->getRestrictionExpiry( $restrictionType );
649 $formattedexpiry = $this->
msg(
651 $lang->formatExpiry( $expiry,
true,
'infinity', $user )
653 $message .= $this->
msg(
'word-separator' )->escaped() . $formattedexpiry;
657 $pageInfo[
'header-restrictions'][] = [
658 $this->
msg(
"restriction-$restrictionType" ), $message
662 $pageInfo[
'header-restrictions'][] = [
666 $this->
msg(
'pageinfo-view-protect-log' )->text(),
668 [
'type' =>
'protect',
'page' =>
$title->getPrefixedText() ]
677 $pageInfo[
'header-edits'] = [];
679 $firstRev = $this->revisionLookup->getFirstRevision( $this->
getTitle() );
680 $lastRev = $this->
getWikiPage()->getRevisionRecord();
681 $batch = $this->linkBatchFactory->newLinkBatch();
683 $firstRevUser = $firstRev->getUser( RevisionRecord::FOR_THIS_USER, $user );
684 if ( $firstRevUser ) {
685 $batch->add(
NS_USER, $firstRevUser->getName() );
691 $lastRevUser = $lastRev->getUser( RevisionRecord::FOR_THIS_USER, $user );
692 if ( $lastRevUser ) {
693 $batch->add(
NS_USER, $lastRevUser->getName() );
702 $pageInfo[
'header-edits'][] = [
703 $this->
msg(
'pageinfo-firstuser' ),
708 $pageInfo[
'header-edits'][] = [
709 $this->
msg(
'pageinfo-firsttime' ),
712 $lang->userTimeAndDate( $firstRev->getTimestamp(), $user ),
714 [
'oldid' => $firstRev->getId() ]
721 $pageInfo[
'header-edits'][] = [
722 $this->
msg(
'pageinfo-lastuser' ),
727 $pageInfo[
'header-edits'][] = [
728 $this->
msg(
'pageinfo-lasttime' ),
731 $lang->userTimeAndDate( $this->getWikiPage()->getTimestamp(), $user ),
733 [
'oldid' => $this->getWikiPage()->getLatest() ]
739 $pageInfo[
'header-edits'][] = [
740 $this->
msg(
'pageinfo-edits' ),
741 $lang->formatNum( $pageCounts[
'edits'] )
745 if ( $pageCounts[
'authors'] > 0 ) {
746 $pageInfo[
'header-edits'][] = [
747 $this->
msg(
'pageinfo-authors' ),
748 $lang->formatNum( $pageCounts[
'authors'] )
753 $pageInfo[
'header-edits'][] = [
755 'pageinfo-recent-edits',
756 $lang->formatDuration( $config->get(
'RCMaxAge' ) )
758 $lang->formatNum( $pageCounts[
'recent_edits'] )
762 $pageInfo[
'header-edits'][] = [
763 $this->
msg(
'pageinfo-recent-authors' ),
764 $lang->formatNum( $pageCounts[
'recent_authors'] )
768 $magicWords = $this->magicWordFactory->getDoubleUnderscoreArray();
774 $localizedWords = $this->contentLanguage->getMagicWords();
777 foreach ( $pageProperties as $property => $value ) {
778 if ( in_array( $property, $wordIDs ) ) {
779 $listItems[] = Html::element(
'li', [], $localizedWords[$property][1] );
783 $localizedList = Html::rawElement(
'ul', [], implode(
'', $listItems ) );
784 $hiddenCategories = $this->
getWikiPage()->getHiddenCategories();
787 count( $listItems ) > 0 ||
788 count( $hiddenCategories ) > 0 ||
789 $pageCounts[
'transclusion'][
'from'] > 0 ||
790 $pageCounts[
'transclusion'][
'to'] > 0
792 $options = [
'LIMIT' => $config->get(
'PageInfoTransclusionLimit' ) ];
793 $transcludedTemplates =
$title->getTemplateLinksFrom( $options );
794 if ( $config->get(
'MiserMode' ) ) {
795 $transcludedTargets = [];
797 $transcludedTargets =
$title->getTemplateLinksTo( $options );
801 $pageInfo[
'header-properties'] = [];
804 if ( count( $listItems ) > 0 ) {
805 $pageInfo[
'header-properties'][] = [
806 $this->
msg(
'pageinfo-magic-words' )->numParams( count( $listItems ) ),
812 if ( count( $hiddenCategories ) > 0 ) {
813 $pageInfo[
'header-properties'][] = [
814 $this->
msg(
'pageinfo-hidden-categories' )
815 ->numParams( count( $hiddenCategories ) ),
821 if ( $pageCounts[
'transclusion'][
'from'] > 0 ) {
822 if ( $pageCounts[
'transclusion'][
'from'] > count( $transcludedTemplates ) ) {
823 $more = $this->
msg(
'morenotlisted' )->escaped();
833 $pageInfo[
'header-properties'][] = [
834 $this->
msg(
'pageinfo-templates' )
835 ->numParams( $pageCounts[
'transclusion'][
'from'] ),
836 $templateListFormatter->format( $transcludedTemplates,
false, $more )
840 if ( !$config->get(
'MiserMode' ) && $pageCounts[
'transclusion'][
'to'] > 0 ) {
841 if ( $pageCounts[
'transclusion'][
'to'] > count( $transcludedTargets ) ) {
844 $this->
msg(
'moredotdotdot' )->text(),
846 [
'hidelinks' => 1,
'hideredirs' => 1 ]
857 $pageInfo[
'header-properties'][] = [
858 $this->
msg(
'pageinfo-transclusions' )
859 ->numParams( $pageCounts[
'transclusion'][
'to'] ),
860 $templateListFormatter->format( $transcludedTargets,
false, $more )
913 $page = $this->getWikiPage();
915 $config = $this->context->getConfig();
916 $cache = $this->wanObjectCache;
918 return $cache->getWithSetCallback(
919 self::getCacheKey(
$cache, $page->getTitle(), $page->getLatest() ),
920 WANObjectCache::TTL_WEEK,
921 function ( $oldValue, &$ttl, &$setOpts ) use ( $page, $config, $fname ) {
924 $title = $page->getTitle();
925 $id =
$title->getArticleID();
928 $dbrWatchlist = $this->loadBalancer->getConnectionRef(
932 $setOpts += Database::getCacheSetOptions(
$dbr, $dbrWatchlist );
935 $tables = [
'revision' ];
936 $field =
'rev_actor';
937 $pageField =
'rev_page';
938 $tsField =
'rev_timestamp';
940 $tables = [
'revision_actor_temp' ];
941 $field =
'revactor_actor';
942 $pageField =
'revactor_page';
943 $tsField =
'revactor_timestamp';
947 $watchedItemStore = $this->watchedItemStore;
952 if ( $config->get(
'ShowUpdatedMarker' ) ) {
953 $updated = (int)
wfTimestamp( TS_UNIX, $page->getTimestamp() );
956 $updated - $config->get(
'WatchersMaxAge' )
961 $edits = (int)
$dbr->selectField(
964 [
'rev_page' => $id ],
967 $result[
'edits'] = $edits;
970 if ( $config->get(
'MiserMode' ) ) {
971 $result[
'authors'] = 0;
973 $result[
'authors'] = (int)
$dbr->selectField(
975 "COUNT(DISTINCT $field)",
976 [ $pageField => $id ],
984 $threshold =
$dbr->timestamp( time() - $config->get(
'RCMaxAge' ) );
987 $edits = (int)
$dbr->selectField(
992 "rev_timestamp >= " .
$dbr->addQuotes( $threshold )
996 $result[
'recent_edits'] = $edits;
999 $result[
'recent_authors'] = (int)
$dbr->selectField(
1001 "COUNT(DISTINCT $field)",
1004 "$tsField >= " .
$dbr->addQuotes( $threshold )
1012 if ( $this->namespaceInfo->hasSubpages(
$title->getNamespace() ) ) {
1013 $conds = [
'page_namespace' =>
$title->getNamespace() ];
1014 $conds[] =
'page_title ' .
1018 $conds[
'page_is_redirect'] = 1;
1019 $result[
'subpages'][
'redirects'] = (int)
$dbr->selectField(
1027 $conds[
'page_is_redirect'] = 0;
1028 $result[
'subpages'][
'nonredirects'] = (int)
$dbr->selectField(
1036 $result[
'subpages'][
'total'] = $result[
'subpages'][
'redirects']
1037 + $result[
'subpages'][
'nonredirects'];
1041 if ( $config->get(
'MiserMode' ) ) {
1042 $result[
'transclusion'][
'to'] = 0;
1044 $result[
'transclusion'][
'to'] = (int)
$dbr->selectField(
1048 'tl_namespace' =>
$title->getNamespace(),
1049 'tl_title' =>
$title->getDBkey()
1055 $result[
'transclusion'][
'from'] = (int)
$dbr->selectField(
1058 [
'tl_from' =>
$title->getArticleID() ],