MediaWiki  1.27.2
InfoAction.php
Go to the documentation of this file.
1 <?php
30 class InfoAction extends FormlessAction {
31  const VERSION = 1;
32 
38  public function getName() {
39  return 'info';
40  }
41 
47  public function requiresUnblock() {
48  return false;
49  }
50 
56  public function requiresWrite() {
57  return false;
58  }
59 
67  public static function invalidateCache( Title $title, $revid = null ) {
68  if ( !$revid ) {
69  $revision = Revision::newFromTitle( $title, 0, Revision::READ_LATEST );
70  $revid = $revision ? $revision->getId() : null;
71  }
72  if ( $revid !== null ) {
73  $key = self::getCacheKey( $title, $revid );
75  }
76  }
77 
83  public function onView() {
84  $content = '';
85 
86  // Validate revision
87  $oldid = $this->page->getOldID();
88  if ( $oldid ) {
89  $revision = $this->page->getRevisionFetched();
90 
91  // Revision is missing
92  if ( $revision === null ) {
93  return $this->msg( 'missing-revision', $oldid )->parse();
94  }
95 
96  // Revision is not current
97  if ( !$revision->isCurrent() ) {
98  return $this->msg( 'pageinfo-not-current' )->plain();
99  }
100  }
101 
102  // Page header
103  if ( !$this->msg( 'pageinfo-header' )->isDisabled() ) {
104  $content .= $this->msg( 'pageinfo-header' )->parse();
105  }
106 
107  // Hide "This page is a member of # hidden categories" explanation
108  $content .= Html::element( 'style', [],
109  '.mw-hiddenCategoriesExplanation { display: none; }' ) . "\n";
110 
111  // Hide "Templates used on this page" explanation
112  $content .= Html::element( 'style', [],
113  '.mw-templatesUsedExplanation { display: none; }' ) . "\n";
114 
115  // Get page information
116  $pageInfo = $this->pageInfo();
117 
118  // Allow extensions to add additional information
119  Hooks::run( 'InfoAction', [ $this->getContext(), &$pageInfo ] );
120 
121  // Render page information
122  foreach ( $pageInfo as $header => $infoTable ) {
123  // Messages:
124  // pageinfo-header-basic, pageinfo-header-edits, pageinfo-header-restrictions,
125  // pageinfo-header-properties, pageinfo-category-info
126  $content .= $this->makeHeader( $this->msg( "pageinfo-${header}" )->escaped() ) . "\n";
127  $table = "\n";
128  foreach ( $infoTable as $infoRow ) {
129  $name = ( $infoRow[0] instanceof Message ) ? $infoRow[0]->escaped() : $infoRow[0];
130  $value = ( $infoRow[1] instanceof Message ) ? $infoRow[1]->escaped() : $infoRow[1];
131  $id = ( $infoRow[0] instanceof Message ) ? $infoRow[0]->getKey() : null;
132  $table = $this->addRow( $table, $name, $value, $id ) . "\n";
133  }
134  $content = $this->addTable( $content, $table ) . "\n";
135  }
136 
137  // Page footer
138  if ( !$this->msg( 'pageinfo-footer' )->isDisabled() ) {
139  $content .= $this->msg( 'pageinfo-footer' )->parse();
140  }
141 
142  return $content;
143  }
144 
151  protected function makeHeader( $header ) {
152  $spanAttribs = [ 'class' => 'mw-headline', 'id' => Sanitizer::escapeId( $header ) ];
153 
154  return Html::rawElement( 'h2', [], Html::element( 'span', $spanAttribs, $header ) );
155  }
156 
166  protected function addRow( $table, $name, $value, $id ) {
167  return $table .
169  'tr',
170  $id === null ? [] : [ 'id' => 'mw-' . $id ],
171  Html::rawElement( 'td', [ 'style' => 'vertical-align: top;' ], $name ) .
172  Html::rawElement( 'td', [], $value )
173  );
174  }
175 
183  protected function addTable( $content, $table ) {
184  return $content . Html::rawElement( 'table', [ 'class' => 'wikitable mw-page-info' ],
185  $table );
186  }
187 
195  protected function pageInfo() {
197 
198  $user = $this->getUser();
199  $lang = $this->getLanguage();
200  $title = $this->getTitle();
201  $id = $title->getArticleID();
202  $config = $this->context->getConfig();
203 
204  $pageCounts = $this->pageCounts( $this->page );
205 
206  $pageProperties = [];
207  $props = PageProps::getInstance()->getAllProperties( $title );
208  if ( isset( $props[$id] ) ) {
209  $pageProperties = $props[$id];
210  }
211 
212  // Basic information
213  $pageInfo = [];
214  $pageInfo['header-basic'] = [];
215 
216  // Display title
217  $displayTitle = $title->getPrefixedText();
218  if ( isset( $pageProperties['displaytitle'] ) ) {
219  $displayTitle = $pageProperties['displaytitle'];
220  }
221 
222  $pageInfo['header-basic'][] = [
223  $this->msg( 'pageinfo-display-title' ), $displayTitle
224  ];
225 
226  // Is it a redirect? If so, where to?
227  if ( $title->isRedirect() ) {
228  $pageInfo['header-basic'][] = [
229  $this->msg( 'pageinfo-redirectsto' ),
230  Linker::link( $this->page->getRedirectTarget() ) .
231  $this->msg( 'word-separator' )->escaped() .
232  $this->msg( 'parentheses' )->rawParams( Linker::link(
233  $this->page->getRedirectTarget(),
234  $this->msg( 'pageinfo-redirectsto-info' )->escaped(),
235  [],
236  [ 'action' => 'info' ]
237  ) )->escaped()
238  ];
239  }
240 
241  // Default sort key
242  $sortKey = $title->getCategorySortkey();
243  if ( isset( $pageProperties['defaultsort'] ) ) {
244  $sortKey = $pageProperties['defaultsort'];
245  }
246 
247  $sortKey = htmlspecialchars( $sortKey );
248  $pageInfo['header-basic'][] = [ $this->msg( 'pageinfo-default-sort' ), $sortKey ];
249 
250  // Page length (in bytes)
251  $pageInfo['header-basic'][] = [
252  $this->msg( 'pageinfo-length' ), $lang->formatNum( $title->getLength() )
253  ];
254 
255  // Page ID (number not localised, as it's a database ID)
256  $pageInfo['header-basic'][] = [ $this->msg( 'pageinfo-article-id' ), $id ];
257 
258  // Language in which the page content is (supposed to be) written
259  $pageLang = $title->getPageLanguage()->getCode();
260 
261  if ( $config->get( 'PageLanguageUseDB' )
262  && $this->getTitle()->userCan( 'pagelang', $this->getUser() )
263  ) {
264  // Link to Special:PageLanguage with pre-filled page title if user has permissions
265  $titleObj = SpecialPage::getTitleFor( 'PageLanguage', $title->getPrefixedText() );
266  $langDisp = Linker::link(
267  $titleObj,
268  $this->msg( 'pageinfo-language' )->escaped()
269  );
270  } else {
271  // Display just the message
272  $langDisp = $this->msg( 'pageinfo-language' )->escaped();
273  }
274 
275  $pageInfo['header-basic'][] = [ $langDisp,
276  Language::fetchLanguageName( $pageLang, $lang->getCode() )
277  . ' ' . $this->msg( 'parentheses', $pageLang )->escaped() ];
278 
279  // Content model of the page
280  $pageInfo['header-basic'][] = [
281  $this->msg( 'pageinfo-content-model' ),
282  htmlspecialchars( ContentHandler::getLocalizedName( $title->getContentModel() ) )
283  ];
284 
285  // Search engine status
286  $pOutput = new ParserOutput();
287  if ( isset( $pageProperties['noindex'] ) ) {
288  $pOutput->setIndexPolicy( 'noindex' );
289  }
290  if ( isset( $pageProperties['index'] ) ) {
291  $pOutput->setIndexPolicy( 'index' );
292  }
293 
294  // Use robot policy logic
295  $policy = $this->page->getRobotPolicy( 'view', $pOutput );
296  $pageInfo['header-basic'][] = [
297  // Messages: pageinfo-robot-index, pageinfo-robot-noindex
298  $this->msg( 'pageinfo-robot-policy' ),
299  $this->msg( "pageinfo-robot-${policy['index']}" )
300  ];
301 
302  $unwatchedPageThreshold = $config->get( 'UnwatchedPageThreshold' );
303  if (
304  $user->isAllowed( 'unwatchedpages' ) ||
305  ( $unwatchedPageThreshold !== false &&
306  $pageCounts['watchers'] >= $unwatchedPageThreshold )
307  ) {
308  // Number of page watchers
309  $pageInfo['header-basic'][] = [
310  $this->msg( 'pageinfo-watchers' ),
311  $lang->formatNum( $pageCounts['watchers'] )
312  ];
313  if (
314  $config->get( 'ShowUpdatedMarker' ) &&
315  isset( $pageCounts['visitingWatchers'] )
316  ) {
317  $minToDisclose = $config->get( 'UnwatchedPageSecret' );
318  if ( $pageCounts['visitingWatchers'] > $minToDisclose ||
319  $user->isAllowed( 'unwatchedpages' ) ) {
320  $pageInfo['header-basic'][] = [
321  $this->msg( 'pageinfo-visiting-watchers' ),
322  $lang->formatNum( $pageCounts['visitingWatchers'] )
323  ];
324  } else {
325  $pageInfo['header-basic'][] = [
326  $this->msg( 'pageinfo-visiting-watchers' ),
327  $this->msg( 'pageinfo-few-visiting-watchers' )
328  ];
329  }
330  }
331  } elseif ( $unwatchedPageThreshold !== false ) {
332  $pageInfo['header-basic'][] = [
333  $this->msg( 'pageinfo-watchers' ),
334  $this->msg( 'pageinfo-few-watchers' )->numParams( $unwatchedPageThreshold )
335  ];
336  }
337 
338  // Redirects to this page
339  $whatLinksHere = SpecialPage::getTitleFor( 'Whatlinkshere', $title->getPrefixedText() );
340  $pageInfo['header-basic'][] = [
341  Linker::link(
342  $whatLinksHere,
343  $this->msg( 'pageinfo-redirects-name' )->escaped(),
344  [],
345  [
346  'hidelinks' => 1,
347  'hidetrans' => 1,
348  'hideimages' => $title->getNamespace() == NS_FILE
349  ]
350  ),
351  $this->msg( 'pageinfo-redirects-value' )
352  ->numParams( count( $title->getRedirectsHere() ) )
353  ];
354 
355  // Is it counted as a content page?
356  if ( $this->page->isCountable() ) {
357  $pageInfo['header-basic'][] = [
358  $this->msg( 'pageinfo-contentpage' ),
359  $this->msg( 'pageinfo-contentpage-yes' )
360  ];
361  }
362 
363  // Subpages of this page, if subpages are enabled for the current NS
364  if ( MWNamespace::hasSubpages( $title->getNamespace() ) ) {
365  $prefixIndex = SpecialPage::getTitleFor(
366  'Prefixindex', $title->getPrefixedText() . '/' );
367  $pageInfo['header-basic'][] = [
368  Linker::link( $prefixIndex, $this->msg( 'pageinfo-subpages-name' )->escaped() ),
369  $this->msg( 'pageinfo-subpages-value' )
370  ->numParams(
371  $pageCounts['subpages']['total'],
372  $pageCounts['subpages']['redirects'],
373  $pageCounts['subpages']['nonredirects'] )
374  ];
375  }
376 
377  if ( $title->inNamespace( NS_CATEGORY ) ) {
378  $category = Category::newFromTitle( $title );
379 
380  // $allCount is the total number of cat members,
381  // not the count of how many members are normal pages.
382  $allCount = (int)$category->getPageCount();
383  $subcatCount = (int)$category->getSubcatCount();
384  $fileCount = (int)$category->getFileCount();
385  $pagesCount = $allCount - $subcatCount - $fileCount;
386 
387  $pageInfo['category-info'] = [
388  [
389  $this->msg( 'pageinfo-category-total' ),
390  $lang->formatNum( $allCount )
391  ],
392  [
393  $this->msg( 'pageinfo-category-pages' ),
394  $lang->formatNum( $pagesCount )
395  ],
396  [
397  $this->msg( 'pageinfo-category-subcats' ),
398  $lang->formatNum( $subcatCount )
399  ],
400  [
401  $this->msg( 'pageinfo-category-files' ),
402  $lang->formatNum( $fileCount )
403  ]
404  ];
405  }
406 
407  // Page protection
408  $pageInfo['header-restrictions'] = [];
409 
410  // Is this page affected by the cascading protection of something which includes it?
411  if ( $title->isCascadeProtected() ) {
412  $cascadingFrom = '';
413  $sources = $title->getCascadeProtectionSources()[0];
414 
415  foreach ( $sources as $sourceTitle ) {
416  $cascadingFrom .= Html::rawElement(
417  'li', [], Linker::linkKnown( $sourceTitle ) );
418  }
419 
420  $cascadingFrom = Html::rawElement( 'ul', [], $cascadingFrom );
421  $pageInfo['header-restrictions'][] = [
422  $this->msg( 'pageinfo-protect-cascading-from' ),
423  $cascadingFrom
424  ];
425  }
426 
427  // Is out protection set to cascade to other pages?
428  if ( $title->areRestrictionsCascading() ) {
429  $pageInfo['header-restrictions'][] = [
430  $this->msg( 'pageinfo-protect-cascading' ),
431  $this->msg( 'pageinfo-protect-cascading-yes' )
432  ];
433  }
434 
435  // Page protection
436  foreach ( $title->getRestrictionTypes() as $restrictionType ) {
437  $protectionLevel = implode( ', ', $title->getRestrictions( $restrictionType ) );
438 
439  if ( $protectionLevel == '' ) {
440  // Allow all users
441  $message = $this->msg( 'protect-default' )->escaped();
442  } else {
443  // Administrators only
444  // Messages: protect-level-autoconfirmed, protect-level-sysop
445  $message = $this->msg( "protect-level-$protectionLevel" );
446  if ( $message->isDisabled() ) {
447  // Require "$1" permission
448  $message = $this->msg( "protect-fallback", $protectionLevel )->parse();
449  } else {
450  $message = $message->escaped();
451  }
452  }
453  $expiry = $title->getRestrictionExpiry( $restrictionType );
454  $formattedexpiry = $this->msg( 'parentheses',
455  $this->getLanguage()->formatExpiry( $expiry ) )->escaped();
456  $message .= $this->msg( 'word-separator' )->escaped() . $formattedexpiry;
457 
458  // Messages: restriction-edit, restriction-move, restriction-create,
459  // restriction-upload
460  $pageInfo['header-restrictions'][] = [
461  $this->msg( "restriction-$restrictionType" ), $message
462  ];
463  }
464 
465  if ( !$this->page->exists() ) {
466  return $pageInfo;
467  }
468 
469  // Edit history
470  $pageInfo['header-edits'] = [];
471 
472  $firstRev = $this->page->getOldestRevision();
473  $lastRev = $this->page->getRevision();
474  $batch = new LinkBatch;
475 
476  if ( $firstRev ) {
477  $firstRevUser = $firstRev->getUserText( Revision::FOR_THIS_USER );
478  if ( $firstRevUser !== '' ) {
479  $batch->add( NS_USER, $firstRevUser );
480  $batch->add( NS_USER_TALK, $firstRevUser );
481  }
482  }
483 
484  if ( $lastRev ) {
485  $lastRevUser = $lastRev->getUserText( Revision::FOR_THIS_USER );
486  if ( $lastRevUser !== '' ) {
487  $batch->add( NS_USER, $lastRevUser );
488  $batch->add( NS_USER_TALK, $lastRevUser );
489  }
490  }
491 
492  $batch->execute();
493 
494  if ( $firstRev ) {
495  // Page creator
496  $pageInfo['header-edits'][] = [
497  $this->msg( 'pageinfo-firstuser' ),
498  Linker::revUserTools( $firstRev )
499  ];
500 
501  // Date of page creation
502  $pageInfo['header-edits'][] = [
503  $this->msg( 'pageinfo-firsttime' ),
505  $title,
506  htmlspecialchars( $lang->userTimeAndDate( $firstRev->getTimestamp(), $user ) ),
507  [],
508  [ 'oldid' => $firstRev->getId() ]
509  )
510  ];
511  }
512 
513  if ( $lastRev ) {
514  // Latest editor
515  $pageInfo['header-edits'][] = [
516  $this->msg( 'pageinfo-lastuser' ),
517  Linker::revUserTools( $lastRev )
518  ];
519 
520  // Date of latest edit
521  $pageInfo['header-edits'][] = [
522  $this->msg( 'pageinfo-lasttime' ),
524  $title,
525  htmlspecialchars(
526  $lang->userTimeAndDate( $this->page->getTimestamp(), $user )
527  ),
528  [],
529  [ 'oldid' => $this->page->getLatest() ]
530  )
531  ];
532  }
533 
534  // Total number of edits
535  $pageInfo['header-edits'][] = [
536  $this->msg( 'pageinfo-edits' ), $lang->formatNum( $pageCounts['edits'] )
537  ];
538 
539  // Total number of distinct authors
540  if ( $pageCounts['authors'] > 0 ) {
541  $pageInfo['header-edits'][] = [
542  $this->msg( 'pageinfo-authors' ), $lang->formatNum( $pageCounts['authors'] )
543  ];
544  }
545 
546  // Recent number of edits (within past 30 days)
547  $pageInfo['header-edits'][] = [
548  $this->msg( 'pageinfo-recent-edits',
549  $lang->formatDuration( $config->get( 'RCMaxAge' ) ) ),
550  $lang->formatNum( $pageCounts['recent_edits'] )
551  ];
552 
553  // Recent number of distinct authors
554  $pageInfo['header-edits'][] = [
555  $this->msg( 'pageinfo-recent-authors' ),
556  $lang->formatNum( $pageCounts['recent_authors'] )
557  ];
558 
559  // Array of MagicWord objects
561 
562  // Array of magic word IDs
563  $wordIDs = $magicWords->names;
564 
565  // Array of IDs => localized magic words
566  $localizedWords = $wgContLang->getMagicWords();
567 
568  $listItems = [];
569  foreach ( $pageProperties as $property => $value ) {
570  if ( in_array( $property, $wordIDs ) ) {
571  $listItems[] = Html::element( 'li', [], $localizedWords[$property][1] );
572  }
573  }
574 
575  $localizedList = Html::rawElement( 'ul', [], implode( '', $listItems ) );
576  $hiddenCategories = $this->page->getHiddenCategories();
577 
578  if (
579  count( $listItems ) > 0 ||
580  count( $hiddenCategories ) > 0 ||
581  $pageCounts['transclusion']['from'] > 0 ||
582  $pageCounts['transclusion']['to'] > 0
583  ) {
584  $options = [ 'LIMIT' => $config->get( 'PageInfoTransclusionLimit' ) ];
585  $transcludedTemplates = $title->getTemplateLinksFrom( $options );
586  if ( $config->get( 'MiserMode' ) ) {
587  $transcludedTargets = [];
588  } else {
589  $transcludedTargets = $title->getTemplateLinksTo( $options );
590  }
591 
592  // Page properties
593  $pageInfo['header-properties'] = [];
594 
595  // Magic words
596  if ( count( $listItems ) > 0 ) {
597  $pageInfo['header-properties'][] = [
598  $this->msg( 'pageinfo-magic-words' )->numParams( count( $listItems ) ),
599  $localizedList
600  ];
601  }
602 
603  // Hidden categories
604  if ( count( $hiddenCategories ) > 0 ) {
605  $pageInfo['header-properties'][] = [
606  $this->msg( 'pageinfo-hidden-categories' )
607  ->numParams( count( $hiddenCategories ) ),
608  Linker::formatHiddenCategories( $hiddenCategories )
609  ];
610  }
611 
612  // Transcluded templates
613  if ( $pageCounts['transclusion']['from'] > 0 ) {
614  if ( $pageCounts['transclusion']['from'] > count( $transcludedTemplates ) ) {
615  $more = $this->msg( 'morenotlisted' )->escaped();
616  } else {
617  $more = null;
618  }
619 
620  $pageInfo['header-properties'][] = [
621  $this->msg( 'pageinfo-templates' )
622  ->numParams( $pageCounts['transclusion']['from'] ),
624  $transcludedTemplates,
625  false,
626  false,
627  $more )
628  ];
629  }
630 
631  if ( !$config->get( 'MiserMode' ) && $pageCounts['transclusion']['to'] > 0 ) {
632  if ( $pageCounts['transclusion']['to'] > count( $transcludedTargets ) ) {
633  $more = Linker::link(
634  $whatLinksHere,
635  $this->msg( 'moredotdotdot' )->escaped(),
636  [],
637  [ 'hidelinks' => 1, 'hideredirs' => 1 ]
638  );
639  } else {
640  $more = null;
641  }
642 
643  $pageInfo['header-properties'][] = [
644  $this->msg( 'pageinfo-transclusions' )
645  ->numParams( $pageCounts['transclusion']['to'] ),
647  $transcludedTargets,
648  false,
649  false,
650  $more )
651  ];
652  }
653  }
654 
655  return $pageInfo;
656  }
657 
664  protected function pageCounts( Page $page ) {
665  $fname = __METHOD__;
666  $config = $this->context->getConfig();
667 
668  return ObjectCache::getMainWANInstance()->getWithSetCallback(
669  self::getCacheKey( $page->getTitle(), $page->getLatest() ),
670  86400 * 7,
671  function ( $oldValue, &$ttl, &$setOpts ) use ( $page, $config, $fname ) {
672  $title = $page->getTitle();
673  $id = $title->getArticleID();
674 
675  $dbr = wfGetDB( DB_SLAVE );
676  $dbrWatchlist = wfGetDB( DB_SLAVE, 'watchlist' );
677 
678  $setOpts += Database::getCacheSetOptions( $dbr, $dbrWatchlist );
679 
680  $watchedItemStore = WatchedItemStore::getDefaultInstance();
681 
682  $result = [];
683  $result['watchers'] = $watchedItemStore->countWatchers( $title );
684 
685  if ( $config->get( 'ShowUpdatedMarker' ) ) {
686  $updated = wfTimestamp( TS_UNIX, $page->getTimestamp() );
687  $result['visitingWatchers'] = $watchedItemStore->countVisitingWatchers(
688  $title,
689  $updated - $config->get( 'WatchersMaxAge' )
690  );
691  }
692 
693  // Total number of edits
694  $edits = (int)$dbr->selectField(
695  'revision',
696  'COUNT(*)',
697  [ 'rev_page' => $id ],
698  $fname
699  );
700  $result['edits'] = $edits;
701 
702  // Total number of distinct authors
703  if ( $config->get( 'MiserMode' ) ) {
704  $result['authors'] = 0;
705  } else {
706  $result['authors'] = (int)$dbr->selectField(
707  'revision',
708  'COUNT(DISTINCT rev_user_text)',
709  [ 'rev_page' => $id ],
710  $fname
711  );
712  }
713 
714  // "Recent" threshold defined by RCMaxAge setting
715  $threshold = $dbr->timestamp( time() - $config->get( 'RCMaxAge' ) );
716 
717  // Recent number of edits
718  $edits = (int)$dbr->selectField(
719  'revision',
720  'COUNT(rev_page)',
721  [
722  'rev_page' => $id,
723  "rev_timestamp >= " . $dbr->addQuotes( $threshold )
724  ],
725  $fname
726  );
727  $result['recent_edits'] = $edits;
728 
729  // Recent number of distinct authors
730  $result['recent_authors'] = (int)$dbr->selectField(
731  'revision',
732  'COUNT(DISTINCT rev_user_text)',
733  [
734  'rev_page' => $id,
735  "rev_timestamp >= " . $dbr->addQuotes( $threshold )
736  ],
737  $fname
738  );
739 
740  // Subpages (if enabled)
741  if ( MWNamespace::hasSubpages( $title->getNamespace() ) ) {
742  $conds = [ 'page_namespace' => $title->getNamespace() ];
743  $conds[] = 'page_title ' .
744  $dbr->buildLike( $title->getDBkey() . '/', $dbr->anyString() );
745 
746  // Subpages of this page (redirects)
747  $conds['page_is_redirect'] = 1;
748  $result['subpages']['redirects'] = (int)$dbr->selectField(
749  'page',
750  'COUNT(page_id)',
751  $conds,
752  $fname
753  );
754 
755  // Subpages of this page (non-redirects)
756  $conds['page_is_redirect'] = 0;
757  $result['subpages']['nonredirects'] = (int)$dbr->selectField(
758  'page',
759  'COUNT(page_id)',
760  $conds,
761  $fname
762  );
763 
764  // Subpages of this page (total)
765  $result['subpages']['total'] = $result['subpages']['redirects']
766  + $result['subpages']['nonredirects'];
767  }
768 
769  // Counts for the number of transclusion links (to/from)
770  if ( $config->get( 'MiserMode' ) ) {
771  $result['transclusion']['to'] = 0;
772  } else {
773  $result['transclusion']['to'] = (int)$dbr->selectField(
774  'templatelinks',
775  'COUNT(tl_from)',
776  [
777  'tl_namespace' => $title->getNamespace(),
778  'tl_title' => $title->getDBkey()
779  ],
780  $fname
781  );
782  }
783 
784  $result['transclusion']['from'] = (int)$dbr->selectField(
785  'templatelinks',
786  'COUNT(*)',
787  [ 'tl_from' => $title->getArticleID() ],
788  $fname
789  );
790 
791  return $result;
792  }
793  );
794  }
795 
801  protected function getPageTitle() {
802  return $this->msg( 'pageinfo-title', $this->getTitle()->getPrefixedText() )->text();
803  }
804 
809  protected function getContributors() {
810  $contributors = $this->page->getContributors();
811  $real_names = [];
812  $user_names = [];
813  $anon_ips = [];
814 
815  # Sift for real versus user names
816 
817  foreach ( $contributors as $user ) {
818  $page = $user->isAnon()
819  ? SpecialPage::getTitleFor( 'Contributions', $user->getName() )
820  : $user->getUserPage();
821 
822  $hiddenPrefs = $this->context->getConfig()->get( 'HiddenPrefs' );
823  if ( $user->getId() == 0 ) {
824  $anon_ips[] = Linker::link( $page, htmlspecialchars( $user->getName() ) );
825  } elseif ( !in_array( 'realname', $hiddenPrefs ) && $user->getRealName() ) {
826  $real_names[] = Linker::link( $page, htmlspecialchars( $user->getRealName() ) );
827  } else {
828  $user_names[] = Linker::link( $page, htmlspecialchars( $user->getName() ) );
829  }
830  }
831 
832  $lang = $this->getLanguage();
833 
834  $real = $lang->listToText( $real_names );
835 
836  # "ThisSite user(s) A, B and C"
837  if ( count( $user_names ) ) {
838  $user = $this->msg( 'siteusers' )
839  ->rawParams( $lang->listToText( $user_names ) )
840  ->params( count( $user_names ) )->escaped();
841  } else {
842  $user = false;
843  }
844 
845  if ( count( $anon_ips ) ) {
846  $anon = $this->msg( 'anonusers' )
847  ->rawParams( $lang->listToText( $anon_ips ) )
848  ->params( count( $anon_ips ) )->escaped();
849  } else {
850  $anon = false;
851  }
852 
853  # This is the big list, all mooshed together. We sift for blank strings
854  $fulllist = [];
855  foreach ( [ $real, $user, $anon ] as $s ) {
856  if ( $s !== '' ) {
857  array_push( $fulllist, $s );
858  }
859  }
860 
861  $count = count( $fulllist );
862 
863  # "Based on work by ..."
864  return $count
865  ? $this->msg( 'othercontribs' )->rawParams(
866  $lang->listToText( $fulllist ) )->params( $count )->escaped()
867  : '';
868  }
869 
875  protected function getDescription() {
876  return '';
877  }
878 
884  protected static function getCacheKey( Title $title, $revId ) {
885  return wfMemcKey( 'infoaction', md5( $title->getPrefixedText() ), $revId, self::VERSION );
886  }
887 }
const FOR_THIS_USER
Definition: Revision.php:84
static getMainWANInstance()
Get the main WAN cache object.
wfGetDB($db, $groups=[], $wiki=false)
Get a Database object.
static linkKnown($target, $html=null, $customAttribs=[], $query=[], $options=[ 'known', 'noclasses'])
Identical to link(), except $options defaults to 'known'.
Definition: Linker.php:264
magic word the default is to use $key to get the and $key value or $key value text $key value html to format the value $key
Definition: hooks.txt:2321
const VERSION
Definition: InfoAction.php:31
$property
getTitle()
Shortcut to get the Title object from the page.
Definition: Action.php:246
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
static getTitleFor($name, $subpage=false, $fragment= '')
Get a localised Title object for a specified special page name.
Definition: SpecialPage.php:75
static rawElement($element, $attribs=[], $contents= '')
Returns an HTML element in a string.
Definition: Html.php:210
if(!isset($args[0])) $lang
pageInfo()
Returns page information in an easily-manipulated format.
Definition: InfoAction.php:195
getUser()
Shortcut to get the User being used for this instance.
Definition: Action.php:217
$value
onView()
Shows page information on GET request.
Definition: InfoAction.php:83
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context $revId
Definition: hooks.txt:1004
requiresUnblock()
Whether this action can still be executed by a blocked user.
Definition: InfoAction.php:47
getPrefixedText()
Get the prefixed title with spaces.
Definition: Title.php:1449
getLanguage()
Shortcut to get the user Language being used for this instance.
Definition: Action.php:236
Represents a title within MediaWiki.
Definition: Title.php:34
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
static newFromTitle(LinkTarget $linkTarget, $id=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given link target...
Definition: Revision.php:117
makeHeader($header)
Creates a header that can be added to the output.
Definition: InfoAction.php:151
pageCounts(Page $page)
Returns page counts that would be too "expensive" to retrieve by normal means.
Definition: InfoAction.php:664
static formatTemplates($templates, $preview=false, $section=false, $more=null)
Returns HTML for the "templates used on this page" list.
Definition: Linker.php:2039
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message.Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item.Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page.Return false to stop further processing of the tag $reader:XMLReader object &$pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision.Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag.Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload.Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports.&$fullInterwikiPrefix:Interwiki prefix, may contain colons.&$pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable.Can be used to lazy-load the import sources list.&$importSources:The value of $wgImportSources.Modify as necessary.See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page.$context:IContextSource object &$pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect.&$title:Title object for the current page &$request:WebRequest &$ignoreRedirect:boolean to skip redirect check &$target:Title/string of redirect target &$article:Article object 'InternalParseBeforeLinks':during Parser's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings.&$parser:Parser object &$text:string containing partially parsed text &$stripState:Parser's internal StripState object 'InternalParseBeforeSanitize':during Parser's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings.Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments.&$parser:Parser object &$text:string containing partially parsed text &$stripState:Parser's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not.Return true without providing an interwiki to continue interwiki search.$prefix:interwiki prefix we are looking for.&$iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user's email has been invalidated successfully.$user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification.Callee may modify $url and $query, URL will be constructed as $url.$query &$url:URL to index.php &$query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) &$article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() &$ip:IP being check &$result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from &$allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn't match your organization.$addr:The e-mail address entered by the user &$result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user &$result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we're looking for a messages file for &$file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED!Use $magicWords in a file listed in $wgExtensionMessagesFiles instead.Use this to define synonyms of magic words depending of the language &$magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces.Do not use this hook to add namespaces.Use CanonicalNamespaces for that.&$namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED!Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead.Use to define aliases of special pages names depending of the language &$specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names.&$names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page's language links.This is called in various places to allow extensions to define the effective language links for a page.$title:The page's Title.&$links:Associative array mapping language codes to prefixed links of the form"language:title".&$linkFlags:Associative array mapping prefixed links to arrays of flags.Currently unused, but planned to provide support for marking individual language links in the UI, e.g.for featured articles. 'LanguageSelector':Hook to change the language selector available on a page.$out:The output page.$cssClassName:CSS class name of the language selector. 'LinkBegin':Used when generating internal and interwiki links in Linker::link(), before processing starts.Return false to skip default processing and return $ret.See documentation for Linker::link() for details on the expected meanings of parameters.$skin:the Skin object $target:the Title that the link is pointing to &$html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:1796
$batch
Definition: linkcache.txt:23
static getDoubleUnderscoreArray()
Get a MagicWordArray of double-underscore entities.
Definition: MagicWord.php:307
Class representing a list of titles The execute() method checks them all for existence and adds them ...
Definition: LinkBatch.php:31
wfTimestamp($outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
static getLocalizedName($name, Language $lang=null)
Returns the localized name for a given content model.
Interface for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
Definition: Page.php:24
static invalidateCache(Title $title, $revid=null)
Clear the info cache for a given Title.
Definition: InfoAction.php:67
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1004
static newFromTitle($title)
Factory function.
Definition: Category.php:131
const NS_CATEGORY
Definition: Defines.php:83
const DB_SLAVE
Definition: Defines.php:46
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:912
static hasSubpages($index)
Does the namespace allow subpages?
getContext()
Get the IContextSource in use here.
Definition: Action.php:178
static run($event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:131
const NS_FILE
Definition: Defines.php:75
Displays information about a page.
Definition: InfoAction.php:30
static getCacheSetOptions(IDatabase $db1)
Merge the result of getSessionLagStatus() for several DBs using the most pessimistic values to estima...
Definition: Database.php:2907
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
static fetchLanguageName($code, $inLanguage=null, $include= 'all')
Definition: Language.php:886
$page
Page on which we're performing the action.
Definition: Action.php:44
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition: hooks.txt:242
static link($target, $html=null, $customAttribs=[], $query=[], $options=[])
This function returns an HTML link to the given target.
Definition: Linker.php:195
static escapeId($id, $options=[])
Given a value, escape it so that it can be used in an id attribute and return it. ...
Definition: Sanitizer.php:1132
requiresWrite()
Whether this action requires the wiki not to be locked.
Definition: InfoAction.php:56
An action which just does something, without showing a form first.
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
static getInstance()
Definition: PageProps.php:66
getDescription()
Returns the description that goes below the "

" tag.

Definition: InfoAction.php:875
if(!defined( 'MEDIAWIKI')) $fname
This file is not a valid entry point, perform no further processing unless MEDIAWIKI is defined...
Definition: Setup.php:35
addRow($table, $name, $value, $id)
Adds a row to a table that will be added to the content.
Definition: InfoAction.php:166
static formatHiddenCategories($hiddencats)
Returns HTML for the "hidden categories on this page" list.
Definition: Linker.php:2132
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist e g Watchlist removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content $content
Definition: hooks.txt:1004
this class mediates it Skin Encapsulates a look and feel for the wiki All of the functions that render HTML and make choices about how to render it are here and are called from various other places when and is meant to be subclassed with other skins that may override some of its functions The User object contains a reference to a and so rather than having a global skin object we just rely on the global User and get the skin with $wgUser and also has some character encoding functions and other locale stuff The current user interface language is instantiated as and the local content language as $wgContLang
Definition: design.txt:56
$count
wfMemcKey()
Make a cache key for the local wiki.
getName()
Returns the name of the action this object responds to.
Definition: InfoAction.php:38
getPageTitle()
Returns the name that goes in the "

" page title.

Definition: InfoAction.php:801
const TS_UNIX
Unix time - the number of seconds since 1970-01-01 00:00:00 UTC.
getContributors()
Get a list of contributors of $article.
Definition: InfoAction.php:809
msg()
Get a Message object with context set Parameters are the same as wfMessage()
Definition: Action.php:256
static getCacheKey(Title $title, $revId)
Definition: InfoAction.php:884
const NS_USER_TALK
Definition: Defines.php:72
static revUserTools($rev, $isPublic=false)
Generate a user tool link cluster if the current user is allowed to view it.
Definition: Linker.php:1252
static element($element, $attribs=[], $contents= '')
Identical to rawElement(), but HTML-escapes $contents (like Xml::element()).
Definition: Html.php:230
do that in ParserLimitReportFormat instead use this to modify the parameters of the image and a DIV can begin in one section and end in another Make sure your code can handle that case gracefully See the EditSectionClearerLink extension for an example zero but section is usually empty its values are the globals values before the output is cached one of or reset my talk page
Definition: hooks.txt:2338
addTable($content, $table)
Adds a table to the content that will be added to the output.
Definition: InfoAction.php:183
magicword txt Magic Words are some phrases used in the wikitext They are used for two that looks like templates but that don t accept any parameter *Parser functions(like{{fullurl:...}},{{#special:...}}) $magicWords['en']
Definition: magicword.txt:33
Allows to change the fields on the form that will be generated $name
Definition: hooks.txt:310