MediaWiki REL1_35
InfoAction.php
Go to the documentation of this file.
1<?php
28
35 private const VERSION = 1;
36
42 public function getName() {
43 return 'info';
44 }
45
51 public function requiresUnblock() {
52 return false;
53 }
54
60 public function requiresWrite() {
61 return false;
62 }
63
71 public static function invalidateCache( Title $title, $revid = null ) {
72 $services = MediaWikiServices::getInstance();
73 if ( !$revid ) {
74 $revision = $services->getRevisionLookup()
75 ->getRevisionByTitle( $title, 0, IDBAccessObject::READ_LATEST );
76 $revid = $revision ? $revision->getId() : null;
77 }
78 if ( $revid !== null ) {
79 $cache = $services->getMainWANObjectCache();
80 $key = self::getCacheKey( $cache, $title, $revid );
81 $cache->delete( $key );
82 }
83 }
84
90 public function onView() {
91 $content = '';
92
93 // Validate revision
94 $oldid = $this->getArticle()->getOldID();
95 if ( $oldid ) {
96 $revRecord = $this->getArticle()->fetchRevisionRecord();
97
98 // Revision is missing
99 if ( $revRecord === null ) {
100 return $this->msg( 'missing-revision', $oldid )->parse();
101 }
102
103 // Revision is not current
104 if ( !$revRecord->isCurrent() ) {
105 return $this->msg( 'pageinfo-not-current' )->plain();
106 }
107 }
108
109 // "Help" button
110 $this->addHelpLink( 'Page information' );
111
112 // Page header
113 if ( !$this->msg( 'pageinfo-header' )->isDisabled() ) {
114 $content .= $this->msg( 'pageinfo-header' )->parse();
115 }
116
117 // Hide "This page is a member of # hidden categories" explanation
118 $content .= Html::element( 'style', [],
119 '.mw-hiddenCategoriesExplanation { display: none; }' ) . "\n";
120
121 // Hide "Templates used on this page" explanation
122 $content .= Html::element( 'style', [],
123 '.mw-templatesUsedExplanation { display: none; }' ) . "\n";
124
125 // Get page information
126 $pageInfo = $this->pageInfo();
127
128 // Allow extensions to add additional information
129 $this->getHookRunner()->onInfoAction( $this->getContext(), $pageInfo );
130
131 // Render page information
132 foreach ( $pageInfo as $header => $infoTable ) {
133 // Messages:
134 // pageinfo-header-basic, pageinfo-header-edits, pageinfo-header-restrictions,
135 // pageinfo-header-properties, pageinfo-category-info
136 $content .= $this->makeHeader(
137 $this->msg( "pageinfo-${header}" )->text(),
138 "mw-pageinfo-${header}"
139 ) . "\n";
140 $table = "\n";
141 $below = "";
142 foreach ( $infoTable as $infoRow ) {
143 if ( $infoRow[0] == "below" ) {
144 $below = $infoRow[1] . "\n";
145 continue;
146 }
147 $name = ( $infoRow[0] instanceof Message ) ? $infoRow[0]->escaped() : $infoRow[0];
148 $value = ( $infoRow[1] instanceof Message ) ? $infoRow[1]->escaped() : $infoRow[1];
149 $id = ( $infoRow[0] instanceof Message ) ? $infoRow[0]->getKey() : null;
150 $table = $this->addRow( $table, $name, $value, $id ) . "\n";
151 }
152 $content = $this->addTable( $content, $table ) . "\n" . $below;
153 }
154
155 // Page footer
156 if ( !$this->msg( 'pageinfo-footer' )->isDisabled() ) {
157 $content .= $this->msg( 'pageinfo-footer' )->parse();
158 }
159
160 return $content;
161 }
162
170 protected function makeHeader( $header, $canonicalId ) {
171 $spanAttribs = [ 'class' => 'mw-headline', 'id' => Sanitizer::escapeIdForAttribute( $header ) ];
172 $h2Attribs = [ 'id' => Sanitizer::escapeIdForAttribute( $canonicalId ) ];
173
174 return Html::rawElement( 'h2', $h2Attribs, Html::element( 'span', $spanAttribs, $header ) );
175 }
176
186 protected function addRow( $table, $name, $value, $id ) {
187 return $table .
188 Html::rawElement(
189 'tr',
190 $id === null ? [] : [ 'id' => 'mw-' . $id ],
191 Html::rawElement( 'td', [ 'style' => 'vertical-align: top;' ], $name ) .
192 Html::rawElement( 'td', [], $value )
193 );
194 }
195
203 protected function addTable( $content, $table ) {
204 return $content . Html::rawElement( 'table', [ 'class' => 'wikitable mw-page-info' ],
205 $table );
206 }
207
220 protected function pageInfo() {
221 $services = MediaWikiServices::getInstance();
222
223 $user = $this->getUser();
224 $lang = $this->getLanguage();
225 $title = $this->getTitle();
226 $id = $title->getArticleID();
227 $config = $this->context->getConfig();
228 $linkRenderer = $services->getLinkRenderer();
229
230 $pageCounts = $this->pageCounts();
231
232 $props = PageProps::getInstance()->getAllProperties( $title );
233 $pageProperties = $props[$id] ?? [];
234
235 // Basic information
236 $pageInfo = [];
237 $pageInfo['header-basic'] = [];
238
239 // Display title
240 $displayTitle = $pageProperties['displaytitle'] ?? $title->getPrefixedText();
241
242 $pageInfo['header-basic'][] = [
243 $this->msg( 'pageinfo-display-title' ), $displayTitle
244 ];
245
246 // Is it a redirect? If so, where to?
247 $redirectTarget = $this->getWikiPage()->getRedirectTarget();
248 if ( $redirectTarget !== null ) {
249 $pageInfo['header-basic'][] = [
250 $this->msg( 'pageinfo-redirectsto' ),
251 $linkRenderer->makeLink( $redirectTarget ) .
252 $this->msg( 'word-separator' )->escaped() .
253 $this->msg( 'parentheses' )->rawParams( $linkRenderer->makeLink(
254 $redirectTarget,
255 $this->msg( 'pageinfo-redirectsto-info' )->text(),
256 [],
257 [ 'action' => 'info' ]
258 ) )->escaped()
259 ];
260 }
261
262 // Default sort key
263 $sortKey = $pageProperties['defaultsort'] ?? $title->getCategorySortkey();
264
265 $sortKey = htmlspecialchars( $sortKey );
266 $pageInfo['header-basic'][] = [ $this->msg( 'pageinfo-default-sort' ), $sortKey ];
267
268 // Page length (in bytes)
269 $pageInfo['header-basic'][] = [
270 $this->msg( 'pageinfo-length' ), $lang->formatNum( $title->getLength() )
271 ];
272
273 // Page namespace
274 $pageNamespace = $title->getNsText();
275 if ( $pageNamespace ) {
276 $pageInfo['header-basic'][] = [ $this->msg( 'pageinfo-namespace' ), $pageNamespace ];
277 }
278
279 // Page ID (number not localised, as it's a database ID)
280 $pageInfo['header-basic'][] = [ $this->msg( 'pageinfo-article-id' ), $id ];
281
282 // Language in which the page content is (supposed to be) written
283 $pageLang = $title->getPageLanguage()->getCode();
284
285 $pageLangHtml = $pageLang . ' - ' .
286 $services->getLanguageNameUtils()->getLanguageName( $pageLang, $lang->getCode() );
287 // Link to Special:PageLanguage with pre-filled page title if user has permissions
288 $permissionManager = $services->getPermissionManager();
289 if ( $config->get( 'PageLanguageUseDB' )
290 && $permissionManager->userCan( 'pagelang', $user, $title )
291 ) {
292 $pageLangHtml .= ' ' . $this->msg( 'parentheses' )->rawParams( $linkRenderer->makeLink(
293 SpecialPage::getTitleValueFor( 'PageLanguage', $title->getPrefixedText() ),
294 $this->msg( 'pageinfo-language-change' )->text()
295 ) )->escaped();
296 }
297
298 $pageInfo['header-basic'][] = [
299 $this->msg( 'pageinfo-language' )->escaped(),
300 $pageLangHtml
301 ];
302
303 // Content model of the page
304 $modelHtml = htmlspecialchars( ContentHandler::getLocalizedName( $title->getContentModel() ) );
305 // If the user can change it, add a link to Special:ChangeContentModel
306 if ( $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()
310 ) )->escaped();
311 }
312
313 $pageInfo['header-basic'][] = [
314 $this->msg( 'pageinfo-content-model' ),
315 $modelHtml
316 ];
317
318 if ( $title->inNamespace( NS_USER ) ) {
319 $pageUser = User::newFromName( $title->getRootText() );
320 if ( $pageUser && $pageUser->getId() && !$pageUser->isHidden() ) {
321 $pageInfo['header-basic'][] = [
322 $this->msg( 'pageinfo-user-id' ),
323 $pageUser->getId()
324 ];
325 }
326 }
327
328 // Search engine status
329 $pOutput = new ParserOutput();
330 if ( isset( $pageProperties['noindex'] ) ) {
331 $pOutput->setIndexPolicy( 'noindex' );
332 }
333 if ( isset( $pageProperties['index'] ) ) {
334 $pOutput->setIndexPolicy( 'index' );
335 }
336
337 // Use robot policy logic
338 $policy = $this->getArticle()->getRobotPolicy( 'view', $pOutput );
339 $pageInfo['header-basic'][] = [
340 // Messages: pageinfo-robot-index, pageinfo-robot-noindex
341 $this->msg( 'pageinfo-robot-policy' ),
342 $this->msg( "pageinfo-robot-{$policy['index']}" )
343 ];
344
345 $unwatchedPageThreshold = $config->get( 'UnwatchedPageThreshold' );
346 if ( $permissionManager->userHasRight( $user, 'unwatchedpages' ) ||
347 ( $unwatchedPageThreshold !== false &&
348 $pageCounts['watchers'] >= $unwatchedPageThreshold )
349 ) {
350 // Number of page watchers
351 $pageInfo['header-basic'][] = [
352 $this->msg( 'pageinfo-watchers' ),
353 $lang->formatNum( $pageCounts['watchers'] )
354 ];
355 if (
356 $config->get( 'ShowUpdatedMarker' ) &&
357 isset( $pageCounts['visitingWatchers'] )
358 ) {
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'] )
365 ];
366 } else {
367 $pageInfo['header-basic'][] = [
368 $this->msg( 'pageinfo-visiting-watchers' ),
369 $this->msg( 'pageinfo-few-visiting-watchers' )
370 ];
371 }
372 }
373 } elseif ( $unwatchedPageThreshold !== false ) {
374 $pageInfo['header-basic'][] = [
375 $this->msg( 'pageinfo-watchers' ),
376 $this->msg( 'pageinfo-few-watchers' )->numParams( $unwatchedPageThreshold )
377 ];
378 }
379
380 // Redirects to this page
381 $whatLinksHere = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedText() );
382 $pageInfo['header-basic'][] = [
383 $linkRenderer->makeLink(
384 $whatLinksHere,
385 $this->msg( 'pageinfo-redirects-name' )->text(),
386 [],
387 [
388 'hidelinks' => 1,
389 'hidetrans' => 1,
390 'hideimages' => $title->getNamespace() == NS_FILE
391 ]
392 ),
393 $this->msg( 'pageinfo-redirects-value' )
394 ->numParams( count( $title->getRedirectsHere() ) )
395 ];
396
397 // Is it counted as a content page?
398 if ( $this->getWikiPage()->isCountable() ) {
399 $pageInfo['header-basic'][] = [
400 $this->msg( 'pageinfo-contentpage' ),
401 $this->msg( 'pageinfo-contentpage-yes' )
402 ];
403 }
404
405 // Subpages of this page, if subpages are enabled for the current NS
406 if ( $services->getNamespaceInfo()->hasSubpages( $title->getNamespace() ) ) {
407 $prefixIndex = SpecialPage::getTitleFor(
408 'Prefixindex', $title->getPrefixedText() . '/' );
409 $pageInfo['header-basic'][] = [
410 $linkRenderer->makeLink(
411 $prefixIndex,
412 $this->msg( 'pageinfo-subpages-name' )->text()
413 ),
414 $this->msg( 'pageinfo-subpages-value' )
415 ->numParams(
416 $pageCounts['subpages']['total'],
417 $pageCounts['subpages']['redirects'],
418 $pageCounts['subpages']['nonredirects'] )
419 ];
420 }
421
422 if ( $title->inNamespace( NS_CATEGORY ) ) {
423 $category = Category::newFromTitle( $title );
424
425 // $allCount is the total number of cat members,
426 // not the count of how many members are normal pages.
427 $allCount = (int)$category->getPageCount();
428 $subcatCount = (int)$category->getSubcatCount();
429 $fileCount = (int)$category->getFileCount();
430 $pagesCount = $allCount - $subcatCount - $fileCount;
431
432 $pageInfo['category-info'] = [
433 [
434 $this->msg( 'pageinfo-category-total' ),
435 $lang->formatNum( $allCount )
436 ],
437 [
438 $this->msg( 'pageinfo-category-pages' ),
439 $lang->formatNum( $pagesCount )
440 ],
441 [
442 $this->msg( 'pageinfo-category-subcats' ),
443 $lang->formatNum( $subcatCount )
444 ],
445 [
446 $this->msg( 'pageinfo-category-files' ),
447 $lang->formatNum( $fileCount )
448 ]
449 ];
450 }
451
452 // Display image SHA-1 value
453 if ( $title->inNamespace( NS_FILE ) ) {
454 $fileObj = $services->getRepoGroup()->findFile( $title );
455 if ( $fileObj !== false ) {
456 // Convert the base-36 sha1 value obtained from database to base-16
457 $output = Wikimedia\base_convert( $fileObj->getSha1(), 36, 16, 40 );
458 $pageInfo['header-basic'][] = [
459 $this->msg( 'pageinfo-file-hash' ),
460 $output
461 ];
462 }
463 }
464
465 // Page protection
466 $pageInfo['header-restrictions'] = [];
467
468 // Is this page affected by the cascading protection of something which includes it?
469 if ( $title->isCascadeProtected() ) {
470 $cascadingFrom = '';
471 $sources = $title->getCascadeProtectionSources()[0];
472
473 foreach ( $sources as $sourceTitle ) {
474 $cascadingFrom .= Html::rawElement(
475 'li', [], $linkRenderer->makeKnownLink( $sourceTitle ) );
476 }
477
478 $cascadingFrom = Html::rawElement( 'ul', [], $cascadingFrom );
479 $pageInfo['header-restrictions'][] = [
480 $this->msg( 'pageinfo-protect-cascading-from' ),
481 $cascadingFrom
482 ];
483 }
484
485 // Is out protection set to cascade to other pages?
486 if ( $title->areRestrictionsCascading() ) {
487 $pageInfo['header-restrictions'][] = [
488 $this->msg( 'pageinfo-protect-cascading' ),
489 $this->msg( 'pageinfo-protect-cascading-yes' )
490 ];
491 }
492
493 // Page protection
494 foreach ( $title->getRestrictionTypes() as $restrictionType ) {
495 $protectionLevel = implode( ', ', $title->getRestrictions( $restrictionType ) );
496
497 if ( $protectionLevel == '' ) {
498 // Allow all users
499 $message = $this->msg( 'protect-default' )->escaped();
500 } else {
501 // Administrators only
502 // Messages: protect-level-autoconfirmed, protect-level-sysop
503 $message = $this->msg( "protect-level-$protectionLevel" );
504 if ( $message->isDisabled() ) {
505 // Require "$1" permission
506 $message = $this->msg( "protect-fallback", $protectionLevel )->parse();
507 } else {
508 $message = $message->escaped();
509 }
510 }
511 $expiry = $title->getRestrictionExpiry( $restrictionType );
512 $formattedexpiry = $this->msg( 'parentheses',
513 $lang->formatExpiry( $expiry ) )->escaped();
514 $message .= $this->msg( 'word-separator' )->escaped() . $formattedexpiry;
515
516 // Messages: restriction-edit, restriction-move, restriction-create,
517 // restriction-upload
518 $pageInfo['header-restrictions'][] = [
519 $this->msg( "restriction-$restrictionType" ), $message
520 ];
521 }
522 $protectLog = SpecialPage::getTitleFor( 'Log' );
523 $pageInfo['header-restrictions'][] = [
524 'below',
525 $linkRenderer->makeKnownLink(
526 $protectLog,
527 $this->msg( 'pageinfo-view-protect-log' )->text(),
528 [],
529 [ 'type' => 'protect', 'page' => $title->getPrefixedText() ]
530 ),
531 ];
532
533 if ( !$this->getWikiPage()->exists() ) {
534 return $pageInfo;
535 }
536
537 // Edit history
538 $pageInfo['header-edits'] = [];
539
540 $firstRev = MediaWikiServices::getInstance()
541 ->getRevisionLookup()
542 ->getFirstRevision( $this->getTitle() );
543 $lastRev = $this->getWikiPage()->getRevisionRecord();
544 $batch = new LinkBatch;
545
546 if ( $firstRev ) {
547 $firstRevUser = $firstRev->getUser( RevisionRecord::FOR_THIS_USER, $user );
548 if ( $firstRevUser ) {
549 $batch->add( NS_USER, $firstRevUser->getName() );
550 $batch->add( NS_USER_TALK, $firstRevUser->getName() );
551 }
552 }
553
554 if ( $lastRev ) {
555 $lastRevUser = $lastRev->getUser( RevisionRecord::FOR_THIS_USER, $user );
556 if ( $lastRevUser ) {
557 $batch->add( NS_USER, $lastRevUser->getName() );
558 $batch->add( NS_USER_TALK, $lastRevUser->getName() );
559 }
560 }
561
562 $batch->execute();
563
564 if ( $firstRev ) {
565 // Page creator
566 $pageInfo['header-edits'][] = [
567 $this->msg( 'pageinfo-firstuser' ),
568 Linker::revUserTools( $firstRev )
569 ];
570
571 // Date of page creation
572 $pageInfo['header-edits'][] = [
573 $this->msg( 'pageinfo-firsttime' ),
574 $linkRenderer->makeKnownLink(
575 $title,
576 $lang->userTimeAndDate( $firstRev->getTimestamp(), $user ),
577 [],
578 [ 'oldid' => $firstRev->getId() ]
579 )
580 ];
581 }
582
583 if ( $lastRev ) {
584 // Latest editor
585 $pageInfo['header-edits'][] = [
586 $this->msg( 'pageinfo-lastuser' ),
587 Linker::revUserTools( $lastRev )
588 ];
589
590 // Date of latest edit
591 $pageInfo['header-edits'][] = [
592 $this->msg( 'pageinfo-lasttime' ),
593 $linkRenderer->makeKnownLink(
594 $title,
595 $lang->userTimeAndDate( $this->getWikiPage()->getTimestamp(), $user ),
596 [],
597 [ 'oldid' => $this->getWikiPage()->getLatest() ]
598 )
599 ];
600 }
601
602 // Total number of edits
603 $pageInfo['header-edits'][] = [
604 $this->msg( 'pageinfo-edits' ), $lang->formatNum( $pageCounts['edits'] )
605 ];
606
607 // Total number of distinct authors
608 if ( $pageCounts['authors'] > 0 ) {
609 $pageInfo['header-edits'][] = [
610 $this->msg( 'pageinfo-authors' ), $lang->formatNum( $pageCounts['authors'] )
611 ];
612 }
613
614 // Recent number of edits (within past 30 days)
615 $pageInfo['header-edits'][] = [
616 $this->msg( 'pageinfo-recent-edits',
617 $lang->formatDuration( $config->get( 'RCMaxAge' ) ) ),
618 $lang->formatNum( $pageCounts['recent_edits'] )
619 ];
620
621 // Recent number of distinct authors
622 $pageInfo['header-edits'][] = [
623 $this->msg( 'pageinfo-recent-authors' ),
624 $lang->formatNum( $pageCounts['recent_authors'] )
625 ];
626
627 // Array of MagicWord objects
628 $magicWords = $services->getMagicWordFactory()->getDoubleUnderscoreArray();
629
630 // Array of magic word IDs
631 $wordIDs = $magicWords->names;
632
633 // Array of IDs => localized magic words
634 $localizedWords = $services->getContentLanguage()->getMagicWords();
635
636 $listItems = [];
637 foreach ( $pageProperties as $property => $value ) {
638 if ( in_array( $property, $wordIDs ) ) {
639 $listItems[] = Html::element( 'li', [], $localizedWords[$property][1] );
640 }
641 }
642
643 $localizedList = Html::rawElement( 'ul', [], implode( '', $listItems ) );
644 $hiddenCategories = $this->getWikiPage()->getHiddenCategories();
645
646 if (
647 count( $listItems ) > 0 ||
648 count( $hiddenCategories ) > 0 ||
649 $pageCounts['transclusion']['from'] > 0 ||
650 $pageCounts['transclusion']['to'] > 0
651 ) {
652 $options = [ 'LIMIT' => $config->get( 'PageInfoTransclusionLimit' ) ];
653 $transcludedTemplates = $title->getTemplateLinksFrom( $options );
654 if ( $config->get( 'MiserMode' ) ) {
655 $transcludedTargets = [];
656 } else {
657 $transcludedTargets = $title->getTemplateLinksTo( $options );
658 }
659
660 // Page properties
661 $pageInfo['header-properties'] = [];
662
663 // Magic words
664 if ( count( $listItems ) > 0 ) {
665 $pageInfo['header-properties'][] = [
666 $this->msg( 'pageinfo-magic-words' )->numParams( count( $listItems ) ),
667 $localizedList
668 ];
669 }
670
671 // Hidden categories
672 if ( count( $hiddenCategories ) > 0 ) {
673 $pageInfo['header-properties'][] = [
674 $this->msg( 'pageinfo-hidden-categories' )
675 ->numParams( count( $hiddenCategories ) ),
676 Linker::formatHiddenCategories( $hiddenCategories )
677 ];
678 }
679
680 // Transcluded templates
681 if ( $pageCounts['transclusion']['from'] > 0 ) {
682 if ( $pageCounts['transclusion']['from'] > count( $transcludedTemplates ) ) {
683 $more = $this->msg( 'morenotlisted' )->escaped();
684 } else {
685 $more = null;
686 }
687
688 $templateListFormatter = new TemplatesOnThisPageFormatter(
689 $this->getContext(),
690 $linkRenderer
691 );
692
693 $pageInfo['header-properties'][] = [
694 $this->msg( 'pageinfo-templates' )
695 ->numParams( $pageCounts['transclusion']['from'] ),
696 $templateListFormatter->format( $transcludedTemplates, false, $more )
697 ];
698 }
699
700 if ( !$config->get( 'MiserMode' ) && $pageCounts['transclusion']['to'] > 0 ) {
701 if ( $pageCounts['transclusion']['to'] > count( $transcludedTargets ) ) {
702 $more = $linkRenderer->makeLink(
703 $whatLinksHere,
704 $this->msg( 'moredotdotdot' )->text(),
705 [],
706 [ 'hidelinks' => 1, 'hideredirs' => 1 ]
707 );
708 } else {
709 $more = null;
710 }
711
712 $templateListFormatter = new TemplatesOnThisPageFormatter(
713 $this->getContext(),
714 $linkRenderer
715 );
716
717 $pageInfo['header-properties'][] = [
718 $this->msg( 'pageinfo-transclusions' )
719 ->numParams( $pageCounts['transclusion']['to'] ),
720 $templateListFormatter->format( $transcludedTargets, false, $more )
721 ];
722 }
723 }
724
725 return $pageInfo;
726 }
727
733 private function pageCounts() {
734 $page = $this->getWikiPage();
735 $fname = __METHOD__;
736 $config = $this->context->getConfig();
737 $services = MediaWikiServices::getInstance();
738 $cache = $services->getMainWANObjectCache();
739
740 return $cache->getWithSetCallback(
741 self::getCacheKey( $cache, $page->getTitle(), $page->getLatest() ),
742 WANObjectCache::TTL_WEEK,
743 function ( $oldValue, &$ttl, &$setOpts ) use ( $page, $config, $fname, $services ) {
744 $title = $page->getTitle();
745 $id = $title->getArticleID();
746
748 $dbrWatchlist = wfGetDB( DB_REPLICA, 'watchlist' );
749 $setOpts += Database::getCacheSetOptions( $dbr, $dbrWatchlist );
750
751 $tables = [ 'revision_actor_temp' ];
752 $field = 'revactor_actor';
753 $pageField = 'revactor_page';
754 $tsField = 'revactor_timestamp';
755 $joins = [];
756
757 $watchedItemStore = $services->getWatchedItemStore();
758
759 $result = [];
760 $result['watchers'] = $watchedItemStore->countWatchers( $title );
761
762 if ( $config->get( 'ShowUpdatedMarker' ) ) {
763 $updated = (int)wfTimestamp( TS_UNIX, $page->getTimestamp() );
764 $result['visitingWatchers'] = $watchedItemStore->countVisitingWatchers(
765 $title,
766 $updated - $config->get( 'WatchersMaxAge' )
767 );
768 }
769
770 // Total number of edits
771 $edits = (int)$dbr->selectField(
772 'revision',
773 'COUNT(*)',
774 [ 'rev_page' => $id ],
775 $fname
776 );
777 $result['edits'] = $edits;
778
779 // Total number of distinct authors
780 if ( $config->get( 'MiserMode' ) ) {
781 $result['authors'] = 0;
782 } else {
783 $result['authors'] = (int)$dbr->selectField(
784 $tables,
785 "COUNT(DISTINCT $field)",
786 [ $pageField => $id ],
787 $fname,
788 [],
789 $joins
790 );
791 }
792
793 // "Recent" threshold defined by RCMaxAge setting
794 $threshold = $dbr->timestamp( time() - $config->get( 'RCMaxAge' ) );
795
796 // Recent number of edits
797 $edits = (int)$dbr->selectField(
798 'revision',
799 'COUNT(rev_page)',
800 [
801 'rev_page' => $id,
802 "rev_timestamp >= " . $dbr->addQuotes( $threshold )
803 ],
804 $fname
805 );
806 $result['recent_edits'] = $edits;
807
808 // Recent number of distinct authors
809 $result['recent_authors'] = (int)$dbr->selectField(
810 $tables,
811 "COUNT(DISTINCT $field)",
812 [
813 $pageField => $id,
814 "$tsField >= " . $dbr->addQuotes( $threshold )
815 ],
816 $fname,
817 [],
818 $joins
819 );
820
821 // Subpages (if enabled)
822 if ( $services->getNamespaceInfo()->hasSubpages( $title->getNamespace() ) ) {
823 $conds = [ 'page_namespace' => $title->getNamespace() ];
824 $conds[] = 'page_title ' .
825 $dbr->buildLike( $title->getDBkey() . '/', $dbr->anyString() );
826
827 // Subpages of this page (redirects)
828 $conds['page_is_redirect'] = 1;
829 $result['subpages']['redirects'] = (int)$dbr->selectField(
830 'page',
831 'COUNT(page_id)',
832 $conds,
833 $fname
834 );
835
836 // Subpages of this page (non-redirects)
837 $conds['page_is_redirect'] = 0;
838 $result['subpages']['nonredirects'] = (int)$dbr->selectField(
839 'page',
840 'COUNT(page_id)',
841 $conds,
842 $fname
843 );
844
845 // Subpages of this page (total)
846 $result['subpages']['total'] = $result['subpages']['redirects']
847 + $result['subpages']['nonredirects'];
848 }
849
850 // Counts for the number of transclusion links (to/from)
851 if ( $config->get( 'MiserMode' ) ) {
852 $result['transclusion']['to'] = 0;
853 } else {
854 $result['transclusion']['to'] = (int)$dbr->selectField(
855 'templatelinks',
856 'COUNT(tl_from)',
857 [
858 'tl_namespace' => $title->getNamespace(),
859 'tl_title' => $title->getDBkey()
860 ],
861 $fname
862 );
863 }
864
865 $result['transclusion']['from'] = (int)$dbr->selectField(
866 'templatelinks',
867 'COUNT(*)',
868 [ 'tl_from' => $title->getArticleID() ],
869 $fname
870 );
871
872 return $result;
873 }
874 );
875 }
876
882 protected function getPageTitle() {
883 return $this->msg( 'pageinfo-title', $this->getTitle()->getPrefixedText() )->text();
884 }
885
890 protected function getContributors() {
891 $contributors = $this->getWikiPage()->getContributors();
892 $real_names = [];
893 $user_names = [];
894 $anon_ips = [];
895 $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
896
897 # Sift for real versus user names
899 foreach ( $contributors as $user ) {
900 $page = $user->isAnon()
901 ? SpecialPage::getTitleFor( 'Contributions', $user->getName() )
902 : $user->getUserPage();
903
904 $hiddenPrefs = $this->context->getConfig()->get( 'HiddenPrefs' );
905 if ( $user->getId() == 0 ) {
906 $anon_ips[] = $linkRenderer->makeLink( $page, $user->getName() );
907 } elseif ( !in_array( 'realname', $hiddenPrefs ) && $user->getRealName() ) {
908 $real_names[] = $linkRenderer->makeLink( $page, $user->getRealName() );
909 } else {
910 $user_names[] = $linkRenderer->makeLink( $page, $user->getName() );
911 }
912 }
913
914 $lang = $this->getLanguage();
915
916 $real = $lang->listToText( $real_names );
917
918 # "ThisSite user(s) A, B and C"
919 if ( count( $user_names ) ) {
920 $user = $this->msg( 'siteusers' )
921 ->rawParams( $lang->listToText( $user_names ) )
922 ->params( count( $user_names ) )->escaped();
923 } else {
924 $user = false;
925 }
926
927 if ( count( $anon_ips ) ) {
928 $anon = $this->msg( 'anonusers' )
929 ->rawParams( $lang->listToText( $anon_ips ) )
930 ->params( count( $anon_ips ) )->escaped();
931 } else {
932 $anon = false;
933 }
934
935 # This is the big list, all mooshed together. We sift for blank strings
936 $fulllist = [];
937 foreach ( [ $real, $user, $anon ] as $s ) {
938 if ( $s !== '' ) {
939 array_push( $fulllist, $s );
940 }
941 }
942
943 $count = count( $fulllist );
944
945 # "Based on work by ..."
946 return $count
947 ? $this->msg( 'othercontribs' )->rawParams(
948 $lang->listToText( $fulllist ) )->params( $count )->escaped()
949 : '';
950 }
951
957 protected function getDescription() {
958 return '';
959 }
960
967 protected static function getCacheKey( WANObjectCache $cache, Title $title, $revId ) {
968 return $cache->makeKey( 'infoaction', md5( $title->getPrefixedText() ), $revId, self::VERSION );
969 }
970}
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
$magicWords
getWikiPage()
Get a WikiPage object.
Definition Action.php:278
getHookRunner()
Definition Action.php:329
WikiPage Article ImagePage CategoryPage Page $page
Page on which we're performing the action.
Definition Action.php:53
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Definition Action.php:519
getTitle()
Shortcut to get the Title object from the page.
Definition Action.php:299
getContext()
Get the IContextSource in use here.
Definition Action.php:215
getUser()
Shortcut to get the User being used for this instance.
Definition Action.php:249
static exists(string $name)
Check if a given action is recognised, even if it's disabled.
Definition Action.php:206
getArticle()
Get a Article object.
Definition Action.php:289
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Definition Action.php:311
getLanguage()
Shortcut to get the user Language being used for this instance.
Definition Action.php:268
An action which just does something, without showing a form first.
Displays information about a page.
requiresWrite()
Whether this action requires the wiki not to be locked.
makeHeader( $header, $canonicalId)
Creates a header that can be added to the output.
pageCounts()
Returns page counts that would be too "expensive" to retrieve by normal means.
getPageTitle()
Returns the name that goes in the "<h1>" page title.
pageInfo()
Returns an array of info groups (will be rendered as tables), keyed by group ID.
onView()
Shows page information on GET request.
getDescription()
Returns the description that goes below the "<h1>" tag.
requiresUnblock()
Whether this action can still be executed by a blocked user.
getName()
Returns the name of the action this object responds to.
addRow( $table, $name, $value, $id)
Adds a row to a table that will be added to the content.
const VERSION
addTable( $content, $table)
Adds a table to the content that will be added to the output.
static getCacheKey(WANObjectCache $cache, Title $title, $revId)
static invalidateCache(Title $title, $revid=null)
Clear the info cache for a given Title.
getContributors()
Get a list of contributors of $article.
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition LinkBatch.php:35
add( $ns, $dbkey)
static formatHiddenCategories( $hiddencats)
Returns HTML for the "hidden categories on this page" list.
Definition Linker.php:2085
static revUserTools( $rev, $isPublic=false, $useParentheses=true)
Generate a user tool link cluster if the current user is allowed to view it.
Definition Linker.php:1152
MediaWikiServices is the service locator for the application scope of MediaWiki.
Page revision base class.
The Message class deals with fetching and processing of interface message into a variety of formats.
Definition Message.php:161
static getInstance()
Definition PageProps.php:66
static getTitleFor( $name, $subpage=false, $fragment='')
Get a localised Title object for a specified special page name If you don't need a full Title object,...
static getTitleValueFor( $name, $subpage=false, $fragment='')
Get a localised TitleValue object for a specified special page name.
Handles formatting for the "templates used on this page" lists.
Represents a title within MediaWiki.
Definition Title.php:42
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:60
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition User.php:541
Multi-datacenter aware caching interface.
getTimestamp()
Definition WikiPage.php:813
getLatest()
Get the page_latest field.
Definition WikiPage.php:696
getTitle()
Get the title object of the article.
Definition WikiPage.php:318
Relational database abstraction object.
Definition Database.php:50
const NS_USER
Definition Defines.php:72
const NS_FILE
Definition Defines.php:76
const NS_USER_TALK
Definition Defines.php:73
const NS_CATEGORY
Definition Defines.php:84
$cache
Definition mcc.php:33
const DB_REPLICA
Definition defines.php:25
$content
Definition router.php:76
if(!isset( $args[0])) $lang
$header
$contributors