MediaWiki  master
Article.php
Go to the documentation of this file.
1 <?php
25 use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
44 use Wikimedia\IPUtils;
45 use Wikimedia\NonSerializable\NonSerializableTrait;
46 
56 class Article implements Page {
57  use ProtectedHookAccessorTrait;
58  use NonSerializableTrait;
59 
65  protected $mContext;
66 
68  protected $mPage;
69 
74  public $mOldId;
75 
77  public $mRedirectedFrom = null;
78 
80  public $mRedirectUrl = false;
81 
86  private $fetchResult = null;
87 
93  public $mParserOutput = null;
94 
100  protected $viewIsRenderAction = false;
101 
105  protected $linkRenderer;
106 
110  private $revisionStore;
111 
115  private $userNameUtils;
116 
120  private $userOptionsLookup;
121 
123  private $commentFormatter;
124 
126  private $wikiPageFactory;
127 
129  private $jobQueueGroup;
130 
137  private $mRevisionRecord = null;
138 
143  public function __construct( Title $title, $oldId = null ) {
144  $this->mOldId = $oldId;
145  $this->mPage = $this->newPage( $title );
146 
147  $services = MediaWikiServices::getInstance();
148  $this->linkRenderer = $services->getLinkRenderer();
149  $this->revisionStore = $services->getRevisionStore();
150  $this->userNameUtils = $services->getUserNameUtils();
151  $this->userOptionsLookup = $services->getUserOptionsLookup();
152  $this->commentFormatter = $services->getCommentFormatter();
153  $this->wikiPageFactory = $services->getWikiPageFactory();
154  $this->jobQueueGroup = $services->getJobQueueGroup();
155  }
156 
161  protected function newPage( Title $title ) {
162  return new WikiPage( $title );
163  }
164 
170  public static function newFromID( $id ) {
171  $t = Title::newFromID( $id );
172  return $t === null ? null : new static( $t );
173  }
174 
182  public static function newFromTitle( $title, IContextSource $context ): self {
183  if ( $title->getNamespace() === NS_MEDIA ) {
184  // XXX: This should not be here, but where should it go?
185  $title = Title::makeTitle( NS_FILE, $title->getDBkey() );
186  }
187 
188  $page = null;
189  ( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )
190  // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
191  ->onArticleFromTitle( $title, $page, $context );
192  if ( !$page ) {
193  switch ( $title->getNamespace() ) {
194  case NS_FILE:
195  $page = new ImagePage( $title );
196  break;
197  case NS_CATEGORY:
198  $page = new CategoryPage( $title );
199  break;
200  default:
201  $page = new Article( $title );
202  }
203  }
204  $page->setContext( $context );
205 
206  return $page;
207  }
208 
216  public static function newFromWikiPage( WikiPage $page, IContextSource $context ) {
217  $article = self::newFromTitle( $page->getTitle(), $context );
218  $article->mPage = $page; // override to keep process cached vars
219  return $article;
220  }
221 
227  public function getRedirectedFrom() {
228  return $this->mRedirectedFrom;
229  }
230 
236  public function setRedirectedFrom( Title $from ) {
237  $this->mRedirectedFrom = $from;
238  }
239 
245  public function getTitle() {
246  return $this->mPage->getTitle();
247  }
248 
255  public function getPage() {
256  return $this->mPage;
257  }
258 
259  public function clear() {
260  $this->mRedirectedFrom = null; # Title object if set
261  $this->mRedirectUrl = false;
262  $this->mRevisionRecord = null;
263  $this->fetchResult = null;
264 
265  // TODO hard-deprecate direct access to public fields
266 
267  $this->mPage->clear();
268  }
269 
277  public function getOldID() {
278  if ( $this->mOldId === null ) {
279  $this->mOldId = $this->getOldIDFromRequest();
280  }
281 
282  return $this->mOldId;
283  }
284 
290  public function getOldIDFromRequest() {
291  $this->mRedirectUrl = false;
292 
293  $request = $this->getContext()->getRequest();
294  $oldid = $request->getIntOrNull( 'oldid' );
295 
296  if ( $oldid === null ) {
297  return 0;
298  }
299 
300  if ( $oldid !== 0 ) {
301  # Load the given revision and check whether the page is another one.
302  # In that case, update this instance to reflect the change.
303  if ( $oldid === $this->mPage->getLatest() ) {
304  $this->mRevisionRecord = $this->mPage->getRevisionRecord();
305  } else {
306  $this->mRevisionRecord = $this->revisionStore->getRevisionById( $oldid );
307  if ( $this->mRevisionRecord !== null ) {
308  $revPageId = $this->mRevisionRecord->getPageId();
309  // Revision title doesn't match the page title given?
310  if ( $this->mPage->getId() !== $revPageId ) {
311  $this->mPage = $this->wikiPageFactory->newFromID( $revPageId );
312  }
313  }
314  }
315  }
316 
317  $oldRev = $this->mRevisionRecord;
318  if ( $request->getRawVal( 'direction' ) === 'next' ) {
319  $nextid = 0;
320  if ( $oldRev ) {
321  $nextRev = $this->revisionStore->getNextRevision( $oldRev );
322  if ( $nextRev ) {
323  $nextid = $nextRev->getId();
324  }
325  }
326  if ( $nextid ) {
327  $oldid = $nextid;
328  $this->mRevisionRecord = null;
329  } else {
330  $this->mRedirectUrl = $this->getTitle()->getFullURL( 'redirect=no' );
331  }
332  } elseif ( $request->getRawVal( 'direction' ) === 'prev' ) {
333  $previd = 0;
334  if ( $oldRev ) {
335  $prevRev = $this->revisionStore->getPreviousRevision( $oldRev );
336  if ( $prevRev ) {
337  $previd = $prevRev->getId();
338  }
339  }
340  if ( $previd ) {
341  $oldid = $previd;
342  $this->mRevisionRecord = null;
343  }
344  }
345 
346  return $oldid;
347  }
348 
358  public function fetchRevisionRecord() {
359  if ( $this->fetchResult ) {
360  return $this->mRevisionRecord;
361  }
362 
363  $oldid = $this->getOldID();
364 
365  // $this->mRevisionRecord might already be fetched by getOldIDFromRequest()
366  if ( !$this->mRevisionRecord ) {
367  if ( !$oldid ) {
368  $this->mRevisionRecord = $this->mPage->getRevisionRecord();
369 
370  if ( !$this->mRevisionRecord ) {
371  wfDebug( __METHOD__ . " failed to find page data for title " .
372  $this->getTitle()->getPrefixedText() );
373 
374  // Output for this case is done by showMissingArticle().
375  $this->fetchResult = Status::newFatal( 'noarticletext' );
376  return null;
377  }
378  } else {
379  $this->mRevisionRecord = $this->revisionStore->getRevisionById( $oldid );
380 
381  if ( !$this->mRevisionRecord ) {
382  wfDebug( __METHOD__ . " failed to load revision, rev_id $oldid" );
383 
384  $this->fetchResult = Status::newFatal( 'missing-revision', $oldid );
385  return null;
386  }
387  }
388  }
389 
390  if ( !$this->mRevisionRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getContext()->getAuthority() ) ) {
391  wfDebug( __METHOD__ . " failed to retrieve content of revision " . $this->mRevisionRecord->getId() );
392 
393  // Output for this case is done by showDeletedRevisionHeader().
394  // title used in wikilinks, should not contain whitespaces
395  $this->fetchResult = new Status;
396  $title = $this->getTitle()->getPrefixedDBkey();
397 
398  if ( $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
399  $this->fetchResult->fatal( 'rev-suppressed-text' );
400  } else {
401  $this->fetchResult->fatal( 'rev-deleted-text-permission', $title );
402  }
403 
404  return null;
405  }
406 
407  $this->fetchResult = Status::newGood( $this->mRevisionRecord );
408  return $this->mRevisionRecord;
409  }
410 
416  public function isCurrent() {
417  # If no oldid, this is the current version.
418  if ( $this->getOldID() == 0 ) {
419  return true;
420  }
421 
422  return $this->mPage->exists() &&
423  $this->mRevisionRecord &&
424  $this->mRevisionRecord->isCurrent();
425  }
426 
435  public function getRevIdFetched() {
436  if ( $this->fetchResult && $this->fetchResult->isOK() ) {
438  $rev = $this->fetchResult->getValue();
439  return $rev->getId();
440  } else {
441  return $this->mPage->getLatest();
442  }
443  }
444 
449  public function view() {
450  $context = $this->getContext();
451  $useFileCache = $context->getConfig()->get( MainConfigNames::UseFileCache );
452 
453  # Get variables from query string
454  # As side effect this will load the revision and update the title
455  # in a revision ID is passed in the request, so this should remain
456  # the first call of this method even if $oldid is used way below.
457  $oldid = $this->getOldID();
458 
459  $authority = $context->getAuthority();
460  # Another check in case getOldID() is altering the title
461  $permissionStatus = PermissionStatus::newEmpty();
462  if ( !$authority
463  ->authorizeRead( 'read', $this->getTitle(), $permissionStatus )
464  ) {
465  wfDebug( __METHOD__ . ": denied on secondary read check" );
466  throw new PermissionsError( 'read', $permissionStatus );
467  }
468 
469  $outputPage = $context->getOutput();
470  # getOldID() may as well want us to redirect somewhere else
471  if ( $this->mRedirectUrl ) {
472  $outputPage->redirect( $this->mRedirectUrl );
473  wfDebug( __METHOD__ . ": redirecting due to oldid" );
474 
475  return;
476  }
477 
478  # If we got diff in the query, we want to see a diff page instead of the article.
479  if ( $context->getRequest()->getCheck( 'diff' ) ) {
480  wfDebug( __METHOD__ . ": showing diff page" );
481  $this->showDiffPage();
482 
483  return;
484  }
485 
486  # Set page title (may be overridden from ParserOutput if title conversion is enabled or DISPLAYTITLE is used)
488  str_replace( '_', ' ', $this->getTitle()->getNsText() ),
489  ':',
490  $this->getTitle()->getText()
491  ) );
492 
493  $outputPage->setArticleFlag( true );
494  # Allow frames by default
495  $outputPage->setPreventClickjacking( false );
496 
497  $parserOptions = $this->getParserOptions();
498 
499  $poOptions = [];
500  # Allow extensions to vary parser options used for article rendering
501  ( new HookRunner( MediaWikiServices::getInstance()->getHookContainer() ) )
502  ->onArticleParserOptions( $this, $parserOptions );
503  # Render printable version, use printable version cache
504  if ( $outputPage->isPrintable() ) {
505  $parserOptions->setIsPrintable( true );
506  $poOptions['enableSectionEditLinks'] = false;
507  $outputPage->prependHTML(
508  Html::warningBox(
509  $outputPage->msg( 'printableversion-deprecated-warning' )->escaped()
510  )
511  );
512  } elseif ( $this->viewIsRenderAction || !$this->isCurrent() ||
513  !$authority->probablyCan( 'edit', $this->getTitle() )
514  ) {
515  $poOptions['enableSectionEditLinks'] = false;
516  }
517 
518  # Try client and file cache
519  if ( $oldid === 0 && $this->mPage->checkTouched() ) {
520  # Try to stream the output from file cache
521  if ( $useFileCache && $this->tryFileCache() ) {
522  wfDebug( __METHOD__ . ": done file cache" );
523  # tell wgOut that output is taken care of
524  $outputPage->disable();
525  $this->mPage->doViewUpdates( $authority, $oldid );
526 
527  return;
528  }
529  }
530 
531  $this->showRedirectedFromHeader();
532  $this->showNamespaceHeader();
533 
534  if ( $this->viewIsRenderAction ) {
535  $poOptions += [ 'absoluteURLs' => true ];
536  }
537  $poOptions += [ 'includeDebugInfo' => true ];
538 
539  try {
540  $continue =
541  $this->generateContentOutput( $authority, $parserOptions, $oldid, $outputPage, $poOptions );
542  } catch ( BadRevisionException $e ) {
543  $continue = false;
544  $this->showViewError( wfMessage( 'badrevision' )->text() );
545  }
546 
547  if ( !$continue ) {
548  return;
549  }
550 
551  # For the main page, overwrite the <title> element with the con-
552  # tents of 'pagetitle-view-mainpage' instead of the default (if
553  # that's not empty).
554  # This message always exists because it is in the i18n files
555  if ( $this->getTitle()->isMainPage() ) {
556  $msg = $context->msg( 'pagetitle-view-mainpage' )->inContentLanguage();
557  if ( !$msg->isDisabled() ) {
558  $outputPage->setHTMLTitle( $msg->text() );
559  }
560  }
561 
562  # Use adaptive TTLs for CDN so delayed/failed purges are noticed less often.
563  # This could use getTouched(), but that could be scary for major template edits.
564  $outputPage->adaptCdnTTL( $this->mPage->getTimestamp(), IExpiringStore::TTL_DAY );
565 
566  $this->showViewFooter();
567  $this->mPage->doViewUpdates( $authority, $oldid, $this->fetchRevisionRecord() );
568 
569  # Load the postEdit module if the user just saved this revision
570  # See also EditPage::setPostEditCookie
571  $request = $context->getRequest();
572  $cookieKey = EditPage::POST_EDIT_COOKIE_KEY_PREFIX . $this->getRevIdFetched();
573  $postEdit = $request->getCookie( $cookieKey );
574  if ( $postEdit ) {
575  # Clear the cookie. This also prevents caching of the response.
576  $request->response()->clearCookie( $cookieKey );
577  $outputPage->addJsConfigVars( 'wgPostEdit', $postEdit );
578  $outputPage->addModules( 'mediawiki.action.view.postEdit' ); // FIXME: test this
579  }
580  }
581 
594  private function generateContentOutput(
595  Authority $performer,
596  ParserOptions $parserOptions,
597  int $oldid,
598  OutputPage $outputPage,
599  array $textOptions
600  ): bool {
601  # Should the parser cache be used?
602  $useParserCache = true;
603  $pOutput = null;
604  $parserOutputAccess = MediaWikiServices::getInstance()->getParserOutputAccess();
605 
606  // NOTE: $outputDone and $useParserCache may be changed by the hook
607  $this->getHookRunner()->onArticleViewHeader( $this, $outputDone, $useParserCache );
608  if ( $outputDone ) {
609  if ( $outputDone instanceof ParserOutput ) {
610  $pOutput = $outputDone;
611  }
612 
613  if ( $pOutput ) {
614  $this->doOutputMetaData( $pOutput, $outputPage );
615  }
616  return true;
617  }
618 
619  // Early abort if the page doesn't exist
620  if ( !$this->mPage->exists() ) {
621  wfDebug( __METHOD__ . ": showing missing article" );
622  $this->showMissingArticle();
623  $this->mPage->doViewUpdates( $performer );
624  return false; // skip all further output to OutputPage
625  }
626 
627  // Try the latest parser cache
628  // NOTE: try latest-revision cache first to avoid loading revision.
629  if ( $useParserCache && !$oldid ) {
630  $pOutput = $parserOutputAccess->getCachedParserOutput(
631  $this->getPage(),
632  $parserOptions,
633  null,
634  ParserOutputAccess::OPT_NO_AUDIENCE_CHECK // we already checked
635  );
636 
637  if ( $pOutput ) {
638  $this->doOutputFromParserCache( $pOutput, $outputPage, $textOptions );
639  $this->doOutputMetaData( $pOutput, $outputPage );
640  return true;
641  }
642  }
643 
644  $rev = $this->fetchRevisionRecord();
645  if ( !$this->fetchResult->isOK() ) {
646  $this->showViewError( $this->fetchResult->getWikiText(
647  false, false, $this->getContext()->getLanguage()
648  ) );
649  return true;
650  }
651 
652  # Are we looking at an old revision
653  if ( $oldid ) {
654  $this->setOldSubtitle( $oldid );
655 
656  if ( !$this->showDeletedRevisionHeader() ) {
657  wfDebug( __METHOD__ . ": cannot view deleted revision" );
658  return false; // skip all further output to OutputPage
659  }
660 
661  // Try the old revision parser cache
662  // NOTE: Repeating cache check for old revision to avoid fetching $rev
663  // before it's absolutely necessary.
664  if ( $useParserCache ) {
665  $pOutput = $parserOutputAccess->getCachedParserOutput(
666  $this->getPage(),
667  $parserOptions,
668  $rev,
669  ParserOutputAccess::OPT_NO_AUDIENCE_CHECK // we already checked in fetchRevisionRecord
670  );
671 
672  if ( $pOutput ) {
673  $this->doOutputFromParserCache( $pOutput, $outputPage, $textOptions );
674  $this->doOutputMetaData( $pOutput, $outputPage );
675  return true;
676  }
677  }
678  }
679 
680  # Ensure that UI elements requiring revision ID have
681  # the correct version information.
682  $outputPage->setRevisionId( $this->getRevIdFetched() );
683  $outputPage->setRevisionIsCurrent( $rev->isCurrent() );
684  # Preload timestamp to avoid a DB hit
685  $outputPage->setRevisionTimestamp( $rev->getTimestamp() );
686 
687  # Pages containing custom CSS or JavaScript get special treatment
688  if ( $this->getTitle()->isSiteConfigPage() || $this->getTitle()->isUserConfigPage() ) {
689  $dir = $this->getContext()->getLanguage()->getDir();
690  $lang = $this->getContext()->getLanguage()->getHtmlCode();
691 
692  $outputPage->wrapWikiMsg(
693  "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
694  'clearyourcache'
695  );
696  $outputPage->addModuleStyles( 'mediawiki.action.styles' );
697  } elseif ( !$this->getHookRunner()->onArticleRevisionViewCustom(
698  $rev,
699  $this->getTitle(),
700  $oldid,
701  $outputPage )
702  ) {
703  // NOTE: sync with hooks called in DifferenceEngine::renderNewRevision()
704  // Allow extensions do their own custom view for certain pages
705  $this->doOutputMetaData( $pOutput, $outputPage );
706  return true;
707  }
708 
709  # Run the parse, protected by a pool counter
710  wfDebug( __METHOD__ . ": doing uncached parse" );
711 
712  $opt = 0;
713 
714  // we already checked the cache in case 2, don't check again.
715  $opt |= ParserOutputAccess::OPT_NO_CHECK_CACHE;
716 
717  // we already checked in fetchRevisionRecord()
718  $opt |= ParserOutputAccess::OPT_NO_AUDIENCE_CHECK;
719 
720  // Attempt to trigger WikiPage::triggerOpportunisticLinksUpdate
721  // Ideally this should not be the responsibility of the ParserCache to control this.
722  // See https://phabricator.wikimedia.org/T329842#8816557 for more context.
723  $opt |= ParserOutputAccess::OPT_LINKS_UPDATE;
724 
725  if ( !$rev->getId() || !$useParserCache ) {
726  // fake revision or uncacheable options
727  $opt |= ParserOutputAccess::OPT_NO_CACHE;
728  }
729 
730  $renderStatus = $parserOutputAccess->getParserOutput(
731  $this->getPage(),
732  $parserOptions,
733  $rev,
734  $opt
735  );
736 
737  // T327164: If parsoid cache warming is enabled, we want to ensure that the page
738  // the user is currently looking at has a cached parsoid rendering, in case they
739  // open visual editor. The cache entry would typically be missing if it has expired
740  // from the cache or it was invalidated by RefreshLinksJob. When "traditional"
741  // parser output has been invalidated by RefreshLinksJob, we will render it on
742  // the fly when a user requests the page, and thereby populate the cache again,
743  // per the code above.
744  // The code below is intended to do the same for parsoid output, but asynchronously
745  // in a job, so the user does not have to wait.
746  // Note that we get here if the traditional parser output was missing from the cache.
747  // We do not check if the parsoid output is present in the cache, because that check
748  // takes time. The assumption is that if we have traditional parser output
749  // cached, we probably also have parsoid output cached.
750  // So we leave it to ParsoidCachePrewarmJob to determine whether or not parsing is
751  // needed.
752  if ( $oldid === 0 || $oldid === $this->getPage()->getLatest() ) {
753  $parsoidCacheWarmingEnabled = $this->getContext()->getConfig()
754  ->get( MainConfigNames::ParsoidCacheConfig )['WarmParsoidParserCache'];
755 
756  if ( $parsoidCacheWarmingEnabled ) {
757  $parsoidJobSpec = ParsoidCachePrewarmJob::newSpec(
758  $rev->getId(),
759  $rev->getPageId(),
760  [ 'causeAction' => 'view' ]
761  );
762  $this->jobQueueGroup->lazyPush( $parsoidJobSpec );
763  }
764  }
765 
766  $this->doOutputFromRenderStatus(
767  $rev,
768  $renderStatus,
769  $outputPage,
770  $textOptions
771  );
772 
773  if ( !$renderStatus->isOK() ) {
774  return true;
775  }
776 
777  $pOutput = $renderStatus->getValue();
778  $this->doOutputMetaData( $pOutput, $outputPage );
779  return true;
780  }
781 
786  private function doOutputMetaData( ?ParserOutput $pOutput, OutputPage $outputPage ) {
787  # Adjust title for main page & pages with displaytitle
788  if ( $pOutput ) {
789  $this->adjustDisplayTitle( $pOutput );
790  }
791 
792  # Check for any __NOINDEX__ tags on the page using $pOutput
793  $policy = $this->getRobotPolicy( 'view', $pOutput ?: null );
794  $outputPage->setIndexPolicy( $policy['index'] );
795  $outputPage->setFollowPolicy( $policy['follow'] ); // FIXME: test this
796 
797  $this->mParserOutput = $pOutput;
798  }
799 
805  private function doOutputFromParserCache(
806  ParserOutput $pOutput,
807  OutputPage $outputPage,
808  array $textOptions
809  ) {
810  # Ensure that UI elements requiring revision ID have
811  # the correct version information.
812  $oldid = $pOutput->getCacheRevisionId() ?? $this->getRevIdFetched();
813  $outputPage->setRevisionId( $oldid );
814  $outputPage->setRevisionIsCurrent( $oldid === $this->mPage->getLatest() );
815  $outputPage->addParserOutput( $pOutput, $textOptions );
816  # Preload timestamp to avoid a DB hit
817  $cachedTimestamp = $pOutput->getTimestamp();
818  if ( $cachedTimestamp !== null ) {
819  $outputPage->setRevisionTimestamp( $cachedTimestamp );
820  $this->mPage->setTimestamp( $cachedTimestamp );
821  }
822  }
823 
830  private function doOutputFromRenderStatus(
831  RevisionRecord $rev,
832  Status $renderStatus,
833  OutputPage $outputPage,
834  array $textOptions
835  ) {
836  $context = $this->getContext();
837  $cdnMaxageStale = $context->getConfig()->get( MainConfigNames::CdnMaxageStale );
838  $ok = $renderStatus->isOK();
839 
840  $pOutput = $ok ? $renderStatus->getValue() : null;
841 
842  // Cache stale ParserOutput object with a short expiry
843  if ( $ok && $renderStatus->hasMessage( 'view-pool-dirty-output' ) ) {
844  $outputPage->setCdnMaxage( $cdnMaxageStale );
845  $outputPage->setLastModified( $pOutput->getCacheTime() );
846  $staleReason = $renderStatus->hasMessage( 'view-pool-contention' )
847  ? $context->msg( 'view-pool-contention' )
848  : $context->msg( 'view-pool-timeout' );
849  $outputPage->addHTML( "<!-- parser cache is expired, " .
850  "sending anyway due to $staleReason-->\n" );
851  }
852 
853  if ( !$renderStatus->isOK() ) {
854  $this->showViewError( $renderStatus->getWikiText(
855  false, 'view-pool-error', $context->getLanguage()
856  ) );
857  return;
858  }
859 
860  if ( $pOutput ) {
861  $outputPage->addParserOutput( $pOutput, $textOptions );
862  }
863 
864  if ( $this->getRevisionRedirectTarget( $rev ) ) {
865  $outputPage->addSubtitle( "<span id=\"redirectsub\">" .
866  $context->msg( 'redirectpagesub' )->parse() . "</span>" );
867  }
868  }
869 
874  private function getRevisionRedirectTarget( RevisionRecord $revision ) {
875  // TODO: find a *good* place for the code that determines the redirect target for
876  // a given revision!
877  // NOTE: Use main slot content. Compare code in DerivedPageDataUpdater::revisionIsRedirect.
878  $content = $revision->getContent( SlotRecord::MAIN );
879  return $content ? $content->getRedirectTarget() : null;
880  }
881 
886  public function adjustDisplayTitle( ParserOutput $pOutput ) {
887  $out = $this->getContext()->getOutput();
888 
889  # Adjust the title if it was set by displaytitle, -{T|}- or language conversion
890  $titleText = $pOutput->getTitleText();
891  if ( strval( $titleText ) !== '' ) {
892  $out->setPageTitle( $titleText );
893  $out->setDisplayTitle( $titleText );
894  }
895  }
896 
901  protected function showDiffPage() {
902  $context = $this->getContext();
903  $request = $context->getRequest();
904  $diff = $request->getVal( 'diff' );
905  $rcid = $request->getInt( 'rcid' );
906  $purge = $request->getRawVal( 'action' ) === 'purge';
907  $unhide = $request->getInt( 'unhide' ) === 1;
908  $oldid = $this->getOldID();
909 
910  $rev = $this->fetchRevisionRecord();
911 
912  if ( !$rev ) {
913  // T213621: $rev maybe null due to either lack of permission to view the
914  // revision or actually not existing. So let's try loading it from the id
915  $rev = $this->revisionStore->getRevisionById( $oldid );
916  if ( $rev ) {
917  // Revision exists but $user lacks permission to diff it.
918  // Do nothing here.
919  // The $rev will later be used to create standard diff elements however.
920  } else {
921  $context->getOutput()->setPageTitle( $context->msg( 'errorpagetitle' ) );
922  $msg = $context->msg( 'difference-missing-revision' )
923  ->params( $oldid )
924  ->numParams( 1 )
925  ->parseAsBlock();
926  $context->getOutput()->addHTML( $msg );
927  return;
928  }
929  }
930 
931  $contentHandler = MediaWikiServices::getInstance()
932  ->getContentHandlerFactory()
933  ->getContentHandler(
934  $rev->getSlot( SlotRecord::MAIN, RevisionRecord::RAW )->getModel()
935  );
936  $de = $contentHandler->createDifferenceEngine(
937  $context,
938  $oldid,
939  $diff,
940  $rcid,
941  $purge,
942  $unhide
943  );
944  $de->setSlotDiffOptions( [
945  'diff-type' => $request->getVal( 'diff-type' ),
946  'expand-url' => $this->viewIsRenderAction
947  ] );
948  $de->showDiffPage( $this->isDiffOnlyView() );
949 
950  // Run view updates for the newer revision being diffed (and shown
951  // below the diff if not diffOnly).
952  [ , $new ] = $de->mapDiffPrevNext( $oldid, $diff );
953  // New can be false, convert it to 0 - this conveniently means the latest revision
954  $this->mPage->doViewUpdates( $context->getAuthority(), (int)$new );
955 
956  // Add link to help page; see T321569
957  $context->getOutput()->addHelpLink( 'Help:Diff' );
958  }
959 
960  protected function isDiffOnlyView() {
961  return $this->getContext()->getRequest()->getBool(
962  'diffonly',
963  $this->userOptionsLookup->getBoolOption( $this->getContext()->getUser(), 'diffonly' )
964  );
965  }
966 
974  public function getRobotPolicy( $action, ParserOutput $pOutput = null ) {
975  $context = $this->getContext();
976  $mainConfig = $context->getConfig();
977  $articleRobotPolicies = $mainConfig->get( MainConfigNames::ArticleRobotPolicies );
978  $namespaceRobotPolicies = $mainConfig->get( MainConfigNames::NamespaceRobotPolicies );
979  $defaultRobotPolicy = $mainConfig->get( MainConfigNames::DefaultRobotPolicy );
980  $title = $this->getTitle();
981  $ns = $title->getNamespace();
982 
983  # Don't index user and user talk pages for blocked users (T13443)
984  if ( ( $ns === NS_USER || $ns === NS_USER_TALK ) && !$title->isSubpage() ) {
985  $specificTarget = null;
986  $vagueTarget = null;
987  $titleText = $title->getText();
988  if ( IPUtils::isValid( $titleText ) ) {
989  $vagueTarget = $titleText;
990  } else {
991  $specificTarget = $titleText;
992  }
993  if ( DatabaseBlock::newFromTarget( $specificTarget, $vagueTarget ) instanceof DatabaseBlock ) {
994  return [
995  'index' => 'noindex',
996  'follow' => 'nofollow'
997  ];
998  }
999  }
1000 
1001  if ( $this->mPage->getId() === 0 || $this->getOldID() ) {
1002  # Non-articles (special pages etc), and old revisions
1003  return [
1004  'index' => 'noindex',
1005  'follow' => 'nofollow'
1006  ];
1007  } elseif ( $context->getOutput()->isPrintable() ) {
1008  # Discourage indexing of printable versions, but encourage following
1009  return [
1010  'index' => 'noindex',
1011  'follow' => 'follow'
1012  ];
1013  } elseif ( $context->getRequest()->getInt( 'curid' ) ) {
1014  # For ?curid=x urls, disallow indexing
1015  return [
1016  'index' => 'noindex',
1017  'follow' => 'follow'
1018  ];
1019  }
1020 
1021  # Otherwise, construct the policy based on the various config variables.
1022  $policy = self::formatRobotPolicy( $defaultRobotPolicy );
1023 
1024  if ( isset( $namespaceRobotPolicies[$ns] ) ) {
1025  # Honour customised robot policies for this namespace
1026  $policy = array_merge(
1027  $policy,
1028  self::formatRobotPolicy( $namespaceRobotPolicies[$ns] )
1029  );
1030  }
1031  if ( $title->canUseNoindex() && $pOutput && $pOutput->getIndexPolicy() ) {
1032  # __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates
1033  # a final check that we have really got the parser output.
1034  $policy = array_merge(
1035  $policy,
1036  [ 'index' => $pOutput->getIndexPolicy() ]
1037  );
1038  }
1039 
1040  if ( isset( $articleRobotPolicies[$title->getPrefixedText()] ) ) {
1041  # (T16900) site config can override user-defined __INDEX__ or __NOINDEX__
1042  $policy = array_merge(
1043  $policy,
1044  self::formatRobotPolicy( $articleRobotPolicies[$title->getPrefixedText()] )
1045  );
1046  }
1047 
1048  return $policy;
1049  }
1050 
1058  public static function formatRobotPolicy( $policy ) {
1059  if ( is_array( $policy ) ) {
1060  return $policy;
1061  } elseif ( !$policy ) {
1062  return [];
1063  }
1064 
1065  $arr = [];
1066  foreach ( explode( ',', $policy ) as $var ) {
1067  $var = trim( $var );
1068  if ( $var === 'index' || $var === 'noindex' ) {
1069  $arr['index'] = $var;
1070  } elseif ( $var === 'follow' || $var === 'nofollow' ) {
1071  $arr['follow'] = $var;
1072  }
1073  }
1074 
1075  return $arr;
1076  }
1077 
1085  public function showRedirectedFromHeader() {
1086  $context = $this->getContext();
1087  $redirectSources = $context->getConfig()->get( MainConfigNames::RedirectSources );
1088  $outputPage = $context->getOutput();
1089  $request = $context->getRequest();
1090  $rdfrom = $request->getVal( 'rdfrom' );
1091 
1092  // Construct a URL for the current page view, but with the target title
1093  $query = $request->getValues();
1094  unset( $query['rdfrom'] );
1095  unset( $query['title'] );
1096  if ( $this->getTitle()->isRedirect() ) {
1097  // Prevent double redirects
1098  $query['redirect'] = 'no';
1099  }
1100  $redirectTargetUrl = $this->getTitle()->getLinkURL( $query );
1101 
1102  if ( isset( $this->mRedirectedFrom ) ) {
1103  // This is an internally redirected page view.
1104  // We'll need a backlink to the source page for navigation.
1105  if ( $this->getHookRunner()->onArticleViewRedirect( $this ) ) {
1106  $redir = $this->linkRenderer->makeKnownLink(
1107  $this->mRedirectedFrom,
1108  null,
1109  [],
1110  [ 'redirect' => 'no' ]
1111  );
1112 
1113  $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
1114  $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
1115  . "</span>" );
1116 
1117  // Add the script to update the displayed URL and
1118  // set the fragment if one was specified in the redirect
1119  $outputPage->addJsConfigVars( [
1120  'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1121  ] );
1122  $outputPage->addModules( 'mediawiki.action.view.redirect' );
1123 
1124  // Add a <link rel="canonical"> tag
1125  $outputPage->setCanonicalUrl( $this->getTitle()->getCanonicalURL() );
1126 
1127  // Tell the output object that the user arrived at this article through a redirect
1128  $outputPage->setRedirectedFrom( $this->mRedirectedFrom );
1129 
1130  return true;
1131  }
1132  } elseif ( $rdfrom ) {
1133  // This is an externally redirected view, from some other wiki.
1134  // If it was reported from a trusted site, supply a backlink.
1135  if ( $redirectSources && preg_match( $redirectSources, $rdfrom ) ) {
1136  $redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
1137  $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
1138  $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
1139  . "</span>" );
1140 
1141  // Add the script to update the displayed URL
1142  $outputPage->addJsConfigVars( [
1143  'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1144  ] );
1145  $outputPage->addModules( 'mediawiki.action.view.redirect' );
1146 
1147  return true;
1148  }
1149  }
1150 
1151  return false;
1152  }
1153 
1158  public function showNamespaceHeader() {
1159  if ( $this->getTitle()->isTalkPage() && !$this->getContext()->msg( 'talkpageheader' )->isDisabled() ) {
1160  $this->getContext()->getOutput()->wrapWikiMsg(
1161  "<div class=\"mw-talkpageheader\">\n$1\n</div>",
1162  [ 'talkpageheader' ]
1163  );
1164  }
1165  }
1166 
1170  public function showViewFooter() {
1171  # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
1172  if ( $this->getTitle()->getNamespace() === NS_USER_TALK
1173  && IPUtils::isValid( $this->getTitle()->getText() )
1174  ) {
1175  $this->getContext()->getOutput()->addWikiMsg( 'anontalkpagetext' );
1176  }
1177 
1178  // Show a footer allowing the user to patrol the shown revision or page if possible
1179  $patrolFooterShown = $this->showPatrolFooter();
1180 
1181  $this->getHookRunner()->onArticleViewFooter( $this, $patrolFooterShown );
1182  }
1183 
1194  public function showPatrolFooter() {
1195  $context = $this->getContext();
1196  $mainConfig = $context->getConfig();
1197  $useNPPatrol = $mainConfig->get( MainConfigNames::UseNPPatrol );
1198  $useRCPatrol = $mainConfig->get( MainConfigNames::UseRCPatrol );
1199  $useFilePatrol = $mainConfig->get( MainConfigNames::UseFilePatrol );
1200  // Allow hooks to decide whether to not output this at all
1201  if ( !$this->getHookRunner()->onArticleShowPatrolFooter( $this ) ) {
1202  return false;
1203  }
1204 
1205  $outputPage = $context->getOutput();
1206  $user = $context->getUser();
1207  $title = $this->getTitle();
1208  $rc = false;
1209 
1210  if ( !$context->getAuthority()->probablyCan( 'patrol', $title )
1211  || !( $useRCPatrol || $useNPPatrol
1212  || ( $useFilePatrol && $title->inNamespace( NS_FILE ) ) )
1213  ) {
1214  // Patrolling is disabled or the user isn't allowed to
1215  return false;
1216  }
1217 
1218  if ( $this->mRevisionRecord
1219  && !RecentChange::isInRCLifespan( $this->mRevisionRecord->getTimestamp(), 21600 )
1220  ) {
1221  // The current revision is already older than what could be in the RC table
1222  // 6h tolerance because the RC might not be cleaned out regularly
1223  return false;
1224  }
1225 
1226  // Check for cached results
1227  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1228  $key = $cache->makeKey( 'unpatrollable-page', $title->getArticleID() );
1229  if ( $cache->get( $key ) ) {
1230  return false;
1231  }
1232 
1233  $dbr = wfGetDB( DB_REPLICA );
1234  $oldestRevisionRow = $dbr->selectRow(
1235  'revision',
1236  [ 'rev_id', 'rev_timestamp' ],
1237  [ 'rev_page' => $title->getArticleID() ],
1238  __METHOD__,
1239  [ 'ORDER BY' => [ 'rev_timestamp', 'rev_id' ] ]
1240  );
1241  $oldestRevisionTimestamp = $oldestRevisionRow ? $oldestRevisionRow->rev_timestamp : false;
1242 
1243  // New page patrol: Get the timestamp of the oldest revision which
1244  // the revision table holds for the given page. Then we look
1245  // whether it's within the RC lifespan and if it is, we try
1246  // to get the recentchanges row belonging to that entry.
1247  $recentPageCreation = false;
1248  if ( $oldestRevisionTimestamp
1249  && RecentChange::isInRCLifespan( $oldestRevisionTimestamp, 21600 )
1250  ) {
1251  // 6h tolerance because the RC might not be cleaned out regularly
1252  $recentPageCreation = true;
1254  [
1255  'rc_this_oldid' => intval( $oldestRevisionRow->rev_id ),
1256  // Avoid selecting a categorization entry
1257  'rc_type' => RC_NEW,
1258  ],
1259  __METHOD__
1260  );
1261  if ( $rc ) {
1262  // Use generic patrol message for new pages
1263  $markPatrolledMsg = $context->msg( 'markaspatrolledtext' );
1264  }
1265  }
1266 
1267  // File patrol: Get the timestamp of the latest upload for this page,
1268  // check whether it is within the RC lifespan and if it is, we try
1269  // to get the recentchanges row belonging to that entry
1270  // (with rc_type = RC_LOG, rc_log_type = upload).
1271  $recentFileUpload = false;
1272  if ( ( !$rc || $rc->getAttribute( 'rc_patrolled' ) ) && $useFilePatrol
1273  && $title->getNamespace() === NS_FILE ) {
1274  // Retrieve timestamp from the current file (lastest upload)
1275  $newestUploadTimestamp = $dbr->selectField(
1276  'image',
1277  'img_timestamp',
1278  [ 'img_name' => $title->getDBkey() ],
1279  __METHOD__
1280  );
1281  if ( $newestUploadTimestamp
1282  && RecentChange::isInRCLifespan( $newestUploadTimestamp, 21600 )
1283  ) {
1284  // 6h tolerance because the RC might not be cleaned out regularly
1285  $recentFileUpload = true;
1287  [
1288  'rc_type' => RC_LOG,
1289  'rc_log_type' => 'upload',
1290  'rc_timestamp' => $newestUploadTimestamp,
1291  'rc_namespace' => NS_FILE,
1292  'rc_cur_id' => $title->getArticleID()
1293  ],
1294  __METHOD__
1295  );
1296  if ( $rc ) {
1297  // Use patrol message specific to files
1298  $markPatrolledMsg = $context->msg( 'markaspatrolledtext-file' );
1299  }
1300  }
1301  }
1302 
1303  if ( !$recentPageCreation && !$recentFileUpload ) {
1304  // Page creation and latest upload (for files) is too old to be in RC
1305 
1306  // We definitely can't patrol so cache the information
1307  // When a new file version is uploaded, the cache is cleared
1308  $cache->set( $key, '1' );
1309 
1310  return false;
1311  }
1312 
1313  if ( !$rc ) {
1314  // Don't cache: This can be hit if the page gets accessed very fast after
1315  // its creation / latest upload or in case we have high replica DB lag. In case
1316  // the revision is too old, we will already return above.
1317  return false;
1318  }
1319 
1320  if ( $rc->getAttribute( 'rc_patrolled' ) ) {
1321  // Patrolled RC entry around
1322 
1323  // Cache the information we gathered above in case we can't patrol
1324  // Don't cache in case we can patrol as this could change
1325  $cache->set( $key, '1' );
1326 
1327  return false;
1328  }
1329 
1330  if ( $rc->getPerformerIdentity()->equals( $user ) ) {
1331  // Don't show a patrol link for own creations/uploads. If the user could
1332  // patrol them, they already would be patrolled
1333  return false;
1334  }
1335 
1336  $outputPage->setPreventClickjacking( true );
1337  if ( $context->getAuthority()->isAllowed( 'writeapi' ) ) {
1338  $outputPage->addModules( 'mediawiki.misc-authed-curate' );
1339  }
1340 
1341  $link = $this->linkRenderer->makeKnownLink(
1342  $title,
1343  // @phan-suppress-next-line PhanPossiblyUndeclaredVariable $markPatrolledMsg is always set when $rc is set
1344  $markPatrolledMsg->text(),
1345  [],
1346  [
1347  'action' => 'markpatrolled',
1348  'rcid' => $rc->getAttribute( 'rc_id' ),
1349  ]
1350  );
1351 
1352  $outputPage->addModuleStyles( 'mediawiki.action.styles' );
1353  $outputPage->addHTML(
1354  "<div class='patrollink' data-mw='interface'>" .
1355  $context->msg( 'markaspatrolledlink' )->rawParams( $link )->escaped() .
1356  '</div>'
1357  );
1358 
1359  return true;
1360  }
1361 
1368  public static function purgePatrolFooterCache( $articleID ) {
1369  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1370  $cache->delete( $cache->makeKey( 'unpatrollable-page', $articleID ) );
1371  }
1372 
1377  public function showMissingArticle() {
1378  $context = $this->getContext();
1379  $send404Code = $context->getConfig()->get( MainConfigNames::Send404Code );
1380 
1381  $outputPage = $context->getOutput();
1382  // Whether the page is a root user page of an existing user (but not a subpage)
1383  $validUserPage = false;
1384 
1385  $title = $this->getTitle();
1386 
1387  $services = MediaWikiServices::getInstance();
1388 
1389  $contextUser = $context->getUser();
1390 
1391  # Show info in user (talk) namespace. Does the user exist? Is he blocked?
1392  if ( $title->getNamespace() === NS_USER
1393  || $title->getNamespace() === NS_USER_TALK
1394  ) {
1395  $rootPart = $title->getRootText();
1396  $user = User::newFromName( $rootPart, false /* allow IP users */ );
1397  $ip = $this->userNameUtils->isIP( $rootPart );
1398  $block = DatabaseBlock::newFromTarget( $user, $user );
1399 
1400  if ( $user && $user->isRegistered() && $user->isHidden() &&
1401  !$context->getAuthority()->isAllowed( 'hideuser' )
1402  ) {
1403  // T120883 if the user is hidden and the viewer cannot see hidden
1404  // users, pretend like it does not exist at all.
1405  $user = false;
1406  }
1407 
1408  if ( !( $user && $user->isRegistered() ) && !$ip ) {
1409  // User does not exist
1410  $outputPage->addHTML( Html::warningBox(
1411  $context->msg( 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) )->parse(),
1412  'mw-userpage-userdoesnotexist'
1413  ) );
1414 
1415  // Show renameuser log extract
1417  $outputPage,
1418  'renameuser',
1419  Title::makeTitleSafe( NS_USER, $rootPart ),
1420  '',
1421  [
1422  'lim' => 10,
1423  'showIfEmpty' => false,
1424  'msgKey' => [ 'renameuser-renamed-notice', $title->getBaseText() ]
1425  ]
1426  );
1427  } elseif (
1428  $block !== null &&
1429  $block->getType() != DatabaseBlock::TYPE_AUTO &&
1430  (
1431  $block->isSitewide() ||
1432  $services->getPermissionManager()->isBlockedFrom( $user, $title, true )
1433  )
1434  ) {
1435  // Show log extract if the user is sitewide blocked or is partially
1436  // blocked and not allowed to edit their user page or user talk page
1438  $outputPage,
1439  'block',
1440  $services->getNamespaceInfo()->getCanonicalName( NS_USER ) . ':' .
1441  $block->getTargetName(),
1442  '',
1443  [
1444  'lim' => 1,
1445  'showIfEmpty' => false,
1446  'msgKey' => [
1447  'blocked-notice-logextract',
1448  $user->getName() # Support GENDER in notice
1449  ]
1450  ]
1451  );
1452  $validUserPage = !$title->isSubpage();
1453  } else {
1454  $validUserPage = !$title->isSubpage();
1455  }
1456  }
1457 
1458  $this->getHookRunner()->onShowMissingArticle( $this );
1459 
1460  # Show delete and move logs if there were any such events.
1461  # The logging query can DOS the site when bots/crawlers cause 404 floods,
1462  # so be careful showing this. 404 pages must be cheap as they are hard to cache.
1463  $dbCache = MediaWikiServices::getInstance()->getMainObjectStash();
1464  $key = $dbCache->makeKey( 'page-recent-delete', md5( $title->getPrefixedText() ) );
1465  $isRegistered = $contextUser->isRegistered();
1466  $sessionExists = $context->getRequest()->getSession()->isPersistent();
1467 
1468  if ( $isRegistered || $dbCache->get( $key ) || $sessionExists ) {
1469  $logTypes = [ 'delete', 'move', 'protect' ];
1470 
1471  $dbr = wfGetDB( DB_REPLICA );
1472 
1473  $conds = [ 'log_action != ' . $dbr->addQuotes( 'revision' ) ];
1474  // Give extensions a chance to hide their (unrelated) log entries
1475  $this->getHookRunner()->onArticle__MissingArticleConditions( $conds, $logTypes );
1477  $outputPage,
1478  $logTypes,
1479  $title,
1480  '',
1481  [
1482  'lim' => 10,
1483  'conds' => $conds,
1484  'showIfEmpty' => false,
1485  'msgKey' => [ $isRegistered || $sessionExists
1486  ? 'moveddeleted-notice'
1487  : 'moveddeleted-notice-recent'
1488  ]
1489  ]
1490  );
1491  }
1492 
1493  if ( !$this->mPage->hasViewableContent() && $send404Code && !$validUserPage ) {
1494  // If there's no backing content, send a 404 Not Found
1495  // for better machine handling of broken links.
1496  $context->getRequest()->response()->statusHeader( 404 );
1497  }
1498 
1499  // Also apply the robot policy for nonexisting pages (even if a 404 was used)
1500  $policy = $this->getRobotPolicy( 'view' );
1501  $outputPage->setIndexPolicy( $policy['index'] );
1502  $outputPage->setFollowPolicy( $policy['follow'] );
1503 
1504  $hookResult = $this->getHookRunner()->onBeforeDisplayNoArticleText( $this );
1505 
1506  if ( !$hookResult ) {
1507  return;
1508  }
1509 
1510  # Show error message
1511  $oldid = $this->getOldID();
1512  if ( !$oldid && $title->getNamespace() === NS_MEDIAWIKI && $title->hasSourceText() ) {
1513  $text = $this->getTitle()->getDefaultMessageText() ?? '';
1514  $outputPage->addWikiTextAsContent( $text );
1515  } else {
1516  if ( $oldid ) {
1517  // T251066: Try loading the revision from the archive table.
1518  // Show link to view it if it exists and the user has permission to view it.
1519  $pa = new PageArchive( $title );
1520  $revRecord = $pa->getArchivedRevisionRecord( $oldid );
1521  if ( $revRecord && $revRecord->userCan(
1522  RevisionRecord::DELETED_TEXT,
1523  $context->getAuthority()
1524  ) ) {
1525  $text = $context->msg(
1526  'missing-revision-permission', $oldid,
1527  $revRecord->getTimestamp(),
1528  $title->getPrefixedDBkey()
1529  )->plain();
1530  } else {
1531  $text = $context->msg( 'missing-revision', $oldid )->plain();
1532  }
1533 
1534  } elseif ( $context->getAuthority()->probablyCan( 'edit', $title ) ) {
1535  $message = $isRegistered ? 'noarticletext' : 'noarticletextanon';
1536  $text = $context->msg( $message )->plain();
1537  } else {
1538  $text = $context->msg( 'noarticletext-nopermission' )->plain();
1539  }
1540 
1541  $dir = $context->getLanguage()->getDir();
1542  $lang = $context->getLanguage()->getHtmlCode();
1543  $outputPage->addWikiTextAsInterface( Xml::openElement( 'div', [
1544  'class' => "noarticletext mw-content-$dir",
1545  'dir' => $dir,
1546  'lang' => $lang,
1547  ] ) . "\n$text\n</div>" );
1548  }
1549  }
1550 
1555  private function showViewError( string $errortext ) {
1556  $outputPage = $this->getContext()->getOutput();
1557  $outputPage->setPageTitle( $this->getContext()->msg( 'errorpagetitle' ) );
1558  $outputPage->disableClientCache();
1559  $outputPage->setRobotPolicy( 'noindex,nofollow' );
1560  $outputPage->clearHTML();
1561  $outputPage->addHTML( Html::errorBox( $outputPage->parseAsContent( $errortext ) ) );
1562  }
1563 
1570  public function showDeletedRevisionHeader() {
1571  if ( !$this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1572  // Not deleted
1573  return true;
1574  }
1575  $outputPage = $this->getContext()->getOutput();
1576  // Used in wikilinks, should not contain whitespaces
1577  $titleText = $this->getTitle()->getPrefixedDBkey();
1578  // If the user is not allowed to see it...
1579  if ( !$this->mRevisionRecord->userCan(
1580  RevisionRecord::DELETED_TEXT,
1581  $this->getContext()->getAuthority()
1582  ) ) {
1583  $outputPage->addHTML(
1584  Html::warningBox(
1585  $outputPage->msg( 'rev-deleted-text-permission', $titleText )->parse(),
1586  'plainlinks'
1587  )
1588  );
1589 
1590  return false;
1591  // If the user needs to confirm that they want to see it...
1592  } elseif ( $this->getContext()->getRequest()->getInt( 'unhide' ) !== 1 ) {
1593  # Give explanation and add a link to view the revision...
1594  $oldid = intval( $this->getOldID() );
1595  $link = $this->getTitle()->getFullURL( "oldid={$oldid}&unhide=1" );
1596  $msg = $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ?
1597  'rev-suppressed-text-unhide' : 'rev-deleted-text-unhide';
1598  $outputPage->addHTML(
1599  Html::warningBox(
1600  $outputPage->msg( $msg, $link )->parse(),
1601  'plainlinks'
1602  )
1603  );
1604 
1605  return false;
1606  // We are allowed to see...
1607  } else {
1608  $msg = $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED )
1609  ? [ 'rev-suppressed-text-view', $titleText ]
1610  : [ 'rev-deleted-text-view', $titleText ];
1611  $outputPage->addHTML(
1612  Html::warningBox(
1613  $outputPage->msg( $msg[0], $msg[1] )->parse(),
1614  'plainlinks'
1615  )
1616  );
1617 
1618  return true;
1619  }
1620  }
1621 
1630  public function setOldSubtitle( $oldid = 0 ) {
1631  if ( !$this->getHookRunner()->onDisplayOldSubtitle( $this, $oldid ) ) {
1632  return;
1633  }
1634 
1635  $context = $this->getContext();
1636  $unhide = $context->getRequest()->getInt( 'unhide' ) === 1;
1637 
1638  # Cascade unhide param in links for easy deletion browsing
1639  $extraParams = [];
1640  if ( $unhide ) {
1641  $extraParams['unhide'] = 1;
1642  }
1643 
1644  if ( $this->mRevisionRecord && $this->mRevisionRecord->getId() === $oldid ) {
1645  $revisionRecord = $this->mRevisionRecord;
1646  } else {
1647  $revisionRecord = $this->revisionStore->getRevisionById( $oldid );
1648  }
1649  if ( !$revisionRecord ) {
1650  throw new LogicException( 'There should be a revision record at this point.' );
1651  }
1652 
1653  $timestamp = $revisionRecord->getTimestamp();
1654 
1655  $current = ( $oldid == $this->mPage->getLatest() );
1656  $language = $context->getLanguage();
1657  $user = $context->getUser();
1658 
1659  $td = $language->userTimeAndDate( $timestamp, $user );
1660  $tddate = $language->userDate( $timestamp, $user );
1661  $tdtime = $language->userTime( $timestamp, $user );
1662 
1663  # Show user links if allowed to see them. If hidden, then show them only if requested...
1664  $userlinks = Linker::revUserTools( $revisionRecord, !$unhide );
1665 
1666  $infomsg = $current && !$context->msg( 'revision-info-current' )->isDisabled()
1667  ? 'revision-info-current'
1668  : 'revision-info';
1669 
1670  $outputPage = $context->getOutput();
1671  $outputPage->addModuleStyles( [
1672  'mediawiki.action.styles',
1673  'mediawiki.interface.helpers.styles'
1674  ] );
1675 
1676  $revisionUser = $revisionRecord->getUser();
1677  $revisionInfo = "<div id=\"mw-{$infomsg}\">" .
1678  $context->msg( $infomsg, $td )
1679  ->rawParams( $userlinks )
1680  ->params(
1681  $revisionRecord->getId(),
1682  $tddate,
1683  $tdtime,
1684  $revisionUser ? $revisionUser->getName() : ''
1685  )
1686  ->rawParams( $this->commentFormatter->formatRevision(
1687  $revisionRecord,
1688  $user,
1689  true,
1690  !$unhide
1691  ) )
1692  ->parse() .
1693  "</div>";
1694 
1695  $lnk = $current
1696  ? $context->msg( 'currentrevisionlink' )->escaped()
1697  : $this->linkRenderer->makeKnownLink(
1698  $this->getTitle(),
1699  $context->msg( 'currentrevisionlink' )->text(),
1700  [],
1701  $extraParams
1702  );
1703  $curdiff = $current
1704  ? $context->msg( 'diff' )->escaped()
1705  : $this->linkRenderer->makeKnownLink(
1706  $this->getTitle(),
1707  $context->msg( 'diff' )->text(),
1708  [],
1709  [
1710  'diff' => 'cur',
1711  'oldid' => $oldid
1712  ] + $extraParams
1713  );
1714  $prevExist = (bool)$this->revisionStore->getPreviousRevision( $revisionRecord );
1715  $prevlink = $prevExist
1716  ? $this->linkRenderer->makeKnownLink(
1717  $this->getTitle(),
1718  $context->msg( 'previousrevision' )->text(),
1719  [],
1720  [
1721  'direction' => 'prev',
1722  'oldid' => $oldid
1723  ] + $extraParams
1724  )
1725  : $context->msg( 'previousrevision' )->escaped();
1726  $prevdiff = $prevExist
1727  ? $this->linkRenderer->makeKnownLink(
1728  $this->getTitle(),
1729  $context->msg( 'diff' )->text(),
1730  [],
1731  [
1732  'diff' => 'prev',
1733  'oldid' => $oldid
1734  ] + $extraParams
1735  )
1736  : $context->msg( 'diff' )->escaped();
1737  $nextlink = $current
1738  ? $context->msg( 'nextrevision' )->escaped()
1739  : $this->linkRenderer->makeKnownLink(
1740  $this->getTitle(),
1741  $context->msg( 'nextrevision' )->text(),
1742  [],
1743  [
1744  'direction' => 'next',
1745  'oldid' => $oldid
1746  ] + $extraParams
1747  );
1748  $nextdiff = $current
1749  ? $context->msg( 'diff' )->escaped()
1750  : $this->linkRenderer->makeKnownLink(
1751  $this->getTitle(),
1752  $context->msg( 'diff' )->text(),
1753  [],
1754  [
1755  'diff' => 'next',
1756  'oldid' => $oldid
1757  ] + $extraParams
1758  );
1759 
1760  $cdel = Linker::getRevDeleteLink(
1761  $context->getAuthority(),
1762  $revisionRecord,
1763  $this->getTitle()
1764  );
1765  if ( $cdel !== '' ) {
1766  $cdel .= ' ';
1767  }
1768 
1769  // the outer div is need for styling the revision info and nav in MobileFrontend
1770  $outputPage->addSubtitle(
1771  Html::warningBox(
1772  $revisionInfo .
1773  "<div id=\"mw-revision-nav\">" . $cdel .
1774  $context->msg( 'revision-nav' )->rawParams(
1775  $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff
1776  )->escaped() . "</div>",
1777  'mw-revision'
1778  )
1779  );
1780  }
1781 
1794  public static function getRedirectHeaderHtml( Language $lang, Title $target, $forceKnown = false ) {
1795  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1796 
1797  $html = '<ul class="redirectText">';
1798  if ( $forceKnown ) {
1799  $link = $linkRenderer->makeKnownLink(
1800  $target,
1801  $target->getFullText(),
1802  [],
1803  // Make sure wiki page redirects are not followed
1804  $target->isRedirect() ? [ 'redirect' => 'no' ] : []
1805  );
1806  } else {
1807  $link = $linkRenderer->makeLink(
1808  $target,
1809  $target->getFullText(),
1810  [],
1811  // Make sure wiki page redirects are not followed
1812  $target->isRedirect() ? [ 'redirect' => 'no' ] : []
1813  );
1814  }
1815  $html .= '<li>' . $link . '</li>';
1816  $html .= '</ul>';
1817 
1818  $redirectToText = wfMessage( 'redirectto' )->inLanguage( $lang )->escaped();
1819 
1820  return '<div class="redirectMsg">' .
1821  '<p>' . $redirectToText . '</p>' .
1822  $html .
1823  '</div>';
1824  }
1825 
1834  public function addHelpLink( $to, $overrideBaseUrl = false ) {
1835  $out = $this->getContext()->getOutput();
1836  $msg = $out->msg( 'namespace-' . $this->getTitle()->getNamespace() . '-helppage' );
1837 
1838  if ( !$msg->isDisabled() ) {
1839  $title = Title::newFromText( $msg->plain() );
1840  if ( $title instanceof Title ) {
1841  $out->addHelpLink( $title->getLocalURL(), true );
1842  }
1843  } else {
1844  $out->addHelpLink( $to, $overrideBaseUrl );
1845  }
1846  }
1847 
1851  public function render() {
1852  $this->getContext()->getRequest()->response()->header( 'X-Robots-Tag: noindex' );
1853  $this->getContext()->getOutput()->setArticleBodyOnly( true );
1854  // We later set 'enableSectionEditLinks=false' based on this; also used by ImagePage
1855  $this->viewIsRenderAction = true;
1856  $this->view();
1857  }
1858 
1862  public function protect() {
1863  $form = new ProtectionForm( $this );
1864  $form->execute();
1865  }
1866 
1870  public function unprotect() {
1871  $this->protect();
1872  }
1873 
1874  /* Caching functions */
1875 
1883  protected function tryFileCache() {
1884  static $called = false;
1885 
1886  if ( $called ) {
1887  wfDebug( "Article::tryFileCache(): called twice!?" );
1888  return false;
1889  }
1890 
1891  $called = true;
1892  if ( $this->isFileCacheable() ) {
1893  $cache = new HTMLFileCache( $this->getTitle(), 'view' );
1894  if ( $cache->isCacheGood( $this->mPage->getTouched() ) ) {
1895  wfDebug( "Article::tryFileCache(): about to load file" );
1896  $cache->loadFromFileCache( $this->getContext() );
1897  return true;
1898  } else {
1899  wfDebug( "Article::tryFileCache(): starting buffer" );
1900  ob_start( [ &$cache, 'saveToFileCache' ] );
1901  }
1902  } else {
1903  wfDebug( "Article::tryFileCache(): not cacheable" );
1904  }
1905 
1906  return false;
1907  }
1908 
1914  public function isFileCacheable( $mode = HTMLFileCache::MODE_NORMAL ) {
1915  $cacheable = false;
1916 
1917  if ( HTMLFileCache::useFileCache( $this->getContext(), $mode ) ) {
1918  $cacheable = $this->mPage->getId()
1919  && !$this->mRedirectedFrom && !$this->getTitle()->isRedirect();
1920  // Extension may have reason to disable file caching on some pages.
1921  if ( $cacheable ) {
1922  $cacheable = $this->getHookRunner()->onIsFileCacheable( $this ) ?? false;
1923  }
1924  }
1925 
1926  return $cacheable;
1927  }
1928 
1942  public function getParserOutput( $oldid = null, UserIdentity $user = null ) {
1943  if ( $user === null ) {
1944  $parserOptions = $this->getParserOptions();
1945  } else {
1946  $parserOptions = $this->mPage->makeParserOptions( $user );
1947  $parserOptions->setRenderReason( 'page-view' );
1948  }
1949 
1950  return $this->mPage->getParserOutput( $parserOptions, $oldid );
1951  }
1952 
1957  public function getParserOptions() {
1958  $parserOptions = $this->mPage->makeParserOptions( $this->getContext() );
1959  $parserOptions->setRenderReason( 'page-view' );
1960  return $parserOptions;
1961  }
1962 
1969  public function setContext( $context ) {
1970  $this->mContext = $context;
1971  }
1972 
1979  public function getContext(): IContextSource {
1980  if ( $this->mContext instanceof IContextSource ) {
1981  return $this->mContext;
1982  } else {
1983  wfDebug( __METHOD__ . " called and \$mContext is null. " .
1984  "Return RequestContext::getMain()" );
1985  return RequestContext::getMain();
1986  }
1987  }
1988 
1998  public function __get( $fname ) {
1999  wfDeprecatedMsg( "Accessing Article::\$$fname is deprecated since MediaWiki 1.35",
2000  '1.35' );
2001 
2002  if ( property_exists( $this->mPage, $fname ) ) {
2003  return $this->mPage->$fname;
2004  }
2005  trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
2006  }
2007 
2017  public function __set( $fname, $fvalue ) {
2018  wfDeprecatedMsg( "Setting Article::\$$fname is deprecated since MediaWiki 1.35",
2019  '1.35' );
2020 
2021  if ( property_exists( $this->mPage, $fname ) ) {
2022  $this->mPage->$fname = $fvalue;
2023  // Note: extensions may want to toss on new fields
2024  } elseif ( !in_array( $fname, [ 'mContext', 'mPage' ] ) ) {
2025  $this->mPage->$fname = $fvalue;
2026  } else {
2027  trigger_error( 'Inaccessible property via __set(): ' . $fname, E_USER_NOTICE );
2028  }
2029  }
2030 
2036  public function getActionOverrides() {
2037  return $this->mPage->getActionOverrides();
2038  }
2039 
2045  public function getTimestamp() {
2046  wfDeprecated( __METHOD__, '1.35' );
2047  return $this->mPage->getTimestamp();
2048  }
2049 }
const NS_USER
Definition: Defines.php:66
const NS_FILE
Definition: Defines.php:70
const RC_NEW
Definition: Defines.php:117
const NS_MEDIAWIKI
Definition: Defines.php:72
const RC_LOG
Definition: Defines.php:118
const NS_MEDIA
Definition: Defines.php:52
const NS_USER_TALK
Definition: Defines.php:67
const NS_CATEGORY
Definition: Defines.php:78
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that a deprecated feature was used.
getContext()
if(!defined('MW_SETUP_CALLBACK'))
Definition: WebStart.php:88
Legacy class representing an editable page and handling UI for some page actions.
Definition: Article.php:56
static newFromWikiPage(WikiPage $page, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition: Article.php:216
getContext()
Gets the context this Article is executed in.
Definition: Article.php:1979
getOldIDFromRequest()
Sets $this->mRedirectUrl to a correct URL if the query parameters are incorrect.
Definition: Article.php:290
getRedirectedFrom()
Get the page this view was redirected from.
Definition: Article.php:227
Title null $mRedirectedFrom
Title from which we were redirected here, if any.
Definition: Article.php:77
bool $viewIsRenderAction
Whether render() was called.
Definition: Article.php:100
view()
This is the default action of the index.php entry point: just view the page of the given title.
Definition: Article.php:449
__construct(Title $title, $oldId=null)
Definition: Article.php:143
getRobotPolicy( $action, ParserOutput $pOutput=null)
Get the robot policy to be used for the current view.
Definition: Article.php:974
static purgePatrolFooterCache( $articleID)
Purge the cache used to check if it is worth showing the patrol footer For example,...
Definition: Article.php:1368
ParserOutput null false $mParserOutput
The ParserOutput generated for viewing the page, initialized by view().
Definition: Article.php:93
getOldID()
Definition: Article.php:277
LinkRenderer $linkRenderer
Definition: Article.php:105
getTitle()
Get the title object of the article.
Definition: Article.php:245
getActionOverrides()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2036
isDiffOnlyView()
Definition: Article.php:960
adjustDisplayTitle(ParserOutput $pOutput)
Adjust title for pages with displaytitle, -{T|}- or language conversion.
Definition: Article.php:886
showDeletedRevisionHeader()
If the revision requested for view is deleted, check permissions.
Definition: Article.php:1570
getParserOptions()
Get parser options suitable for rendering the primary article wikitext.
Definition: Article.php:1957
clear()
Definition: Article.php:259
IContextSource null $mContext
The context this Article is executed in.
Definition: Article.php:65
static getRedirectHeaderHtml(Language $lang, Title $target, $forceKnown=false)
Return the HTML for the top of a redirect page.
Definition: Article.php:1794
getParserOutput( $oldid=null, UserIdentity $user=null)
#-
Definition: Article.php:1942
protect()
action=protect handler
Definition: Article.php:1862
string false $mRedirectUrl
URL to redirect to or false if none.
Definition: Article.php:80
isCurrent()
Returns true if the currently-referenced revision is the current edit to this page (and it exists).
Definition: Article.php:416
showMissingArticle()
Show the error text for a missing article.
Definition: Article.php:1377
__set( $fname, $fvalue)
Definition: Article.php:2017
unprotect()
action=unprotect handler (alias)
Definition: Article.php:1870
newPage(Title $title)
Definition: Article.php:161
getPage()
Get the WikiPage object of this instance.
Definition: Article.php:255
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Definition: Article.php:1834
getTimestamp()
Definition: Article.php:2045
static newFromID( $id)
Constructor from a page id.
Definition: Article.php:170
int null $mOldId
The oldid of the article that was requested to be shown, 0 for the current revision.
Definition: Article.php:74
static formatRobotPolicy( $policy)
Converts a String robot policy into an associative array, to allow merging of several policies using ...
Definition: Article.php:1058
fetchRevisionRecord()
Fetches the revision to work on.
Definition: Article.php:358
showPatrolFooter()
If patrol is possible, output a patrol UI box.
Definition: Article.php:1194
setOldSubtitle( $oldid=0)
Generate the navigation links when browsing through an article revisions It shows the information as:...
Definition: Article.php:1630
showViewFooter()
Show the footer section of an ordinary page view.
Definition: Article.php:1170
WikiPage $mPage
The WikiPage object of this instance.
Definition: Article.php:68
setRedirectedFrom(Title $from)
Tell the page view functions that this view was redirected from another page on the wiki.
Definition: Article.php:236
isFileCacheable( $mode=HTMLFileCache::MODE_NORMAL)
Check if the page can be cached.
Definition: Article.php:1914
tryFileCache()
checkLastModified returns true if it has taken care of all output to the client that is necessary for...
Definition: Article.php:1883
getRevIdFetched()
Use this to fetch the rev ID used on page views.
Definition: Article.php:435
showNamespaceHeader()
Show a header specific to the namespace currently being viewed, like [[MediaWiki:Talkpagetext]].
Definition: Article.php:1158
__get( $fname)
Definition: Article.php:1998
static newFromTitle( $title, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition: Article.php:182
showDiffPage()
Show a diff page according to current request variables.
Definition: Article.php:901
render()
Handle action=render.
Definition: Article.php:1851
showRedirectedFromHeader()
If this request is a redirect view, send "redirected from" subtitle to the output.
Definition: Article.php:1085
setContext( $context)
Sets the context this Article is executed in.
Definition: Article.php:1969
getCacheRevisionId()
Definition: CacheTime.php:98
getCacheTime()
Definition: CacheTime.php:67
Special handling for category description pages.
msg( $key,... $params)
Get a Message object with context set Parameters are the same as wfMessage()
Page view caching in the file system.
static useFileCache(IContextSource $context, $mode=self::MODE_NORMAL)
Check if pages can be cached for this request/user.
Rendering of file description pages.
Definition: ImagePage.php:35
Base class for language-specific code.
Definition: Language.php:57
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
This is the main service interface for converting single-line comments from various DB comment fields...
The HTML user interface for page editing.
Definition: EditPage.php:144
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:565
This class is a collection of static functions that serve two purposes:
Definition: Html.php:55
Class that generates HTML for internal links.
makeKnownLink( $target, $text=null, array $extraAttribs=[], array $query=[])
Make a link that's styled as if the target page exists (usually a "blue link", although the styling m...
makeLink( $target, $text=null, array $extraAttribs=[], array $query=[])
Render a wikilink.
Some internal bits split of from Skin.php.
Definition: Linker.php:67
A class containing constants representing the names of configuration variables.
Service locator for MediaWiki core services.
Service for getting rendered output of a given page.
Handles the page protection UI and backend.
Service for creating WikiPage objects.
A StatusValue for permission errors.
Exception raised when the text of a revision is permanently missing or corrupt.
Page revision base class.
isCurrent()
Checks whether the revision record is a stored current revision.
getPageId( $wikiId=self::LOCAL)
Get the page ID.
getTimestamp()
MCR migration note: this replaced Revision::getTimestamp.
getSlot( $role, $audience=self::FOR_PUBLIC, Authority $performer=null)
Returns meta-data for the given slot.
getContent( $role, $audience=self::FOR_PUBLIC, Authority $performer=null)
Returns the Content of the given slot of this revision.
getId( $wikiId=self::LOCAL)
Get revision ID.
Service for looking up page revisions.
Value object representing a content slot associated with a page revision.
Definition: SlotRecord.php:40
Represents a title within MediaWiki.
Definition: Title.php:82
getFullText()
Get the prefixed title with spaces, plus any fragment (part beginning with '#')
Definition: Title.php:1974
isRedirect( $flags=0)
Is this an article that is a redirect page? Uses link cache, adding it if necessary.
Definition: Title.php:2719
UserNameUtils service.
Provides access to user options.
This is one of the Core classes and should be read at least once by any new developers.
Definition: OutputPage.php:60
disable()
Disable output completely, i.e.
setArticleFlag( $newVal)
Set whether the displayed content is related to the source of the corresponding article on the wiki S...
setRobotPolicy( $policy)
Set the robot policy for the page: http://www.robotstxt.org/meta.html
Definition: OutputPage.php:943
setIndexPolicy( $policy)
Set the index policy for the page, but leave the follow policy un- touched.
setPageTitle( $name)
"Page title" means the contents of <h1>.
redirect( $url, $responsecode='302')
Redirect to $url rather than displaying the normal page.
Definition: OutputPage.php:435
setLastModified( $timestamp)
Override the last modified timestamp.
Definition: OutputPage.php:932
adaptCdnTTL( $mtime, $minTTL=0, $maxTTL=0)
Get TTL in [$minTTL,$maxTTL] and pass it to lowerCdnMaxage()
setRevisionIsCurrent(bool $isCurrent)
Set whether the revision displayed (as set in ::setRevisionId()) is the latest revision of the page.
wrapWikiMsg( $wrap,... $msgSpecs)
This function takes a number of message/argument specifications, wraps them in some overall structure...
setCdnMaxage( $maxage)
Set the value of the "s-maxage" part of the "Cache-control" HTTP header.
parseAsContent( $text, $linestart=true)
Parse wikitext in the page content language and return the HTML.
addParserOutput(ParserOutput $parserOutput, $poOptions=[])
Add everything from a ParserOutput object.
addWikiTextAsInterface( $text, $linestart=true, PageReference $title=null)
Convert wikitext in the user interface language to HTML and add it to the buffer.
setFollowPolicy( $policy)
Set the follow policy for the page, but leave the index policy un- touched.
isPrintable()
Return whether the page is "printable".
addModuleStyles( $modules)
Load the styles of one or more style-only ResourceLoader modules on this page.
Definition: OutputPage.php:658
setHTMLTitle( $name)
"HTML title" means the contents of "<title>".
disableClientCache()
Force the page to send nocache headers.
addSubtitle( $str)
Add $str to the subtitle.
setRevisionId( $revid)
Set the revision ID which will be seen by the wiki text parser for things such as embedded {{REVISION...
clearHTML()
Clear the body HTML.
setPreventClickjacking(bool $enable)
Set the prevent-clickjacking flag.
addHTML( $text)
Append $text to the body HTML.
addJsConfigVars( $keys, $value=null)
Add one or more variables to be set in mw.config in JavaScript.
setCanonicalUrl( $url)
Set the URL to be used for the <link rel=canonical>>.
Definition: OutputPage.php:518
setRevisionTimestamp( $timestamp)
Set the timestamp of the revision which will be displayed.
setRedirectedFrom(PageReference $t)
Set $mRedirectedFrom, the page which redirected us to the current page.
prependHTML( $text)
Prepend $text to the body HTML.
addModules( $modules)
Load one or more ResourceLoader modules on this page.
Definition: OutputPage.php:632
addWikiTextAsContent( $text, $linestart=true, PageReference $title=null)
Convert wikitext in the page content language to HTML and add it to the buffer.
Used to show archived pages and eventually restore them.
Definition: PageArchive.php:32
Set options of the Parser.
setIsPrintable( $x)
Parsing the printable version of the page?
setRenderReason(string $renderReason)
Sets reason for rendering the content.
static formatPageTitle( $nsText, $nsSeparator, $mainText)
Add HTML tags marking the parts of a page title, to be displayed in the first heading of the page.
Definition: Parser.php:6484
static newSpec(int $revisionId, int $pageId, array $params=[])
Show an error when a user tries to do something they do not have the necessary permissions for.
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...
static newFromConds( $conds, $fname=__METHOD__, $dbType=DB_REPLICA)
Find the first recent change matching some specific conditions.
static getMain()
Get the RequestContext object associated with the main request.
hasMessage( $message)
Returns true if the specified message is present as a warning or error.
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:73
isOK()
Returns whether the operation completed.
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:85
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:46
getWikiText( $shortContext=false, $longContext=false, $lang=null)
Get the error list as a wikitext formatted list.
Definition: Status.php:191
static newFromName( $name, $validate='valid')
Definition: User.php:592
Base representation for an editable wiki page.
Definition: WikiPage.php:75
getTitle()
Get the title object of the article.
Definition: WikiPage.php:317
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:113
Interface for objects which can provide a MediaWiki context on request.
This interface represents the authority associated the current execution context, such as a web reque...
Definition: Authority.php:37
Interface for objects representing user identity.
Interface for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
Definition: Page.php:31
const DB_REPLICA
Definition: defines.php:26
$content
Definition: router.php:76
return true
Definition: router.php:90
if(!isset( $args[0])) $lang