MediaWiki  master
Article.php
Go to the documentation of this file.
1 <?php
24 use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
36 use Wikimedia\IPUtils;
37 use Wikimedia\NonSerializable\NonSerializableTrait;
38 
49 class Article implements Page {
50  use ProtectedHookAccessorTrait;
51  use NonSerializableTrait;
52 
58  protected $mContext;
59 
61  protected $mPage;
62 
67  public $mOldId;
68 
70  public $mRedirectedFrom = null;
71 
73  public $mRedirectUrl = false;
74 
79  private $fetchResult = null;
80 
86  public $mParserOutput = null;
87 
93  protected $viewIsRenderAction = false;
94 
98  protected $linkRenderer;
99 
103  private $revisionStore;
104 
109 
113  private $userNameUtils;
114 
121  private $mRevisionRecord = null;
122 
127  public function __construct( Title $title, $oldId = null ) {
128  $this->mOldId = $oldId;
129  $this->mPage = $this->newPage( $title );
130 
131  $services = MediaWikiServices::getInstance();
132  $this->linkRenderer = $services->getLinkRenderer();
133  $this->revisionStore = $services->getRevisionStore();
134  $this->watchlistManager = $services->getWatchlistManager();
135  $this->userNameUtils = $services->getUserNameUtils();
136  }
137 
142  protected function newPage( Title $title ) {
143  return new WikiPage( $title );
144  }
145 
151  public static function newFromID( $id ) {
152  $t = Title::newFromID( $id );
153  return $t == null ? null : new static( $t );
154  }
155 
163  public static function newFromTitle( $title, IContextSource $context ) {
164  if ( $title->getNamespace() === NS_MEDIA ) {
165  // XXX: This should not be here, but where should it go?
166  $title = Title::makeTitle( NS_FILE, $title->getDBkey() );
167  }
168 
169  $page = null;
170  Hooks::runner()->onArticleFromTitle( $title, $page, $context );
171  if ( !$page ) {
172  switch ( $title->getNamespace() ) {
173  case NS_FILE:
174  $page = new ImagePage( $title );
175  break;
176  case NS_CATEGORY:
177  $page = new CategoryPage( $title );
178  break;
179  default:
180  $page = new Article( $title );
181  }
182  }
183  $page->setContext( $context );
184 
185  return $page;
186  }
187 
195  public static function newFromWikiPage( WikiPage $page, IContextSource $context ) {
196  $article = self::newFromTitle( $page->getTitle(), $context );
197  $article->mPage = $page; // override to keep process cached vars
198  return $article;
199  }
200 
206  public function getRedirectedFrom() {
207  return $this->mRedirectedFrom;
208  }
209 
215  public function setRedirectedFrom( Title $from ) {
216  $this->mRedirectedFrom = $from;
217  }
218 
224  public function getTitle() {
225  return $this->mPage->getTitle();
226  }
227 
234  public function getPage() {
235  return $this->mPage;
236  }
237 
238  public function clear() {
239  $this->mRedirectedFrom = null; # Title object if set
240  $this->mRedirectUrl = false;
241  $this->mRevisionRecord = null;
242  $this->fetchResult = null;
243 
244  // TODO hard-deprecate direct access to public fields
245 
246  $this->mPage->clear();
247  }
248 
256  public function getOldID() {
257  if ( $this->mOldId === null ) {
258  $this->mOldId = $this->getOldIDFromRequest();
259  }
260 
261  return $this->mOldId;
262  }
263 
269  public function getOldIDFromRequest() {
270  $this->mRedirectUrl = false;
271 
272  $request = $this->getContext()->getRequest();
273  $oldid = $request->getIntOrNull( 'oldid' );
274 
275  if ( $oldid === null ) {
276  return 0;
277  }
278 
279  if ( $oldid !== 0 ) {
280  # Load the given revision and check whether the page is another one.
281  # In that case, update this instance to reflect the change.
282  if ( $oldid === $this->mPage->getLatest() ) {
283  $this->mRevisionRecord = $this->mPage->getRevisionRecord();
284  } else {
285  $this->mRevisionRecord = $this->revisionStore->getRevisionById( $oldid );
286  if ( $this->mRevisionRecord !== null ) {
287  $revPageId = $this->mRevisionRecord->getPageId();
288  // Revision title doesn't match the page title given?
289  if ( $this->mPage->getId() != $revPageId ) {
290  $function = get_class( $this->mPage ) . '::newFromID';
291  $this->mPage = $function( $revPageId );
292  }
293  }
294  }
295  }
296 
297  $oldRev = $this->mRevisionRecord;
298  if ( $request->getRawVal( 'direction' ) === 'next' ) {
299  $nextid = 0;
300  if ( $oldRev ) {
301  $nextRev = $this->revisionStore->getNextRevision( $oldRev );
302  if ( $nextRev ) {
303  $nextid = $nextRev->getId();
304  }
305  }
306  if ( $nextid ) {
307  $oldid = $nextid;
308  $this->mRevisionRecord = null;
309  } else {
310  $this->mRedirectUrl = $this->getTitle()->getFullURL( 'redirect=no' );
311  }
312  } elseif ( $request->getRawVal( 'direction' ) === 'prev' ) {
313  $previd = 0;
314  if ( $oldRev ) {
315  $prevRev = $this->revisionStore->getPreviousRevision( $oldRev );
316  if ( $prevRev ) {
317  $previd = $prevRev->getId();
318  }
319  }
320  if ( $previd ) {
321  $oldid = $previd;
322  $this->mRevisionRecord = null;
323  }
324  }
325 
326  return $oldid;
327  }
328 
338  public function fetchRevisionRecord() {
339  if ( $this->fetchResult ) {
340  return $this->mRevisionRecord;
341  }
342 
343  $oldid = $this->getOldID();
344 
345  // $this->mRevisionRecord might already be fetched by getOldIDFromRequest()
346  if ( !$this->mRevisionRecord ) {
347  if ( !$oldid ) {
348  $this->mRevisionRecord = $this->mPage->getRevisionRecord();
349 
350  if ( !$this->mRevisionRecord ) {
351  wfDebug( __METHOD__ . " failed to find page data for title " .
352  $this->getTitle()->getPrefixedText() );
353 
354  // Just for sanity, output for this case is done by showMissingArticle().
355  $this->fetchResult = Status::newFatal( 'noarticletext' );
356  return null;
357  }
358  } else {
359  $this->mRevisionRecord = $this->revisionStore->getRevisionById( $oldid );
360 
361  if ( !$this->mRevisionRecord ) {
362  wfDebug( __METHOD__ . " failed to load revision, rev_id $oldid" );
363 
364  $this->fetchResult = Status::newFatal( 'missing-revision', $oldid );
365  return null;
366  }
367  }
368  }
369 
370  if ( !$this->mRevisionRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getContext()->getAuthority() ) ) {
371  wfDebug( __METHOD__ . " failed to retrieve content of revision " . $this->mRevisionRecord->getId() );
372 
373  // Just for sanity, output for this case is done by showDeletedRevisionHeader().
374  // title used in wikilinks, should not contain whitespaces
375  $this->fetchResult = new Status;
376  $title = $this->getTitle()->getPrefixedDBkey();
377 
378  if ( $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
379  $this->fetchResult->fatal( 'rev-suppressed-text' );
380  } else {
381  $this->fetchResult->fatal( 'rev-deleted-text-permission', $title );
382  }
383 
384  return null;
385  }
386 
387  $this->fetchResult = Status::newGood( $this->mRevisionRecord );
388  return $this->mRevisionRecord;
389  }
390 
396  public function isCurrent() {
397  # If no oldid, this is the current version.
398  if ( $this->getOldID() == 0 ) {
399  return true;
400  }
401 
402  return $this->mPage->exists() &&
403  $this->mRevisionRecord &&
404  $this->mRevisionRecord->isCurrent();
405  }
406 
415  public function getRevIdFetched() {
416  if ( $this->fetchResult && $this->fetchResult->isOK() ) {
417  return $this->fetchResult->value->getId();
418  } else {
419  return $this->mPage->getLatest();
420  }
421  }
422 
427  public function view() {
428  global $wgUseFileCache;
429 
430  # Get variables from query string
431  # As side effect this will load the revision and update the title
432  # in a revision ID is passed in the request, so this should remain
433  # the first call of this method even if $oldid is used way below.
434  $oldid = $this->getOldID();
435 
436  $user = $this->getContext()->getUser();
437  # Another check in case getOldID() is altering the title
438  $permissionStatus = PermissionStatus::newEmpty();
439  if ( !$this->getContext()->getAuthority()
440  ->authorizeRead( 'read', $this->getTitle(), $permissionStatus )
441  ) {
442  wfDebug( __METHOD__ . ": denied on secondary read check" );
443  throw new PermissionsError( 'read', $permissionStatus );
444  }
445 
446  $outputPage = $this->getContext()->getOutput();
447  # getOldID() may as well want us to redirect somewhere else
448  if ( $this->mRedirectUrl ) {
449  $outputPage->redirect( $this->mRedirectUrl );
450  wfDebug( __METHOD__ . ": redirecting due to oldid" );
451 
452  return;
453  }
454 
455  # If we got diff in the query, we want to see a diff page instead of the article.
456  if ( $this->getContext()->getRequest()->getCheck( 'diff' ) ) {
457  wfDebug( __METHOD__ . ": showing diff page" );
458  $this->showDiffPage();
459 
460  return;
461  }
462 
463  # Set page title (may be overridden by DISPLAYTITLE)
464  $outputPage->setPageTitle( $this->getTitle()->getPrefixedText() );
465 
466  $outputPage->setArticleFlag( true );
467  # Allow frames by default
468  $outputPage->allowClickjacking();
469 
470  $parserOptions = $this->getParserOptions();
471  $poOptions = [];
472  # Allow extensions to vary parser options used for article rendering
473  Hooks::runner()->onArticleParserOptions( $this, $parserOptions );
474  # Render printable version, use printable version cache
475  if ( $outputPage->isPrintable() ) {
476  $parserOptions->setIsPrintable( true );
477  $poOptions['enableSectionEditLinks'] = false;
478  $outputPage->prependHTML(
480  $outputPage->msg( 'printableversion-deprecated-warning' )->escaped()
481  )
482  );
483  } elseif ( $this->viewIsRenderAction || !$this->isCurrent() ||
484  !$this->getContext()->getAuthority()->probablyCan( 'edit', $this->getTitle() )
485  ) {
486  $poOptions['enableSectionEditLinks'] = false;
487  }
488 
489  # Try client and file cache
490  if ( $oldid === 0 && $this->mPage->checkTouched() ) {
491  # Try to stream the output from file cache
492  if ( $wgUseFileCache && $this->tryFileCache() ) {
493  wfDebug( __METHOD__ . ": done file cache" );
494  # tell wgOut that output is taken care of
495  $outputPage->disable();
496  $this->mPage->doViewUpdates( $user, $oldid );
497 
498  return;
499  }
500  }
501 
502  $this->showRedirectedFromHeader();
503  $this->showNamespaceHeader();
504 
505  $continue =
506  $this->generateContentOutput( $user, $parserOptions, $oldid, $outputPage, $poOptions );
507 
508  if ( !$continue ) {
509  return;
510  }
511 
512  # For the main page, overwrite the <title> element with the con-
513  # tents of 'pagetitle-view-mainpage' instead of the default (if
514  # that's not empty).
515  # This message always exists because it is in the i18n files
516  if ( $this->getTitle()->isMainPage() ) {
517  $msg = wfMessage( 'pagetitle-view-mainpage' )->inContentLanguage();
518  if ( !$msg->isDisabled() ) {
519  $outputPage->setHTMLTitle( $msg->page( $this->getTitle() )->text() );
520  }
521  }
522 
523  # Use adaptive TTLs for CDN so delayed/failed purges are noticed less often.
524  # This could use getTouched(), but that could be scary for major template edits.
525  $outputPage->adaptCdnTTL( $this->mPage->getTimestamp(), IExpiringStore::TTL_DAY );
526 
527  $this->showViewFooter();
528  $this->mPage->doViewUpdates( $user, $oldid ); // FIXME: test this
529 
530  # Load the postEdit module if the user just saved this revision
531  # See also EditPage::setPostEditCookie
532  $request = $this->getContext()->getRequest();
534  $postEdit = $request->getCookie( $cookieKey );
535  if ( $postEdit ) {
536  # Clear the cookie. This also prevents caching of the response.
537  $request->response()->clearCookie( $cookieKey );
538  $outputPage->addJsConfigVars( 'wgPostEdit', $postEdit );
539  $outputPage->addModules( 'mediawiki.action.view.postEdit' ); // FIXME: test this
540  }
541  }
542 
555  private function generateContentOutput(
556  Authority $performer,
557  ParserOptions $parserOptions,
558  int $oldid,
559  OutputPage $outputPage,
560  array $textOptions
561  ): bool {
562  # Should the parser cache be used?
563  $useParserCache = true;
564  $pOutput = null;
565  $parserOutputAccess = MediaWikiServices::getInstance()->getParserOutputAccess();
566 
567  // NOTE: $outputDone and $useParserCache may be changed by the hook
568  $this->getHookRunner()->onArticleViewHeader( $this, $outputDone, $useParserCache );
569  if ( $outputDone ) {
570  if ( $outputDone instanceof ParserOutput ) {
571  $pOutput = $outputDone;
572  }
573 
574  if ( $pOutput ) {
575  $this->doOutputMetaData( $pOutput, $outputPage );
576  }
577  return true;
578  }
579 
580  // Early abort if the page doesn't exist
581  if ( !$this->mPage->exists() ) {
582  wfDebug( __METHOD__ . ": showing missing article" );
583  $this->showMissingArticle();
584  $this->mPage->doViewUpdates( $performer );
585  return false; // skip all further output to OutputPage
586  }
587 
588  // Try the latest parser cache
589  // NOTE: try latest-revision cache first to avoid loading revision.
590  if ( $useParserCache && !$oldid ) {
591  $pOutput = $parserOutputAccess->getCachedParserOutput(
592  $this->getPage(),
593  $parserOptions,
594  null,
595  ParserOutputAccess::OPT_NO_AUDIENCE_CHECK // we already checked
596  );
597 
598  if ( $pOutput ) {
599  $this->doOutputFromParserCache( $pOutput, $outputPage, $textOptions );
600  $this->doOutputMetaData( $pOutput, $outputPage );
601  return true;
602  }
603  }
604 
605  $rev = $this->fetchRevisionRecord();
606  if ( !$this->fetchResult->isOK() ) {
607  $this->showViewError( $this->fetchResult->getWikiText(
608  false, false, $this->getContext()->getLanguage()
609  ) );
610  return true;
611  }
612 
613  # Are we looking at an old revision
614  if ( $oldid ) {
615  $this->setOldSubtitle( $oldid );
616 
617  if ( !$this->showDeletedRevisionHeader() ) {
618  wfDebug( __METHOD__ . ": cannot view deleted revision" );
619  return false; // skip all further output to OutputPage
620  }
621 
622  // Try the old revision parser cache
623  // NOTE: Repeating cache check for old revision to avoid fetching $rev
624  // before it's absolutely necessary.
625  if ( $useParserCache ) {
626  $pOutput = $parserOutputAccess->getCachedParserOutput(
627  $this->getPage(),
628  $parserOptions,
629  $rev,
630  ParserOutputAccess::OPT_NO_AUDIENCE_CHECK // we already checked in fetchRevisionRevord
631  );
632 
633  if ( $pOutput ) {
634  $this->doOutputFromParserCache( $pOutput, $outputPage, $textOptions );
635  $this->doOutputMetaData( $pOutput, $outputPage );
636  return true;
637  }
638  }
639  }
640 
641  # Ensure that UI elements requiring revision ID have
642  # the correct version information.
643  $outputPage->setRevisionId( $this->getRevIdFetched() );
644  # Preload timestamp to avoid a DB hit
645  $outputPage->setRevisionTimestamp( $rev->getTimestamp() );
646 
647  # Pages containing custom CSS or JavaScript get special treatment
648  if ( $this->getTitle()->isSiteConfigPage() || $this->getTitle()->isUserConfigPage() ) {
649  $dir = $this->getContext()->getLanguage()->getDir();
650  $lang = $this->getContext()->getLanguage()->getHtmlCode();
651 
652  $outputPage->wrapWikiMsg(
653  "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
654  'clearyourcache'
655  );
656  $outputPage->addModuleStyles( 'mediawiki.action.styles' );
657  } elseif ( !$this->getHookRunner()->onArticleRevisionViewCustom(
658  $rev,
659  $this->getTitle(),
660  $oldid,
661  $outputPage )
662  ) {
663  // NOTE: sync with hooks called in DifferenceEngine::renderNewRevision()
664  // Allow extensions do their own custom view for certain pages
665  $this->doOutputMetaData( $pOutput, $outputPage );
666  return true;
667  }
668 
669  # Run the parse, protected by a pool counter
670  wfDebug( __METHOD__ . ": doing uncached parse" );
671 
672  if ( !$rev ) {
673  // No revision, abort! Shouldn't happen.
674  return false;
675  }
676 
677  $opt = 0;
678 
679  // we already checked the cache in case 2, don't check again.
680  $opt |= ParserOutputAccess::OPT_NO_CHECK_CACHE;
681 
682  // we already checked in fetchRevisionRecord()
683  $opt |= ParserOutputAccess::OPT_NO_AUDIENCE_CHECK;
684 
685  if ( !$rev->getId() || !$useParserCache ) {
686  // fake revision or uncacheable options
687  $opt |= ParserOutputAccess::OPT_NO_CACHE;
688  }
689 
690  $renderStatus = $parserOutputAccess->getParserOutput(
691  $this->getPage(),
692  $parserOptions,
693  $rev,
694  $opt
695  );
696 
698  $rev,
699  $renderStatus,
700  $outputPage,
701  $textOptions
702  );
703 
704  if ( !$renderStatus->isOK() ) {
705  return true;
706  }
707 
708  $pOutput = $renderStatus->getValue();
709  $this->doOutputMetaData( $pOutput, $outputPage );
710  return true;
711  }
712 
717  private function doOutputMetaData( ?ParserOutput $pOutput, OutputPage $outputPage ) {
718  # Adjust title for main page & pages with displaytitle
719  if ( $pOutput ) {
720  $this->adjustDisplayTitle( $pOutput );
721  }
722 
723  # Check for any __NOINDEX__ tags on the page using $pOutput
724  $policy = $this->getRobotPolicy( 'view', $pOutput ?: null );
725  $outputPage->setIndexPolicy( $policy['index'] );
726  $outputPage->setFollowPolicy( $policy['follow'] ); // FIXME: test this
727 
728  $this->mParserOutput = $pOutput;
729  }
730 
736  private function doOutputFromParserCache(
737  ParserOutput $pOutput,
738  OutputPage $outputPage,
739  array $textOptions
740  ) {
741  # Ensure that UI elements requiring revision ID have
742  # the correct version information.
743  $outputPage->setRevisionId( $pOutput->getCacheRevisionId() ?? $this->getRevIdFetched() );
744 
745  $outputPage->addParserOutput( $pOutput, $textOptions );
746  # Preload timestamp to avoid a DB hit
747  $cachedTimestamp = $pOutput->getTimestamp();
748  if ( $cachedTimestamp !== null ) {
749  $outputPage->setRevisionTimestamp( $cachedTimestamp );
750  $this->mPage->setTimestamp( $cachedTimestamp );
751  }
752  }
753 
760  private function doOutputFromRenderStatus(
761  ?RevisionRecord $rev,
762  Status $renderStatus,
763  OutputPage $outputPage,
764  array $textOptions
765  ) {
766  global $wgCdnMaxageStale;
767  $ok = $renderStatus->isOK();
768 
769  $pOutput = $ok ? $renderStatus->getValue() : null;
770 
771  // Cache stale ParserOutput object with a short expiry
772  if ( $ok && $renderStatus->hasMessage( 'view-pool-dirty-output' ) ) {
773  $outputPage->setCdnMaxage( $wgCdnMaxageStale );
774  $outputPage->setLastModified( $pOutput->getCacheTime() );
775  $staleReason = $renderStatus->hasMessage( 'view-pool-contention' )
776  ? $this->getContext()->msg( 'view-pool-contention' )
777  : $this->getContext()->msg( 'view-pool-timeout' );
778  $outputPage->addHTML( "<!-- parser cache is expired, " .
779  "sending anyway due to $staleReason-->\n" );
780  }
781 
782  if ( !$renderStatus->isOK() ) {
783  $this->showViewError( $renderStatus->getWikiText(
784  false, 'view-pool-error', $this->getContext()->getLanguage()
785  ) );
786  return;
787  }
788 
789  if ( $pOutput ) {
790  $outputPage->addParserOutput( $pOutput, $textOptions );
791  }
792 
793  if ( $this->getRevisionRedirectTarget( $rev ) ) {
794  $outputPage->addSubtitle( "<span id=\"redirectsub\">" .
795  $this->getContext()->msg( 'redirectpagesub' )->parse() . "</span>" );
796  }
797  }
798 
803  private function getRevisionRedirectTarget( RevisionRecord $revision ) {
804  // TODO: find a *good* place for the code that determines the redirect target for
805  // a given revision!
806  // NOTE: Use main slot content. Compare code in DerivedPageDataUpdater::revisionIsRedirect.
807  $content = $revision->getContent( SlotRecord::MAIN );
808  return $content ? $content->getRedirectTarget() : null;
809  }
810 
815  public function adjustDisplayTitle( ParserOutput $pOutput ) {
816  $out = $this->getContext()->getOutput();
817 
818  # Adjust the title if it was set by displaytitle, -{T|}- or language conversion
819  $titleText = $pOutput->getTitleText();
820  if ( strval( $titleText ) !== '' ) {
821  $out->setPageTitle( $titleText );
822  $out->setDisplayTitle( $titleText );
823  }
824  }
825 
830  protected function showDiffPage() {
831  $request = $this->getContext()->getRequest();
832  $user = $this->getContext()->getUser();
833  $diff = $request->getVal( 'diff' );
834  $rcid = $request->getVal( 'rcid' );
835  $diffOnly = $request->getBool( 'diffonly', $user->getOption( 'diffonly' ) );
836  $purge = $request->getRawVal( 'action' ) === 'purge';
837  $unhide = $request->getInt( 'unhide' ) == 1;
838  $oldid = $this->getOldID();
839 
840  $rev = $this->fetchRevisionRecord();
841 
842  if ( !$rev ) {
843  // T213621: $rev maybe null due to either lack of permission to view the
844  // revision or actually not existing. So let's try loading it from the id
845  $rev = $this->revisionStore->getRevisionById( $oldid );
846  if ( $rev ) {
847  // Revision exists but $user lacks permission to diff it.
848  // Do nothing here.
849  // The $rev will later be used to create standard diff elements however.
850  } else {
851  $this->getContext()->getOutput()->setPageTitle( wfMessage( 'errorpagetitle' ) );
852  $msg = $this->getContext()->msg( 'difference-missing-revision' )
853  ->params( $oldid )
854  ->numParams( 1 )
855  ->parseAsBlock();
856  $this->getContext()->getOutput()->addHTML( $msg );
857  return;
858  }
859  }
860 
861  $contentHandler = MediaWikiServices::getInstance()
862  ->getContentHandlerFactory()
863  ->getContentHandler(
864  $rev->getSlot( SlotRecord::MAIN, RevisionRecord::RAW )->getModel()
865  );
866  $de = $contentHandler->createDifferenceEngine(
867  $this->getContext(),
868  $oldid,
869  $diff,
870  $rcid,
871  $purge,
872  $unhide
873  );
874  $de->setSlotDiffOptions( [
875  'diff-type' => $request->getVal( 'diff-type' )
876  ] );
877  $de->showDiffPage( $diffOnly );
878 
879  // Run view updates for the newer revision being diffed (and shown
880  // below the diff if not $diffOnly).
881  list( $old, $new ) = $de->mapDiffPrevNext( $oldid, $diff );
882  // New can be false, convert it to 0 - this conveniently means the latest revision
883  $this->mPage->doViewUpdates( $user, (int)$new );
884  }
885 
893  public function getRobotPolicy( $action, ParserOutput $pOutput = null ) {
895 
896  $ns = $this->getTitle()->getNamespace();
897 
898  # Don't index user and user talk pages for blocked users (T13443)
899  if ( ( $ns === NS_USER || $ns === NS_USER_TALK ) && !$this->getTitle()->isSubpage() ) {
900  $specificTarget = null;
901  $vagueTarget = null;
902  $titleText = $this->getTitle()->getText();
903  if ( IPUtils::isValid( $titleText ) ) {
904  $vagueTarget = $titleText;
905  } else {
906  $specificTarget = $titleText;
907  }
908  if ( DatabaseBlock::newFromTarget( $specificTarget, $vagueTarget ) instanceof DatabaseBlock ) {
909  return [
910  'index' => 'noindex',
911  'follow' => 'nofollow'
912  ];
913  }
914  }
915 
916  if ( $this->mPage->getId() === 0 || $this->getOldID() ) {
917  # Non-articles (special pages etc), and old revisions
918  return [
919  'index' => 'noindex',
920  'follow' => 'nofollow'
921  ];
922  } elseif ( $this->getContext()->getOutput()->isPrintable() ) {
923  # Discourage indexing of printable versions, but encourage following
924  return [
925  'index' => 'noindex',
926  'follow' => 'follow'
927  ];
928  } elseif ( $this->getContext()->getRequest()->getInt( 'curid' ) ) {
929  # For ?curid=x urls, disallow indexing
930  return [
931  'index' => 'noindex',
932  'follow' => 'follow'
933  ];
934  }
935 
936  # Otherwise, construct the policy based on the various config variables.
938 
939  if ( isset( $wgNamespaceRobotPolicies[$ns] ) ) {
940  # Honour customised robot policies for this namespace
941  $policy = array_merge(
942  $policy,
943  self::formatRobotPolicy( $wgNamespaceRobotPolicies[$ns] )
944  );
945  }
946  if ( $this->getTitle()->canUseNoindex() && is_object( $pOutput ) && $pOutput->getIndexPolicy() ) {
947  # __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates
948  # a final sanity check that we have really got the parser output.
949  $policy = array_merge(
950  $policy,
951  [ 'index' => $pOutput->getIndexPolicy() ]
952  );
953  }
954 
955  if ( isset( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] ) ) {
956  # (T16900) site config can override user-defined __INDEX__ or __NOINDEX__
957  $policy = array_merge(
958  $policy,
959  self::formatRobotPolicy( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] )
960  );
961  }
962 
963  return $policy;
964  }
965 
973  public static function formatRobotPolicy( $policy ) {
974  if ( is_array( $policy ) ) {
975  return $policy;
976  } elseif ( !$policy ) {
977  return [];
978  }
979 
980  $policy = explode( ',', $policy );
981  $policy = array_map( 'trim', $policy );
982 
983  $arr = [];
984  foreach ( $policy as $var ) {
985  if ( in_array( $var, [ 'index', 'noindex' ] ) ) {
986  $arr['index'] = $var;
987  } elseif ( in_array( $var, [ 'follow', 'nofollow' ] ) ) {
988  $arr['follow'] = $var;
989  }
990  }
991 
992  return $arr;
993  }
994 
1002  public function showRedirectedFromHeader() {
1003  global $wgRedirectSources;
1004 
1005  $context = $this->getContext();
1006  $outputPage = $context->getOutput();
1007  $request = $context->getRequest();
1008  $rdfrom = $request->getVal( 'rdfrom' );
1009 
1010  // Construct a URL for the current page view, but with the target title
1011  $query = $request->getValues();
1012  unset( $query['rdfrom'] );
1013  unset( $query['title'] );
1014  if ( $this->getTitle()->isRedirect() ) {
1015  // Prevent double redirects
1016  $query['redirect'] = 'no';
1017  }
1018  $redirectTargetUrl = $this->getTitle()->getLinkURL( $query );
1019 
1020  if ( isset( $this->mRedirectedFrom ) ) {
1021  // This is an internally redirected page view.
1022  // We'll need a backlink to the source page for navigation.
1023  if ( $this->getHookRunner()->onArticleViewRedirect( $this ) ) {
1024  $redir = $this->linkRenderer->makeKnownLink(
1025  $this->mRedirectedFrom,
1026  null,
1027  [],
1028  [ 'redirect' => 'no' ]
1029  );
1030 
1031  $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
1032  $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
1033  . "</span>" );
1034 
1035  // Add the script to update the displayed URL and
1036  // set the fragment if one was specified in the redirect
1037  $outputPage->addJsConfigVars( [
1038  'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1039  ] );
1040  $outputPage->addModules( 'mediawiki.action.view.redirect' );
1041 
1042  // Add a <link rel="canonical"> tag
1043  $outputPage->setCanonicalUrl( $this->getTitle()->getCanonicalURL() );
1044 
1045  // Tell the output object that the user arrived at this article through a redirect
1046  $outputPage->setRedirectedFrom( $this->mRedirectedFrom );
1047 
1048  return true;
1049  }
1050  } elseif ( $rdfrom ) {
1051  // This is an externally redirected view, from some other wiki.
1052  // If it was reported from a trusted site, supply a backlink.
1053  if ( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) {
1054  $redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
1055  $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
1056  $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
1057  . "</span>" );
1058 
1059  // Add the script to update the displayed URL
1060  $outputPage->addJsConfigVars( [
1061  'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1062  ] );
1063  $outputPage->addModules( 'mediawiki.action.view.redirect' );
1064 
1065  return true;
1066  }
1067  }
1068 
1069  return false;
1070  }
1071 
1076  public function showNamespaceHeader() {
1077  if ( $this->getTitle()->isTalkPage() && !wfMessage( 'talkpageheader' )->isDisabled() ) {
1078  $this->getContext()->getOutput()->wrapWikiMsg(
1079  "<div class=\"mw-talkpageheader\">\n$1\n</div>",
1080  [ 'talkpageheader' ]
1081  );
1082  }
1083  }
1084 
1088  public function showViewFooter() {
1089  # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
1090  if ( $this->getTitle()->getNamespace() === NS_USER_TALK
1091  && IPUtils::isValid( $this->getTitle()->getText() )
1092  ) {
1093  $this->getContext()->getOutput()->addWikiMsg( 'anontalkpagetext' );
1094  }
1095 
1096  // Show a footer allowing the user to patrol the shown revision or page if possible
1097  $patrolFooterShown = $this->showPatrolFooter();
1098 
1099  $this->getHookRunner()->onArticleViewFooter( $this, $patrolFooterShown );
1100  }
1101 
1112  public function showPatrolFooter() {
1114 
1115  // Allow hooks to decide whether to not output this at all
1116  if ( !$this->getHookRunner()->onArticleShowPatrolFooter( $this ) ) {
1117  return false;
1118  }
1119 
1120  $outputPage = $this->getContext()->getOutput();
1121  $user = $this->getContext()->getUser();
1122  $title = $this->getTitle();
1123  $rc = false;
1124 
1125  if ( !$this->getContext()->getAuthority()->probablyCan( 'patrol', $title )
1127  || ( $wgUseFilePatrol && $title->inNamespace( NS_FILE ) ) )
1128  ) {
1129  // Patrolling is disabled or the user isn't allowed to
1130  return false;
1131  }
1132 
1133  if ( $this->mRevisionRecord
1134  && !RecentChange::isInRCLifespan( $this->mRevisionRecord->getTimestamp(), 21600 )
1135  ) {
1136  // The current revision is already older than what could be in the RC table
1137  // 6h tolerance because the RC might not be cleaned out regularly
1138  return false;
1139  }
1140 
1141  // Check for cached results
1142  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1143  $key = $cache->makeKey( 'unpatrollable-page', $title->getArticleID() );
1144  if ( $cache->get( $key ) ) {
1145  return false;
1146  }
1147 
1148  $dbr = wfGetDB( DB_REPLICA );
1149  $oldestRevisionTimestamp = $dbr->selectField(
1150  'revision',
1151  'MIN( rev_timestamp )',
1152  [ 'rev_page' => $title->getArticleID() ],
1153  __METHOD__
1154  );
1155 
1156  // New page patrol: Get the timestamp of the oldest revison which
1157  // the revision table holds for the given page. Then we look
1158  // whether it's within the RC lifespan and if it is, we try
1159  // to get the recentchanges row belonging to that entry
1160  // (with rc_new = 1).
1161  $recentPageCreation = false;
1162  if ( $oldestRevisionTimestamp
1163  && RecentChange::isInRCLifespan( $oldestRevisionTimestamp, 21600 )
1164  ) {
1165  // 6h tolerance because the RC might not be cleaned out regularly
1166  $recentPageCreation = true;
1168  [
1169  'rc_new' => 1,
1170  'rc_timestamp' => $oldestRevisionTimestamp,
1171  'rc_namespace' => $title->getNamespace(),
1172  'rc_cur_id' => $title->getArticleID()
1173  ],
1174  __METHOD__
1175  );
1176  if ( $rc ) {
1177  // Use generic patrol message for new pages
1178  $markPatrolledMsg = wfMessage( 'markaspatrolledtext' );
1179  }
1180  }
1181 
1182  // File patrol: Get the timestamp of the latest upload for this page,
1183  // check whether it is within the RC lifespan and if it is, we try
1184  // to get the recentchanges row belonging to that entry
1185  // (with rc_type = RC_LOG, rc_log_type = upload).
1186  $recentFileUpload = false;
1187  if ( ( !$rc || $rc->getAttribute( 'rc_patrolled' ) ) && $wgUseFilePatrol
1188  && $title->getNamespace() === NS_FILE ) {
1189  // Retrieve timestamp of most recent upload
1190  $newestUploadTimestamp = $dbr->selectField(
1191  'image',
1192  'MAX( img_timestamp )',
1193  [ 'img_name' => $title->getDBkey() ],
1194  __METHOD__
1195  );
1196  if ( $newestUploadTimestamp
1197  && RecentChange::isInRCLifespan( $newestUploadTimestamp, 21600 )
1198  ) {
1199  // 6h tolerance because the RC might not be cleaned out regularly
1200  $recentFileUpload = true;
1202  [
1203  'rc_type' => RC_LOG,
1204  'rc_log_type' => 'upload',
1205  'rc_timestamp' => $newestUploadTimestamp,
1206  'rc_namespace' => NS_FILE,
1207  'rc_cur_id' => $title->getArticleID()
1208  ],
1209  __METHOD__
1210  );
1211  if ( $rc ) {
1212  // Use patrol message specific to files
1213  $markPatrolledMsg = wfMessage( 'markaspatrolledtext-file' );
1214  }
1215  }
1216  }
1217 
1218  if ( !$recentPageCreation && !$recentFileUpload ) {
1219  // Page creation and latest upload (for files) is too old to be in RC
1220 
1221  // We definitely can't patrol so cache the information
1222  // When a new file version is uploaded, the cache is cleared
1223  $cache->set( $key, '1' );
1224 
1225  return false;
1226  }
1227 
1228  if ( !$rc ) {
1229  // Don't cache: This can be hit if the page gets accessed very fast after
1230  // its creation / latest upload or in case we have high replica DB lag. In case
1231  // the revision is too old, we will already return above.
1232  return false;
1233  }
1234 
1235  if ( $rc->getAttribute( 'rc_patrolled' ) ) {
1236  // Patrolled RC entry around
1237 
1238  // Cache the information we gathered above in case we can't patrol
1239  // Don't cache in case we can patrol as this could change
1240  $cache->set( $key, '1' );
1241 
1242  return false;
1243  }
1244 
1245  if ( $rc->getPerformerIdentity()->equals( $user ) ) {
1246  // Don't show a patrol link for own creations/uploads. If the user could
1247  // patrol them, they already would be patrolled
1248  return false;
1249  }
1250 
1251  $outputPage->preventClickjacking();
1252  if ( $this->getContext()->getAuthority()->isAllowed( 'writeapi' ) ) {
1253  $outputPage->addModules( 'mediawiki.misc-authed-curate' );
1254  }
1255 
1256  $link = $this->linkRenderer->makeKnownLink(
1257  $title,
1258  $markPatrolledMsg->text(),
1259  [],
1260  [
1261  'action' => 'markpatrolled',
1262  'rcid' => $rc->getAttribute( 'rc_id' ),
1263  ]
1264  );
1265 
1266  $outputPage->addModuleStyles( 'mediawiki.action.styles' );
1267  $outputPage->addHTML(
1268  "<div class='patrollink' data-mw='interface'>" .
1269  wfMessage( 'markaspatrolledlink' )->rawParams( $link )->escaped() .
1270  '</div>'
1271  );
1272 
1273  return true;
1274  }
1275 
1282  public static function purgePatrolFooterCache( $articleID ) {
1283  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1284  $cache->delete( $cache->makeKey( 'unpatrollable-page', $articleID ) );
1285  }
1286 
1291  public function showMissingArticle() {
1292  global $wgSend404Code;
1293 
1294  $outputPage = $this->getContext()->getOutput();
1295  // Whether the page is a root user page of an existing user (but not a subpage)
1296  $validUserPage = false;
1297 
1298  $title = $this->getTitle();
1299 
1300  $services = MediaWikiServices::getInstance();
1301 
1302  $contextUser = $this->getContext()->getUser();
1303 
1304  # Show info in user (talk) namespace. Does the user exist? Is he blocked?
1305  if ( $title->getNamespace() === NS_USER
1306  || $title->getNamespace() === NS_USER_TALK
1307  ) {
1308  $rootPart = explode( '/', $title->getText() )[0];
1309  $user = User::newFromName( $rootPart, false /* allow IP users */ );
1310  $ip = $this->userNameUtils->isIP( $rootPart );
1311  $block = DatabaseBlock::newFromTarget( $user, $user );
1312 
1313  if ( $user && $user->isRegistered() && $user->isHidden() &&
1314  !$this->getContext()->getAuthority()->isAllowed( 'hideuser' )
1315  ) {
1316  // T120883 if the user is hidden and the viewer cannot see hidden
1317  // users, pretend like it does not exist at all.
1318  $user = false;
1319  }
1320 
1321  if ( !( $user && $user->isRegistered() ) && !$ip ) { # User does not exist
1322  $outputPage->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
1323  [ 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) ] );
1324  } elseif (
1325  $block !== null &&
1326  $block->getType() != DatabaseBlock::TYPE_AUTO &&
1327  (
1328  $block->isSitewide() ||
1329  $user->isBlockedFrom( $title, true )
1330  )
1331  ) {
1332  // Show log extract if the user is sitewide blocked or is partially
1333  // blocked and not allowed to edit their user page or user talk page
1335  $outputPage,
1336  'block',
1337  $services->getNamespaceInfo()->getCanonicalName( NS_USER ) . ':' .
1338  $block->getTargetName(),
1339  '',
1340  [
1341  'lim' => 1,
1342  'showIfEmpty' => false,
1343  'msgKey' => [
1344  'blocked-notice-logextract',
1345  $user->getName() # Support GENDER in notice
1346  ]
1347  ]
1348  );
1349  $validUserPage = !$title->isSubpage();
1350  } else {
1351  $validUserPage = !$title->isSubpage();
1352  }
1353  }
1354 
1355  $this->getHookRunner()->onShowMissingArticle( $this );
1356 
1357  # Show delete and move logs if there were any such events.
1358  # The logging query can DOS the site when bots/crawlers cause 404 floods,
1359  # so be careful showing this. 404 pages must be cheap as they are hard to cache.
1360  $dbCache = ObjectCache::getInstance( 'db-replicated' );
1361  $key = $dbCache->makeKey( 'page-recent-delete', md5( $title->getPrefixedText() ) );
1362  $isRegistered = $contextUser->isRegistered();
1363  $sessionExists = $this->getContext()->getRequest()->getSession()->isPersistent();
1364 
1365  if ( $isRegistered || $dbCache->get( $key ) || $sessionExists ) {
1366  $logTypes = [ 'delete', 'move', 'protect' ];
1367 
1368  $dbr = wfGetDB( DB_REPLICA );
1369 
1370  $conds = [ 'log_action != ' . $dbr->addQuotes( 'revision' ) ];
1371  // Give extensions a chance to hide their (unrelated) log entries
1372  $this->getHookRunner()->onArticle__MissingArticleConditions( $conds, $logTypes );
1374  $outputPage,
1375  $logTypes,
1376  $title,
1377  '',
1378  [
1379  'lim' => 10,
1380  'conds' => $conds,
1381  'showIfEmpty' => false,
1382  'msgKey' => [ $isRegistered || $sessionExists
1383  ? 'moveddeleted-notice'
1384  : 'moveddeleted-notice-recent'
1385  ]
1386  ]
1387  );
1388  }
1389 
1390  if ( !$this->mPage->hasViewableContent() && $wgSend404Code && !$validUserPage ) {
1391  // If there's no backing content, send a 404 Not Found
1392  // for better machine handling of broken links.
1393  $this->getContext()->getRequest()->response()->statusHeader( 404 );
1394  }
1395 
1396  // Also apply the robot policy for nonexisting pages (even if a 404 was used for sanity)
1397  $policy = $this->getRobotPolicy( 'view' );
1398  $outputPage->setIndexPolicy( $policy['index'] );
1399  $outputPage->setFollowPolicy( $policy['follow'] );
1400 
1401  $hookResult = $this->getHookRunner()->onBeforeDisplayNoArticleText( $this );
1402 
1403  if ( !$hookResult ) {
1404  return;
1405  }
1406 
1407  # Show error message
1408  $oldid = $this->getOldID();
1409  if ( !$oldid && $title->getNamespace() === NS_MEDIAWIKI && $title->hasSourceText() ) {
1410  $text = $this->getTitle()->getDefaultMessageText() ?? '';
1411  $outputPage->addWikiTextAsContent( $text );
1412  } else {
1413  if ( $oldid ) {
1414  // T251066: Try loading the revision from the archive table.
1415  // Show link to view it if it exists and the user has permission to view it.
1416  $pa = new PageArchive( $title, $this->getContext()->getConfig() );
1417  $revRecord = $pa->getArchivedRevisionRecord( $oldid );
1418  if ( $revRecord && $revRecord->userCan(
1419  RevisionRecord::DELETED_TEXT,
1420  $this->getContext()->getAuthority()
1421  ) ) {
1422  $text = wfMessage(
1423  'missing-revision-permission', $oldid,
1424  $revRecord->getTimestamp(),
1425  $title->getPrefixedDBkey()
1426  )->plain();
1427  } else {
1428  $text = wfMessage( 'missing-revision', $oldid )->plain();
1429  }
1430 
1431  } elseif ( $this->getContext()->getAuthority()->probablyCan( 'edit', $title )
1432  ) {
1433  $message = $isRegistered ? 'noarticletext' : 'noarticletextanon';
1434  $text = wfMessage( $message )->plain();
1435  } else {
1436  $text = wfMessage( 'noarticletext-nopermission' )->plain();
1437  }
1438 
1439  $dir = $this->getContext()->getLanguage()->getDir();
1440  $lang = $this->getContext()->getLanguage()->getHtmlCode();
1441  $outputPage->addWikiTextAsInterface( Xml::openElement( 'div', [
1442  'class' => "noarticletext mw-content-$dir",
1443  'dir' => $dir,
1444  'lang' => $lang,
1445  ] ) . "\n$text\n</div>" );
1446  }
1447  }
1448 
1453  private function showViewError( string $errortext ) {
1454  $outputPage = $this->getContext()->getOutput();
1455  $outputPage->setPageTitle( $this->getContext()->msg( 'errorpagetitle' ) );
1456  $outputPage->enableClientCache( false );
1457  $outputPage->setRobotPolicy( 'noindex,nofollow' );
1458  $outputPage->clearHTML();
1459  $outputPage->wrapWikiTextAsInterface( 'errorbox', $errortext );
1460  }
1461 
1468  public function showDeletedRevisionHeader() {
1469  if ( !$this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1470  // Not deleted
1471  return true;
1472  }
1473  $outputPage = $this->getContext()->getOutput();
1474  $user = $this->getContext()->getUser();
1475  // Used in wikilinks, should not contain whitespaces
1476  $titleText = $this->getTitle()->getPrefixedDBkey();
1477  // If the user is not allowed to see it...
1478  if ( !$this->mRevisionRecord->userCan(
1479  RevisionRecord::DELETED_TEXT,
1480  $this->getContext()->getAuthority()
1481  ) ) {
1482  $outputPage->addHtml(
1484  $outputPage->msg( 'rev-deleted-text-permission', $titleText )->parse(),
1485  'plainlinks'
1486  )
1487  );
1488 
1489  return false;
1490  // If the user needs to confirm that they want to see it...
1491  } elseif ( $this->getContext()->getRequest()->getInt( 'unhide' ) != 1 ) {
1492  # Give explanation and add a link to view the revision...
1493  $oldid = intval( $this->getOldID() );
1494  $link = $this->getTitle()->getFullURL( "oldid={$oldid}&unhide=1" );
1495  $msg = $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ?
1496  'rev-suppressed-text-unhide' : 'rev-deleted-text-unhide';
1497  $outputPage->addHtml(
1499  $outputPage->msg( $msg, $link )->parse(),
1500  'plainlinks'
1501  )
1502  );
1503 
1504  return false;
1505  // We are allowed to see...
1506  } else {
1507  $msg = $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED )
1508  ? [ 'rev-suppressed-text-view', $titleText ]
1509  : [ 'rev-deleted-text-view', $titleText ];
1510  $outputPage->addHtml(
1512  $outputPage->msg( $msg[0], $msg[1] )->parse(),
1513  'plainlinks'
1514  )
1515  );
1516 
1517  return true;
1518  }
1519  }
1520 
1529  public function setOldSubtitle( $oldid = 0 ) {
1530  if ( !$this->getHookRunner()->onDisplayOldSubtitle( $this, $oldid ) ) {
1531  return;
1532  }
1533 
1534  $context = $this->getContext();
1535  $unhide = $context->getRequest()->getInt( 'unhide' ) == 1;
1536 
1537  # Cascade unhide param in links for easy deletion browsing
1538  $extraParams = [];
1539  if ( $unhide ) {
1540  $extraParams['unhide'] = 1;
1541  }
1542 
1543  if ( $this->mRevisionRecord && $this->mRevisionRecord->getId() === $oldid ) {
1544  $revisionRecord = $this->mRevisionRecord;
1545  } else {
1546  $revisionRecord = $this->revisionStore->getRevisionById( $oldid );
1547  }
1548 
1549  $timestamp = $revisionRecord->getTimestamp();
1550 
1551  $current = ( $oldid == $this->mPage->getLatest() );
1552  $language = $context->getLanguage();
1553  $user = $context->getUser();
1554 
1555  $td = $language->userTimeAndDate( $timestamp, $user );
1556  $tddate = $language->userDate( $timestamp, $user );
1557  $tdtime = $language->userTime( $timestamp, $user );
1558 
1559  # Show user links if allowed to see them. If hidden, then show them only if requested...
1560  $userlinks = Linker::revUserTools( $revisionRecord, !$unhide );
1561 
1562  $infomsg = $current && !$context->msg( 'revision-info-current' )->isDisabled()
1563  ? 'revision-info-current'
1564  : 'revision-info';
1565 
1566  $outputPage = $context->getOutput();
1567  $outputPage->addModuleStyles( [
1568  'mediawiki.action.styles',
1569  'mediawiki.interface.helpers.styles'
1570  ] );
1571 
1572  $revisionUser = $revisionRecord->getUser();
1573  $revisionInfo = "<div id=\"mw-{$infomsg}\">" .
1574  $context->msg( $infomsg, $td )
1575  ->rawParams( $userlinks )
1576  ->params(
1577  $revisionRecord->getId(),
1578  $tddate,
1579  $tdtime,
1580  $revisionUser ? $revisionUser->getName() : ''
1581  )
1582  ->rawParams( Linker::revComment(
1583  $revisionRecord,
1584  true,
1585  true
1586  ) )
1587  ->parse() .
1588  "</div>";
1589 
1590  $lnk = $current
1591  ? $context->msg( 'currentrevisionlink' )->escaped()
1592  : $this->linkRenderer->makeKnownLink(
1593  $this->getTitle(),
1594  $context->msg( 'currentrevisionlink' )->text(),
1595  [],
1596  $extraParams
1597  );
1598  $curdiff = $current
1599  ? $context->msg( 'diff' )->escaped()
1600  : $this->linkRenderer->makeKnownLink(
1601  $this->getTitle(),
1602  $context->msg( 'diff' )->text(),
1603  [],
1604  [
1605  'diff' => 'cur',
1606  'oldid' => $oldid
1607  ] + $extraParams
1608  );
1609  $prevExist = (bool)$this->revisionStore->getPreviousRevision( $revisionRecord );
1610  $prevlink = $prevExist
1611  ? $this->linkRenderer->makeKnownLink(
1612  $this->getTitle(),
1613  $context->msg( 'previousrevision' )->text(),
1614  [],
1615  [
1616  'direction' => 'prev',
1617  'oldid' => $oldid
1618  ] + $extraParams
1619  )
1620  : $context->msg( 'previousrevision' )->escaped();
1621  $prevdiff = $prevExist
1622  ? $this->linkRenderer->makeKnownLink(
1623  $this->getTitle(),
1624  $context->msg( 'diff' )->text(),
1625  [],
1626  [
1627  'diff' => 'prev',
1628  'oldid' => $oldid
1629  ] + $extraParams
1630  )
1631  : $context->msg( 'diff' )->escaped();
1632  $nextlink = $current
1633  ? $context->msg( 'nextrevision' )->escaped()
1634  : $this->linkRenderer->makeKnownLink(
1635  $this->getTitle(),
1636  $context->msg( 'nextrevision' )->text(),
1637  [],
1638  [
1639  'direction' => 'next',
1640  'oldid' => $oldid
1641  ] + $extraParams
1642  );
1643  $nextdiff = $current
1644  ? $context->msg( 'diff' )->escaped()
1645  : $this->linkRenderer->makeKnownLink(
1646  $this->getTitle(),
1647  $context->msg( 'diff' )->text(),
1648  [],
1649  [
1650  'diff' => 'next',
1651  'oldid' => $oldid
1652  ] + $extraParams
1653  );
1654 
1655  $cdel = Linker::getRevDeleteLink(
1656  $user,
1657  $revisionRecord,
1658  $this->getTitle()
1659  );
1660  if ( $cdel !== '' ) {
1661  $cdel .= ' ';
1662  }
1663 
1664  // the outer div is need for styling the revision info and nav in MobileFrontend
1665  $outputPage->addSubtitle( "<div class=\"mw-revision warningbox\">" . $revisionInfo .
1666  "<div id=\"mw-revision-nav\">" . $cdel .
1667  $context->msg( 'revision-nav' )->rawParams(
1668  $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff
1669  )->escaped() . "</div></div>" );
1670  }
1671 
1685  public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) {
1686  $lang = $this->getTitle()->getPageLanguage();
1687  $out = $this->getContext()->getOutput();
1688  if ( $appendSubtitle ) {
1689  $out->addSubtitle( wfMessage( 'redirectpagesub' ) );
1690  }
1691  $out->addModuleStyles( 'mediawiki.action.view.redirectPage' );
1692  return static::getRedirectHeaderHtml( $lang, $target, $forceKnown );
1693  }
1694 
1707  public static function getRedirectHeaderHtml( Language $lang, $target, $forceKnown = false ) {
1708  if ( !is_array( $target ) ) {
1709  $target = [ $target ];
1710  }
1711 
1712  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1713 
1714  $html = '<ul class="redirectText">';
1716  foreach ( $target as $title ) {
1717  if ( $forceKnown ) {
1718  $link = $linkRenderer->makeKnownLink(
1719  $title,
1720  $title->getFullText(),
1721  [],
1722  // Make sure wiki page redirects are not followed
1723  $title->isRedirect() ? [ 'redirect' => 'no' ] : []
1724  );
1725  } else {
1726  $link = $linkRenderer->makeLink(
1727  $title,
1728  $title->getFullText(),
1729  [],
1730  // Make sure wiki page redirects are not followed
1731  $title->isRedirect() ? [ 'redirect' => 'no' ] : []
1732  );
1733  }
1734  $html .= '<li>' . $link . '</li>';
1735  }
1736  $html .= '</ul>';
1737 
1738  $redirectToText = wfMessage( 'redirectto' )->inLanguage( $lang )->escaped();
1739 
1740  return '<div class="redirectMsg">' .
1741  '<p>' . $redirectToText . '</p>' .
1742  $html .
1743  '</div>';
1744  }
1745 
1754  public function addHelpLink( $to, $overrideBaseUrl = false ) {
1755  $out = $this->getContext()->getOutput();
1756  $msg = $out->msg( 'namespace-' . $this->getTitle()->getNamespace() . '-helppage' );
1757 
1758  if ( !$msg->isDisabled() ) {
1759  $title = Title::newFromText( $msg->plain() );
1760  if ( $title instanceof Title ) {
1761  $out->addHelpLink( $title->getLocalURL(), true );
1762  }
1763  } else {
1764  $out->addHelpLink( $to, $overrideBaseUrl );
1765  }
1766  }
1767 
1771  public function render() {
1772  $this->getContext()->getRequest()->response()->header( 'X-Robots-Tag: noindex' );
1773  $this->getContext()->getOutput()->setArticleBodyOnly( true );
1774  // We later set 'enableSectionEditLinks=false' based on this; also used by ImagePage
1775  $this->viewIsRenderAction = true;
1776  $this->view();
1777  }
1778 
1782  public function protect() {
1783  $form = new ProtectionForm( $this );
1784  $form->execute();
1785  }
1786 
1790  public function unprotect() {
1791  $this->protect();
1792  }
1793 
1802  public function doDelete( $reason, $suppress = false, $immediate = false ) {
1803  $error = '';
1804  $context = $this->getContext();
1805  $outputPage = $context->getOutput();
1806  $user = $context->getUser();
1807  $status = $this->mPage->doDeleteArticleReal(
1808  $reason, $user, $suppress, null, $error,
1809  null, [], 'delete', $immediate
1810  );
1811 
1812  if ( $status->isOK() ) {
1813  $deleted = $this->getTitle()->getPrefixedText();
1814 
1815  $outputPage->setPageTitle( wfMessage( 'actioncomplete' ) );
1816  $outputPage->setRobotPolicy( 'noindex,nofollow' );
1817 
1818  if ( $status->isGood() ) {
1819  $loglink = '[[Special:Log/delete|' . wfMessage( 'deletionlog' )->text() . ']]';
1820  $outputPage->addWikiMsg( 'deletedtext', wfEscapeWikiText( $deleted ), $loglink );
1821  $this->getHookRunner()->onArticleDeleteAfterSuccess( $this->getTitle(), $outputPage );
1822  } else {
1823  $outputPage->addWikiMsg( 'delete-scheduled', wfEscapeWikiText( $deleted ) );
1824  }
1825 
1826  $outputPage->returnToMain( false );
1827  } else {
1828  $outputPage->setPageTitle(
1829  wfMessage( 'cannotdelete-title',
1830  $this->getTitle()->getPrefixedText() )
1831  );
1832 
1833  if ( $error == '' ) {
1834  $outputPage->wrapWikiTextAsInterface(
1835  'error mw-error-cannotdelete',
1836  $status->getWikiText( false, false, $context->getLanguage() )
1837  );
1838  $deleteLogPage = new LogPage( 'delete' );
1839  $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
1840 
1842  $outputPage,
1843  'delete',
1844  $this->getTitle()
1845  );
1846  } else {
1847  $outputPage->addHTML( $error );
1848  }
1849  }
1850  }
1851 
1852  /* Caching functions */
1853 
1861  protected function tryFileCache() {
1862  static $called = false;
1863 
1864  if ( $called ) {
1865  wfDebug( "Article::tryFileCache(): called twice!?" );
1866  return false;
1867  }
1868 
1869  $called = true;
1870  if ( $this->isFileCacheable() ) {
1871  $cache = new HTMLFileCache( $this->getTitle(), 'view' );
1872  if ( $cache->isCacheGood( $this->mPage->getTouched() ) ) {
1873  wfDebug( "Article::tryFileCache(): about to load file" );
1874  $cache->loadFromFileCache( $this->getContext() );
1875  return true;
1876  } else {
1877  wfDebug( "Article::tryFileCache(): starting buffer" );
1878  ob_start( [ &$cache, 'saveToFileCache' ] );
1879  }
1880  } else {
1881  wfDebug( "Article::tryFileCache(): not cacheable" );
1882  }
1883 
1884  return false;
1885  }
1886 
1892  public function isFileCacheable( $mode = HTMLFileCache::MODE_NORMAL ) {
1893  $cacheable = false;
1894 
1895  if ( HTMLFileCache::useFileCache( $this->getContext(), $mode ) ) {
1896  $cacheable = $this->mPage->getId()
1897  && !$this->mRedirectedFrom && !$this->getTitle()->isRedirect();
1898  // Extension may have reason to disable file caching on some pages.
1899  if ( $cacheable ) {
1900  $cacheable = $this->getHookRunner()->onIsFileCacheable( $this );
1901  }
1902  }
1903 
1904  return $cacheable;
1905  }
1906 
1920  public function getParserOutput( $oldid = null, UserIdentity $user = null ) {
1921  if ( $user === null ) {
1922  $parserOptions = $this->getParserOptions();
1923  } else {
1924  $parserOptions = $this->mPage->makeParserOptions( $user );
1925  }
1926 
1927  return $this->mPage->getParserOutput( $parserOptions, $oldid );
1928  }
1929 
1934  public function getParserOptions() {
1935  return $this->mPage->makeParserOptions( $this->getContext() );
1936  }
1937 
1944  public function setContext( $context ) {
1945  $this->mContext = $context;
1946  }
1947 
1954  public function getContext() {
1955  if ( $this->mContext instanceof IContextSource ) {
1956  return $this->mContext;
1957  } else {
1958  wfDebug( __METHOD__ . " called and \$mContext is null. " .
1959  "Return RequestContext::getMain(); for sanity" );
1960  return RequestContext::getMain();
1961  }
1962  }
1963 
1973  public function __get( $fname ) {
1974  wfDeprecatedMsg( "Accessing Article::\$$fname is deprecated since MediaWiki 1.35",
1975  '1.35' );
1976 
1977  if ( property_exists( $this->mPage, $fname ) ) {
1978  return $this->mPage->$fname;
1979  }
1980  trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
1981  }
1982 
1992  public function __set( $fname, $fvalue ) {
1993  wfDeprecatedMsg( "Setting Article::\$$fname is deprecated since MediaWiki 1.35",
1994  '1.35' );
1995 
1996  if ( property_exists( $this->mPage, $fname ) ) {
1997  $this->mPage->$fname = $fvalue;
1998  // Note: extensions may want to toss on new fields
1999  } elseif ( !in_array( $fname, [ 'mContext', 'mPage' ] ) ) {
2000  $this->mPage->$fname = $fvalue;
2001  } else {
2002  trigger_error( 'Inaccessible property via __set(): ' . $fname, E_USER_NOTICE );
2003  }
2004  }
2005 
2011  public function getActionOverrides() {
2012  return $this->mPage->getActionOverrides();
2013  }
2014 
2020  public function getTimestamp() {
2021  wfDeprecated( __METHOD__, '1.35' );
2022  return $this->mPage->getTimestamp();
2023  }
2024 }
$wgSend404Code
$wgSend404Code
Some web hosts attempt to rewrite all responses with a 404 (not found) status code,...
Definition: DefaultSettings.php:3958
Article\showMissingArticle
showMissingArticle()
Show the error text for a missing article.
Definition: Article.php:1291
$wgCdnMaxageStale
$wgCdnMaxageStale
Cache timeout when delivering a stale ParserCache response due to PoolCounter contention.
Definition: DefaultSettings.php:3271
ParserOptions
Set options of the Parser.
Definition: ParserOptions.php:45
HTMLFileCache\useFileCache
static useFileCache(IContextSource $context, $mode=self::MODE_NORMAL)
Check if pages can be cached for this request/user.
Definition: HTMLFileCache.php:94
Article\$mContext
IContextSource null $mContext
The context this Article is executed in.
Definition: Article.php:58
Page
Interface for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
Definition: Page.php:29
MediaWiki\Revision\RevisionRecord\getContent
getContent( $role, $audience=self::FOR_PUBLIC, Authority $performer=null)
Returns the Content of the given slot of this revision.
Definition: RevisionRecord.php:156
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:383
MediaWiki\Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:47
Article\doOutputMetaData
doOutputMetaData(?ParserOutput $pOutput, OutputPage $outputPage)
Definition: Article.php:717
OutputPage\addSubtitle
addSubtitle( $str)
Add $str to the subtitle.
Definition: OutputPage.php:1076
ParserOutput
Definition: ParserOutput.php:31
Article\formatRobotPolicy
static formatRobotPolicy( $policy)
Converts a String robot policy into an associative array, to allow merging of several policies using ...
Definition: Article.php:973
StatusValue\newFatal
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:70
NS_MEDIAWIKI
const NS_MEDIAWIKI
Definition: Defines.php:72
Article\$viewIsRenderAction
bool $viewIsRenderAction
Whether render() was called.
Definition: Article.php:93
Article\view
view()
This is the default action of the index.php entry point: just view the page of the given title.
Definition: Article.php:427
Linker\revUserTools
static revUserTools(RevisionRecord $revRecord, $isPublic=false, $useParentheses=true)
Generate a user tool link cluster if the current user is allowed to view it.
Definition: Linker.php:1319
PageArchive
Used to show archived pages and eventually restore them.
Definition: PageArchive.php:37
Article\generateContentOutput
generateContentOutput(Authority $performer, ParserOptions $parserOptions, int $oldid, OutputPage $outputPage, array $textOptions)
Determines the desired ParserOutput and passes it to $outputPage.
Definition: Article.php:555
Article\getOldIDFromRequest
getOldIDFromRequest()
Sets $this->mRedirectUrl to a correct URL if the query parameters are incorrect.
Definition: Article.php:269
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:193
Article\showPatrolFooter
showPatrolFooter()
If patrol is possible, output a patrol UI box.
Definition: Article.php:1112
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:37
RecentChange\newFromConds
static newFromConds( $conds, $fname=__METHOD__, $dbType=DB_REPLICA)
Find the first recent change matching some specific conditions.
Definition: RecentChange.php:226
Article\tryFileCache
tryFileCache()
checkLastModified returns true if it has taken care of all output to the client that is necessary for...
Definition: Article.php:1861
OutputPage\addModuleStyles
addModuleStyles( $modules)
Load the styles of one or more style-only ResourceLoader modules on this page.
Definition: OutputPage.php:588
Article\$revisionStore
RevisionStore $revisionStore
Definition: Article.php:103
HTMLFileCache
Page view caching in the file system.
Definition: HTMLFileCache.php:35
Page\ParserOutputAccess
Service for getting rendered output of a given page.
Definition: ParserOutputAccess.php:50
MediaWiki\Revision\RevisionStore
Service for looking up page revisions.
Definition: RevisionStore.php:88
MediaWiki\Linker\LinkRenderer
Class that generates HTML links for pages.
Definition: LinkRenderer.php:43
CategoryPage
Special handling for category description pages, showing pages, subcategories and file that belong to...
Definition: CategoryPage.php:30
getAuthority
getAuthority()
WikiPage
Class representing a MediaWiki article and history.
Definition: WikiPage.php:60
User\newFromName
static newFromName( $name, $validate='valid')
Definition: User.php:606
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1182
Article\showDiffPage
showDiffPage()
Show a diff page according to current request variables.
Definition: Article.php:830
$wgArticleRobotPolicies
$wgArticleRobotPolicies
Robot policies per article.
Definition: DefaultSettings.php:9235
ImagePage
Class for viewing MediaWiki file description pages.
Definition: ImagePage.php:34
PermissionsError
Show an error when a user tries to do something they do not have the necessary permissions for.
Definition: PermissionsError.php:32
Html\warningBox
static warningBox( $html, $className='')
Return a warning box.
Definition: Html.php:755
Article\doOutputFromRenderStatus
doOutputFromRenderStatus(?RevisionRecord $rev, Status $renderStatus, OutputPage $outputPage, array $textOptions)
Definition: Article.php:760
Article\adjustDisplayTitle
adjustDisplayTitle(ParserOutput $pOutput)
Adjust title for pages with displaytitle, -{T|}- or language conversion.
Definition: Article.php:815
Article\showNamespaceHeader
showNamespaceHeader()
Show a header specific to the namespace currently being viewed, like [[MediaWiki:Talkpagetext]].
Definition: Article.php:1076
$wgUseRCPatrol
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
Definition: DefaultSettings.php:8034
Xml\openElement
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:110
$wgUseNPPatrol
$wgUseNPPatrol
Use new page patrolling to check new pages on Special:Newpages.
Definition: DefaultSettings.php:8050
MediaWiki\User\UserIdentity
Interface for objects representing user identity.
Definition: UserIdentity.php:39
CacheTime\getCacheRevisionId
getCacheRevisionId()
Definition: CacheTime.php:96
Article\$linkRenderer
LinkRenderer $linkRenderer
Definition: Article.php:98
ProtectionForm
Handles the page protection UI and backend.
Definition: ProtectionForm.php:36
OutputPage\addHTML
addHTML( $text)
Append $text to the body HTML.
Definition: OutputPage.php:1627
Article\doDelete
doDelete( $reason, $suppress=false, $immediate=false)
Perform a deletion and output success or failure messages.
Definition: Article.php:1802
Article\getRevisionRedirectTarget
getRevisionRedirectTarget(RevisionRecord $revision)
Definition: Article.php:803
OutputPage\setLastModified
setLastModified( $timestamp)
Override the last modified timestamp.
Definition: OutputPage.php:860
Article\$mRedirectedFrom
Title null $mRedirectedFrom
Title from which we were redirected here, if any.
Definition: Article.php:70
$dbr
$dbr
Definition: testCompression.php:54
Status
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:44
StatusValue\getValue
getValue()
Definition: StatusValue.php:138
Article\unprotect
unprotect()
action=unprotect handler (alias)
Definition: Article.php:1790
RC_LOG
const RC_LOG
Definition: Defines.php:117
Status\getWikiText
getWikiText( $shortContext=false, $longContext=false, $lang=null)
Get the error list as a wikitext formatted list.
Definition: Status.php:189
MediaWiki\Block\DatabaseBlock
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
Definition: DatabaseBlock.php:52
Article\getRedirectedFrom
getRedirectedFrom()
Get the page this view was redirected from.
Definition: Article.php:206
Article\clear
clear()
Definition: Article.php:238
Article\newFromWikiPage
static newFromWikiPage(WikiPage $page, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition: Article.php:195
wfDeprecatedMsg
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
Definition: GlobalFunctions.php:1028
Article\getTitle
getTitle()
Get the title object of the article.
Definition: Article.php:224
Article\render
render()
Handle action=render.
Definition: Article.php:1771
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
Definition: GlobalFunctions.php:997
MediaWiki\Watchlist\WatchlistManager
WatchlistManager service.
Definition: WatchlistManager.php:52
Article\$mParserOutput
ParserOutput null false $mParserOutput
The ParserOutput generated for viewing the page, initialized by view().
Definition: Article.php:86
Article\addHelpLink
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Definition: Article.php:1754
ParserOutput\getTimestamp
getTimestamp()
Definition: ParserOutput.php:681
Article\setRedirectedFrom
setRedirectedFrom(Title $from)
Tell the page view functions that this view was redirected from another page on the wiki.
Definition: Article.php:215
$wgUseFileCache
$wgUseFileCache
This will cache static pages for non-logged-in users to reduce database traffic on public sites.
Definition: DefaultSettings.php:3111
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2198
Article\__construct
__construct(Title $title, $oldId=null)
Definition: Article.php:127
StatusValue\isOK
isOK()
Returns whether the operation completed.
Definition: StatusValue.php:131
Article\showDeletedRevisionHeader
showDeletedRevisionHeader()
If the revision requested for view is deleted, check permissions.
Definition: Article.php:1468
Article\isFileCacheable
isFileCacheable( $mode=HTMLFileCache::MODE_NORMAL)
Check if the page can be cached.
Definition: Article.php:1892
WikiPage\getTitle
getTitle()
Get the title object of the article.
Definition: WikiPage.php:311
OutputPage\setRevisionId
setRevisionId( $revid)
Set the revision ID which will be seen by the wiki text parser for things such as embedded {{REVISION...
Definition: OutputPage.php:1692
LogPage
Class to simplify the use of log pages.
Definition: LogPage.php:38
Article\setOldSubtitle
setOldSubtitle( $oldid=0)
Generate the navigation links when browsing through an article revisions It shows the information as:...
Definition: Article.php:1529
Xml\element
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:41
ObjectCache\getInstance
static getInstance( $id)
Get a cached instance of the specified type of cache object.
Definition: ObjectCache.php:74
Article\getPage
getPage()
Get the WikiPage object of this instance.
Definition: Article.php:234
Article\getContext
getContext()
Gets the context this Article is executed in.
Definition: Article.php:1954
$title
$title
Definition: testCompression.php:38
Article\getRobotPolicy
getRobotPolicy( $action, ParserOutput $pOutput=null)
Get the robot policy to be used for the current view.
Definition: Article.php:893
Linker\makeExternalLink
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition: Linker.php:1011
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:651
LogEventsList\showLogExtract
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
Definition: LogEventsList.php:597
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
Article\newFromID
static newFromID( $id)
Constructor from a page id.
Definition: Article.php:151
Article\newPage
newPage(Title $title)
Definition: Article.php:142
$wgUseFilePatrol
$wgUseFilePatrol
Use file patrolling to check new files on Special:Newfiles.
Definition: DefaultSettings.php:8061
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:894
OutputPage
This is one of the Core classes and should be read at least once by any new developers.
Definition: OutputPage.php:50
Article\isCurrent
isCurrent()
Returns true if the currently-referenced revision is the current edit to this page (and it exists).
Definition: Article.php:396
OutputPage\wrapWikiMsg
wrapWikiMsg( $wrap,... $msgSpecs)
This function takes a number of message/argument specifications, wraps them in some overall structure...
Definition: OutputPage.php:4127
Article\$userNameUtils
UserNameUtils $userNameUtils
Definition: Article.php:113
MediaWiki\Permissions\Authority
This interface represents the authority associated the current execution context, such as a web reque...
Definition: Authority.php:37
ParserOutput\getTitleText
getTitleText()
Definition: ParserOutput.php:594
MediaWiki\Linker\LinkRenderer\makeKnownLink
makeKnownLink( $target, $text=null, array $extraAttribs=[], array $query=[])
Definition: LinkRenderer.php:227
OutputPage\setIndexPolicy
setIndexPolicy( $policy)
Set the index policy for the page, but leave the follow policy un- touched.
Definition: OutputPage.php:898
Linker\revComment
static revComment(RevisionRecord $revRecord, $local=false, $isPublic=false, $useParentheses=true)
Wrap and format the given revision's comment block, if the current user is allowed to view it.
Definition: Linker.php:1782
$content
$content
Definition: router.php:76
OutputPage\setRevisionTimestamp
setRevisionTimestamp( $timestamp)
Set the timestamp of the revision which will be displayed.
Definition: OutputPage.php:1723
NS_MEDIA
const NS_MEDIA
Definition: Defines.php:52
Article\$mOldId
int null $mOldId
The oldid of the article that was requested to be shown, 0 for the current revision.
Definition: Article.php:67
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:82
HTMLFileCache\MODE_NORMAL
const MODE_NORMAL
Definition: HTMLFileCache.php:36
Article\protect
protect()
action=protect handler
Definition: Article.php:1782
Hooks\runner
static runner()
Get a HookRunner instance for calling hooks using the new interfaces.
Definition: Hooks.php:173
Article\$watchlistManager
WatchlistManager $watchlistManager
Definition: Article.php:108
Article\getTimestamp
getTimestamp()
Definition: Article.php:2020
Article\doOutputFromParserCache
doOutputFromParserCache(ParserOutput $pOutput, OutputPage $outputPage, array $textOptions)
Definition: Article.php:736
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:1455
$wgNamespaceRobotPolicies
$wgNamespaceRobotPolicies
Robot policies per namespaces.
Definition: DefaultSettings.php:9207
NS_USER
const NS_USER
Definition: Defines.php:66
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:484
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:58
MediaWiki\Permissions\PermissionStatus
A StatusValue for permission errors.
Definition: PermissionStatus.php:35
Article\__get
__get( $fname)
Definition: Article.php:1973
OutputPage\setFollowPolicy
setFollowPolicy( $policy)
Set the follow policy for the page, but leave the index policy un- touched.
Definition: OutputPage.php:920
Article\fetchRevisionRecord
fetchRevisionRecord()
Fetches the revision to work on.
Definition: Article.php:338
Title
Represents a title within MediaWiki.
Definition: Title.php:48
OutputPage\setCdnMaxage
setCdnMaxage( $maxage)
Set the value of the "s-maxage" part of the "Cache-control" HTTP header.
Definition: OutputPage.php:2138
$cache
$cache
Definition: mcc.php:33
MediaWiki\Revision\RevisionRecord\getId
getId( $wikiId=self::LOCAL)
Get revision ID.
Definition: RevisionRecord.php:279
Article\getRevIdFetched
getRevIdFetched()
Use this to fetch the rev ID used on page views.
Definition: Article.php:415
MediaWiki\User\UserNameUtils
UserNameUtils service.
Definition: UserNameUtils.php:42
Article\showRedirectedFromHeader
showRedirectedFromHeader()
If this request is a redirect view, send "redirected from" subtitle to the output.
Definition: Article.php:1002
RecentChange\isInRCLifespan
static isInRCLifespan( $timestamp, $tolerance=0)
Check whether the given timestamp is new enough to have a RC row with a given tolerance as the recent...
Definition: RecentChange.php:1272
NS_CATEGORY
const NS_CATEGORY
Definition: Defines.php:78
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:67
MediaWiki\Revision\RevisionRecord\getTimestamp
getTimestamp()
MCR migration note: this replaced Revision::getTimestamp.
Definition: RevisionRecord.php:459
Article\getActionOverrides
getActionOverrides()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2011
StatusValue\hasMessage
hasMessage( $message)
Returns true if the specified message is present as a warning or error.
Definition: StatusValue.php:319
Article\$mRedirectUrl
string bool $mRedirectUrl
URL to redirect to or false if none.
Definition: Article.php:73
Article\getParserOptions
getParserOptions()
Get parser options suitable for rendering the primary article wikitext.
Definition: Article.php:1934
$wgRedirectSources
$wgRedirectSources
If local interwikis are set up which allow redirects, set this regexp to restrict URLs which will be ...
Definition: DefaultSettings.php:4842
$t
$t
Definition: testCompression.php:74
Article
Class for viewing MediaWiki article and history.
Definition: Article.php:49
NS_FILE
const NS_FILE
Definition: Defines.php:70
Article\getOldID
getOldID()
Definition: Article.php:256
EditPage\POST_EDIT_COOKIE_KEY_PREFIX
const POST_EDIT_COOKIE_KEY_PREFIX
Prefix of key for cookie used to pass post-edit state.
Definition: EditPage.php:107
Article\getParserOutput
getParserOutput( $oldid=null, UserIdentity $user=null)
#-
Definition: Article.php:1920
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:68
Linker\getRevDeleteLink
static getRevDeleteLink(Authority $performer, RevisionRecord $revRecord, LinkTarget $title)
Get a revision-deletion link, or disabled link, or nothing, depending on user permissions & the setti...
Definition: Linker.php:2360
Article\setContext
setContext( $context)
Sets the context this Article is executed in.
Definition: Article.php:1944
Title\newFromID
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:529
OutputPage\addParserOutput
addParserOutput(ParserOutput $parserOutput, $poOptions=[])
Add everything from a ParserOutput object.
Definition: OutputPage.php:2027
MediaWiki\Linker\LinkRenderer\makeLink
makeLink( $target, $text=null, array $extraAttribs=[], array $query=[])
Definition: LinkRenderer.php:160
$wgDefaultRobotPolicy
$wgDefaultRobotPolicy
Default robot policy.
Definition: DefaultSettings.php:9191
Article\viewRedirect
viewRedirect( $target, $appendSubtitle=true, $forceKnown=false)
Return the HTML for the top of a redirect page.
Definition: Article.php:1685
Language
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
Definition: Language.php:42
Article\showViewError
showViewError(string $errortext)
Show error text for errors generated in Article::view().
Definition: Article.php:1453
Article\$mRevisionRecord
RevisionRecord null $mRevisionRecord
Revision to be shown.
Definition: Article.php:121
Article\__set
__set( $fname, $fvalue)
Definition: Article.php:1992
Article\showViewFooter
showViewFooter()
Show the footer section of an ordinary page view.
Definition: Article.php:1088
MediaWiki\Revision\SlotRecord
Value object representing a content slot associated with a page revision.
Definition: SlotRecord.php:40
Article\$mPage
WikiPage $mPage
The WikiPage object of this instance.
Definition: Article.php:61
Article\getRedirectHeaderHtml
static getRedirectHeaderHtml(Language $lang, $target, $forceKnown=false)
Return the HTML for the top of a redirect page.
Definition: Article.php:1707
Article\newFromTitle
static newFromTitle( $title, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition: Article.php:163
Article\purgePatrolFooterCache
static purgePatrolFooterCache( $articleID)
Purge the cache used to check if it is worth showing the patrol footer For example,...
Definition: Article.php:1282
Article\$fetchResult
Status null $fetchResult
represents the outcome of fetchRevisionRecord().
Definition: Article.php:79