219 $services = MediaWikiServices::getInstance();
224 $id =
$title->getArticleID();
225 $config = $this->context->getConfig();
226 $linkRenderer = $services->getLinkRenderer();
228 $pageCounts = $this->
pageCounts( $this->page );
231 $pageProperties = $props[$id] ?? [];
235 $pageInfo[
'header-basic'] = [];
238 $displayTitle = $pageProperties[
'displaytitle'] ??
$title->getPrefixedText();
240 $pageInfo[
'header-basic'][] = [
241 $this->
msg(
'pageinfo-display-title' ), $displayTitle
245 $redirectTarget = $this->page->getRedirectTarget();
246 if ( $redirectTarget !==
null ) {
247 $pageInfo[
'header-basic'][] = [
248 $this->
msg(
'pageinfo-redirectsto' ),
249 $linkRenderer->makeLink( $redirectTarget ) .
250 $this->
msg(
'word-separator' )->escaped() .
251 $this->
msg(
'parentheses' )->rawParams( $linkRenderer->makeLink(
253 $this->msg(
'pageinfo-redirectsto-info' )->text(),
255 [
'action' =>
'info' ]
261 $sortKey = $pageProperties[
'defaultsort'] ??
$title->getCategorySortkey();
263 $sortKey = htmlspecialchars( $sortKey );
264 $pageInfo[
'header-basic'][] = [ $this->
msg(
'pageinfo-default-sort' ), $sortKey ];
267 $pageInfo[
'header-basic'][] = [
268 $this->
msg(
'pageinfo-length' ),
$lang->formatNum(
$title->getLength() )
272 $pageNamespace =
$title->getNsText();
273 if ( $pageNamespace ) {
274 $pageInfo[
'header-basic'][] = [ $this->
msg(
'pageinfo-namespace' ), $pageNamespace ];
278 $pageInfo[
'header-basic'][] = [ $this->
msg(
'pageinfo-article-id' ), $id ];
281 $pageLang =
$title->getPageLanguage()->getCode();
283 $pageLangHtml = $pageLang .
' - ' .
284 Language::fetchLanguageName( $pageLang,
$lang->getCode() );
286 $permissionManager = $services->getPermissionManager();
287 if ( $config->get(
'PageLanguageUseDB' )
288 && $permissionManager->userCan(
'pagelang', $user,
$title )
290 $pageLangHtml .=
' ' . $this->
msg(
'parentheses' )->rawParams( $linkRenderer->makeLink(
291 SpecialPage::getTitleValueFor(
'PageLanguage',
$title->getPrefixedText() ),
292 $this->msg(
'pageinfo-language-change' )->text()
296 $pageInfo[
'header-basic'][] = [
297 $this->
msg(
'pageinfo-language' )->escaped(),
302 $modelHtml = htmlspecialchars( ContentHandler::getLocalizedName(
$title->getContentModel() ) );
304 if ( $config->get(
'ContentHandlerUseDB' )
305 && $permissionManager->userCan(
'editcontentmodel', $user,
$title )
307 $modelHtml .=
' ' . $this->
msg(
'parentheses' )->rawParams( $linkRenderer->makeLink(
308 SpecialPage::getTitleValueFor(
'ChangeContentModel',
$title->getPrefixedText() ),
309 $this->msg(
'pageinfo-content-model-change' )->text()
313 $pageInfo[
'header-basic'][] = [
314 $this->
msg(
'pageinfo-content-model' ),
319 $pageUser = User::newFromName(
$title->getRootText() );
320 if ( $pageUser && $pageUser->getId() && !$pageUser->isHidden() ) {
321 $pageInfo[
'header-basic'][] = [
322 $this->
msg(
'pageinfo-user-id' ),
330 if ( isset( $pageProperties[
'noindex'] ) ) {
331 $pOutput->setIndexPolicy(
'noindex' );
333 if ( isset( $pageProperties[
'index'] ) ) {
334 $pOutput->setIndexPolicy(
'index' );
338 $policy = $this->page->getRobotPolicy(
'view', $pOutput );
339 $pageInfo[
'header-basic'][] = [
341 $this->
msg(
'pageinfo-robot-policy' ),
342 $this->
msg(
"pageinfo-robot-${policy['index']}" )
345 $unwatchedPageThreshold = $config->get(
'UnwatchedPageThreshold' );
346 if ( $permissionManager->userHasRight( $user,
'unwatchedpages' ) ||
347 ( $unwatchedPageThreshold !==
false &&
348 $pageCounts[
'watchers'] >= $unwatchedPageThreshold )
351 $pageInfo[
'header-basic'][] = [
352 $this->
msg(
'pageinfo-watchers' ),
353 $lang->formatNum( $pageCounts[
'watchers'] )
356 $config->get(
'ShowUpdatedMarker' ) &&
357 isset( $pageCounts[
'visitingWatchers'] )
359 $minToDisclose = $config->get(
'UnwatchedPageSecret' );
360 if ( $pageCounts[
'visitingWatchers'] > $minToDisclose ||
361 $permissionManager->userHasRight( $user,
'unwatchedpages' ) ) {
362 $pageInfo[
'header-basic'][] = [
363 $this->
msg(
'pageinfo-visiting-watchers' ),
364 $lang->formatNum( $pageCounts[
'visitingWatchers'] )
367 $pageInfo[
'header-basic'][] = [
368 $this->
msg(
'pageinfo-visiting-watchers' ),
369 $this->
msg(
'pageinfo-few-visiting-watchers' )
373 } elseif ( $unwatchedPageThreshold !==
false ) {
374 $pageInfo[
'header-basic'][] = [
375 $this->
msg(
'pageinfo-watchers' ),
376 $this->
msg(
'pageinfo-few-watchers' )->numParams( $unwatchedPageThreshold )
381 $whatLinksHere = SpecialPage::getTitleFor(
'Whatlinkshere',
$title->getPrefixedText() );
382 $pageInfo[
'header-basic'][] = [
383 $linkRenderer->makeLink(
385 $this->
msg(
'pageinfo-redirects-name' )->text(),
393 $this->
msg(
'pageinfo-redirects-value' )
394 ->numParams( count(
$title->getRedirectsHere() ) )
398 if ( $this->page->isCountable() ) {
399 $pageInfo[
'header-basic'][] = [
400 $this->
msg(
'pageinfo-contentpage' ),
401 $this->
msg(
'pageinfo-contentpage-yes' )
406 if ( $services->getNamespaceInfo()->hasSubpages(
$title->getNamespace() ) ) {
407 $prefixIndex = SpecialPage::getTitleFor(
408 'Prefixindex',
$title->getPrefixedText() .
'/' );
409 $pageInfo[
'header-basic'][] = [
410 $linkRenderer->makeLink(
412 $this->
msg(
'pageinfo-subpages-name' )->text()
414 $this->
msg(
'pageinfo-subpages-value' )
416 $pageCounts[
'subpages'][
'total'],
417 $pageCounts[
'subpages'][
'redirects'],
418 $pageCounts[
'subpages'][
'nonredirects'] )
423 $category = Category::newFromTitle(
$title );
427 $allCount = (int)$category->getPageCount();
428 $subcatCount = (int)$category->getSubcatCount();
429 $fileCount = (int)$category->getFileCount();
430 $pagesCount = $allCount - $subcatCount - $fileCount;
432 $pageInfo[
'category-info'] = [
434 $this->
msg(
'pageinfo-category-total' ),
435 $lang->formatNum( $allCount )
438 $this->
msg(
'pageinfo-category-pages' ),
439 $lang->formatNum( $pagesCount )
442 $this->
msg(
'pageinfo-category-subcats' ),
443 $lang->formatNum( $subcatCount )
446 $this->
msg(
'pageinfo-category-files' ),
447 $lang->formatNum( $fileCount )
454 $fileObj = $services->getRepoGroup()->findFile(
$title );
455 if ( $fileObj !==
false ) {
457 $output = Wikimedia\base_convert( $fileObj->getSha1(), 36, 16, 40 );
458 $pageInfo[
'header-basic'][] = [
459 $this->
msg(
'pageinfo-file-hash' ),
466 $pageInfo[
'header-restrictions'] = [];
469 if (
$title->isCascadeProtected() ) {
471 $sources =
$title->getCascadeProtectionSources()[0];
473 foreach ( $sources as $sourceTitle ) {
474 $cascadingFrom .= Html::rawElement(
475 'li', [], $linkRenderer->makeKnownLink( $sourceTitle ) );
478 $cascadingFrom = Html::rawElement(
'ul', [], $cascadingFrom );
479 $pageInfo[
'header-restrictions'][] = [
480 $this->
msg(
'pageinfo-protect-cascading-from' ),
486 if (
$title->areRestrictionsCascading() ) {
487 $pageInfo[
'header-restrictions'][] = [
488 $this->
msg(
'pageinfo-protect-cascading' ),
489 $this->
msg(
'pageinfo-protect-cascading-yes' )
494 foreach (
$title->getRestrictionTypes() as $restrictionType ) {
495 $protectionLevel = implode(
', ',
$title->getRestrictions( $restrictionType ) );
497 if ( $protectionLevel ==
'' ) {
499 $message = $this->
msg(
'protect-default' )->escaped();
503 $message = $this->
msg(
"protect-level-$protectionLevel" );
504 if ( $message->isDisabled() ) {
506 $message = $this->
msg(
"protect-fallback", $protectionLevel )->parse();
508 $message = $message->escaped();
511 $expiry =
$title->getRestrictionExpiry( $restrictionType );
512 $formattedexpiry = $this->
msg(
'parentheses',
513 $lang->formatExpiry( $expiry ) )->escaped();
514 $message .= $this->
msg(
'word-separator' )->escaped() . $formattedexpiry;
518 $pageInfo[
'header-restrictions'][] = [
519 $this->
msg(
"restriction-$restrictionType" ), $message
522 $protectLog = SpecialPage::getTitleFor(
'Log' );
523 $pageInfo[
'header-restrictions'][] = [
525 $linkRenderer->makeKnownLink(
527 $this->
msg(
'pageinfo-view-protect-log' )->text(),
529 [
'type' =>
'protect',
'page' =>
$title->getPrefixedText() ]
533 if ( !$this->page->exists() ) {
538 $pageInfo[
'header-edits'] = [];
540 $firstRev = $this->page->getOldestRevision();
541 $lastRev = $this->page->getRevision();
545 $firstRevUser = $firstRev->getUserText( RevisionRecord::FOR_THIS_USER );
546 if ( $firstRevUser !==
'' ) {
547 $firstRevUserTitle = Title::makeTitle(
NS_USER, $firstRevUser );
548 $batch->addObj( $firstRevUserTitle );
549 $batch->addObj( $firstRevUserTitle->getTalkPage() );
554 $lastRevUser = $lastRev->getUserText( RevisionRecord::FOR_THIS_USER );
555 if ( $lastRevUser !==
'' ) {
556 $lastRevUserTitle = Title::makeTitle(
NS_USER, $lastRevUser );
557 $batch->addObj( $lastRevUserTitle );
558 $batch->addObj( $lastRevUserTitle->getTalkPage() );
566 $pageInfo[
'header-edits'][] = [
567 $this->
msg(
'pageinfo-firstuser' ),
572 $pageInfo[
'header-edits'][] = [
573 $this->
msg(
'pageinfo-firsttime' ),
574 $linkRenderer->makeKnownLink(
576 $lang->userTimeAndDate( $firstRev->getTimestamp(), $user ),
578 [
'oldid' => $firstRev->getId() ]
585 $pageInfo[
'header-edits'][] = [
586 $this->
msg(
'pageinfo-lastuser' ),
591 $pageInfo[
'header-edits'][] = [
592 $this->
msg(
'pageinfo-lasttime' ),
593 $linkRenderer->makeKnownLink(
595 $lang->userTimeAndDate( $this->page->getTimestamp(), $user ),
597 [
'oldid' => $this->page->getLatest() ]
603 $pageInfo[
'header-edits'][] = [
604 $this->
msg(
'pageinfo-edits' ),
$lang->formatNum( $pageCounts[
'edits'] )
608 if ( $pageCounts[
'authors'] > 0 ) {
609 $pageInfo[
'header-edits'][] = [
610 $this->
msg(
'pageinfo-authors' ),
$lang->formatNum( $pageCounts[
'authors'] )
615 $pageInfo[
'header-edits'][] = [
616 $this->
msg(
'pageinfo-recent-edits',
617 $lang->formatDuration( $config->get(
'RCMaxAge' ) ) ),
618 $lang->formatNum( $pageCounts[
'recent_edits'] )
622 $pageInfo[
'header-edits'][] = [
623 $this->
msg(
'pageinfo-recent-authors' ),
624 $lang->formatNum( $pageCounts[
'recent_authors'] )
628 $magicWords = $services->getMagicWordFactory()->getDoubleUnderscoreArray();
634 $localizedWords = $services->getContentLanguage()->getMagicWords();
637 foreach ( $pageProperties as $property => $value ) {
638 if ( in_array( $property, $wordIDs ) ) {
639 $listItems[] = Html::element(
'li', [], $localizedWords[$property][1] );
643 $localizedList = Html::rawElement(
'ul', [], implode(
'', $listItems ) );
644 $hiddenCategories = $this->page->getHiddenCategories();
647 count( $listItems ) > 0 ||
648 count( $hiddenCategories ) > 0 ||
649 $pageCounts[
'transclusion'][
'from'] > 0 ||
650 $pageCounts[
'transclusion'][
'to'] > 0
652 $options = [
'LIMIT' => $config->get(
'PageInfoTransclusionLimit' ) ];
653 $transcludedTemplates =
$title->getTemplateLinksFrom( $options );
654 if ( $config->get(
'MiserMode' ) ) {
655 $transcludedTargets = [];
657 $transcludedTargets =
$title->getTemplateLinksTo( $options );
661 $pageInfo[
'header-properties'] = [];
664 if ( count( $listItems ) > 0 ) {
665 $pageInfo[
'header-properties'][] = [
666 $this->
msg(
'pageinfo-magic-words' )->numParams( count( $listItems ) ),
672 if ( count( $hiddenCategories ) > 0 ) {
673 $pageInfo[
'header-properties'][] = [
674 $this->
msg(
'pageinfo-hidden-categories' )
675 ->numParams( count( $hiddenCategories ) ),
681 if ( $pageCounts[
'transclusion'][
'from'] > 0 ) {
682 if ( $pageCounts[
'transclusion'][
'from'] > count( $transcludedTemplates ) ) {
683 $more = $this->
msg(
'morenotlisted' )->escaped();
693 $pageInfo[
'header-properties'][] = [
694 $this->
msg(
'pageinfo-templates' )
695 ->numParams( $pageCounts[
'transclusion'][
'from'] ),
696 $templateListFormatter->format( $transcludedTemplates,
false, $more )
700 if ( !$config->get(
'MiserMode' ) && $pageCounts[
'transclusion'][
'to'] > 0 ) {
701 if ( $pageCounts[
'transclusion'][
'to'] > count( $transcludedTargets ) ) {
702 $more = $linkRenderer->makeLink(
704 $this->
msg(
'moredotdotdot' )->text(),
706 [
'hidelinks' => 1,
'hideredirs' => 1 ]
717 $pageInfo[
'header-properties'][] = [
718 $this->
msg(
'pageinfo-transclusions' )
719 ->numParams( $pageCounts[
'transclusion'][
'to'] ),
720 $templateListFormatter->format( $transcludedTargets,
false, $more )