MediaWiki  master
Article.php
Go to the documentation of this file.
1 <?php
22 use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
36 use Wikimedia\IPUtils;
37 use Wikimedia\NonSerializable\NonSerializableTrait;
38 
48 class Article implements Page {
49  use ProtectedHookAccessorTrait;
50  use NonSerializableTrait;
51 
57  protected $mContext;
58 
60  protected $mPage;
61 
66  public $mOldId;
67 
69  public $mRedirectedFrom = null;
70 
72  public $mRedirectUrl = false;
73 
78  private $fetchResult = null;
79 
85  public $mParserOutput = null;
86 
92  protected $viewIsRenderAction = false;
93 
97  protected $linkRenderer;
98 
102  private $revisionStore;
103 
107  private $watchlistManager;
108 
112  private $userNameUtils;
113 
117  private $userOptionsLookup;
118 
125  private $mRevisionRecord = null;
126 
131  public function __construct( Title $title, $oldId = null ) {
132  $this->mOldId = $oldId;
133  $this->mPage = $this->newPage( $title );
134 
135  $services = MediaWikiServices::getInstance();
136  $this->linkRenderer = $services->getLinkRenderer();
137  $this->revisionStore = $services->getRevisionStore();
138  $this->watchlistManager = $services->getWatchlistManager();
139  $this->userNameUtils = $services->getUserNameUtils();
140  $this->userOptionsLookup = $services->getUserOptionsLookup();
141  }
142 
147  protected function newPage( Title $title ) {
148  return new WikiPage( $title );
149  }
150 
156  public static function newFromID( $id ) {
157  $t = Title::newFromID( $id );
158  return $t == null ? null : new static( $t );
159  }
160 
168  public static function newFromTitle( $title, IContextSource $context ) {
169  if ( $title->getNamespace() === NS_MEDIA ) {
170  // XXX: This should not be here, but where should it go?
171  $title = Title::makeTitle( NS_FILE, $title->getDBkey() );
172  }
173 
174  $page = null;
175  // @phan-suppress-next-line PhanTypeMismatchArgument Type mismatch on pass-by-ref args
176  Hooks::runner()->onArticleFromTitle( $title, $page, $context );
177  if ( !$page ) {
178  switch ( $title->getNamespace() ) {
179  case NS_FILE:
180  $page = new ImagePage( $title );
181  break;
182  case NS_CATEGORY:
183  $page = new CategoryPage( $title );
184  break;
185  default:
186  $page = new Article( $title );
187  }
188  }
189  $page->setContext( $context );
190 
191  return $page;
192  }
193 
201  public static function newFromWikiPage( WikiPage $page, IContextSource $context ) {
202  $article = self::newFromTitle( $page->getTitle(), $context );
203  $article->mPage = $page; // override to keep process cached vars
204  return $article;
205  }
206 
212  public function getRedirectedFrom() {
213  return $this->mRedirectedFrom;
214  }
215 
221  public function setRedirectedFrom( Title $from ) {
222  $this->mRedirectedFrom = $from;
223  }
224 
230  public function getTitle() {
231  return $this->mPage->getTitle();
232  }
233 
240  public function getPage() {
241  return $this->mPage;
242  }
243 
244  public function clear() {
245  $this->mRedirectedFrom = null; # Title object if set
246  $this->mRedirectUrl = false;
247  $this->mRevisionRecord = null;
248  $this->fetchResult = null;
249 
250  // TODO hard-deprecate direct access to public fields
251 
252  $this->mPage->clear();
253  }
254 
262  public function getOldID() {
263  if ( $this->mOldId === null ) {
264  $this->mOldId = $this->getOldIDFromRequest();
265  }
266 
267  return $this->mOldId;
268  }
269 
275  public function getOldIDFromRequest() {
276  $this->mRedirectUrl = false;
277 
278  $request = $this->getContext()->getRequest();
279  $oldid = $request->getIntOrNull( 'oldid' );
280 
281  if ( $oldid === null ) {
282  return 0;
283  }
284 
285  if ( $oldid !== 0 ) {
286  # Load the given revision and check whether the page is another one.
287  # In that case, update this instance to reflect the change.
288  if ( $oldid === $this->mPage->getLatest() ) {
289  $this->mRevisionRecord = $this->mPage->getRevisionRecord();
290  } else {
291  $this->mRevisionRecord = $this->revisionStore->getRevisionById( $oldid );
292  if ( $this->mRevisionRecord !== null ) {
293  $revPageId = $this->mRevisionRecord->getPageId();
294  // Revision title doesn't match the page title given?
295  if ( $this->mPage->getId() != $revPageId ) {
296  $function = get_class( $this->mPage ) . '::newFromID';
297  $this->mPage = $function( $revPageId );
298  }
299  }
300  }
301  }
302 
303  $oldRev = $this->mRevisionRecord;
304  if ( $request->getRawVal( 'direction' ) === 'next' ) {
305  $nextid = 0;
306  if ( $oldRev ) {
307  $nextRev = $this->revisionStore->getNextRevision( $oldRev );
308  if ( $nextRev ) {
309  $nextid = $nextRev->getId();
310  }
311  }
312  if ( $nextid ) {
313  $oldid = $nextid;
314  $this->mRevisionRecord = null;
315  } else {
316  $this->mRedirectUrl = $this->getTitle()->getFullURL( 'redirect=no' );
317  }
318  } elseif ( $request->getRawVal( 'direction' ) === 'prev' ) {
319  $previd = 0;
320  if ( $oldRev ) {
321  $prevRev = $this->revisionStore->getPreviousRevision( $oldRev );
322  if ( $prevRev ) {
323  $previd = $prevRev->getId();
324  }
325  }
326  if ( $previd ) {
327  $oldid = $previd;
328  $this->mRevisionRecord = null;
329  }
330  }
331 
332  return $oldid;
333  }
334 
344  public function fetchRevisionRecord() {
345  if ( $this->fetchResult ) {
346  return $this->mRevisionRecord;
347  }
348 
349  $oldid = $this->getOldID();
350 
351  // $this->mRevisionRecord might already be fetched by getOldIDFromRequest()
352  if ( !$this->mRevisionRecord ) {
353  if ( !$oldid ) {
354  $this->mRevisionRecord = $this->mPage->getRevisionRecord();
355 
356  if ( !$this->mRevisionRecord ) {
357  wfDebug( __METHOD__ . " failed to find page data for title " .
358  $this->getTitle()->getPrefixedText() );
359 
360  // Output for this case is done by showMissingArticle().
361  $this->fetchResult = Status::newFatal( 'noarticletext' );
362  return null;
363  }
364  } else {
365  $this->mRevisionRecord = $this->revisionStore->getRevisionById( $oldid );
366 
367  if ( !$this->mRevisionRecord ) {
368  wfDebug( __METHOD__ . " failed to load revision, rev_id $oldid" );
369 
370  $this->fetchResult = Status::newFatal( 'missing-revision', $oldid );
371  return null;
372  }
373  }
374  }
375 
376  if ( !$this->mRevisionRecord->userCan( RevisionRecord::DELETED_TEXT, $this->getContext()->getAuthority() ) ) {
377  wfDebug( __METHOD__ . " failed to retrieve content of revision " . $this->mRevisionRecord->getId() );
378 
379  // Output for this case is done by showDeletedRevisionHeader().
380  // title used in wikilinks, should not contain whitespaces
381  $this->fetchResult = new Status;
382  $title = $this->getTitle()->getPrefixedDBkey();
383 
384  if ( $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ) {
385  $this->fetchResult->fatal( 'rev-suppressed-text' );
386  } else {
387  $this->fetchResult->fatal( 'rev-deleted-text-permission', $title );
388  }
389 
390  return null;
391  }
392 
393  $this->fetchResult = Status::newGood( $this->mRevisionRecord );
394  return $this->mRevisionRecord;
395  }
396 
402  public function isCurrent() {
403  # If no oldid, this is the current version.
404  if ( $this->getOldID() == 0 ) {
405  return true;
406  }
407 
408  return $this->mPage->exists() &&
409  $this->mRevisionRecord &&
410  $this->mRevisionRecord->isCurrent();
411  }
412 
421  public function getRevIdFetched() {
422  if ( $this->fetchResult && $this->fetchResult->isOK() ) {
423  return $this->fetchResult->value->getId();
424  } else {
425  return $this->mPage->getLatest();
426  }
427  }
428 
433  public function view() {
434  $context = $this->getContext();
435  $useFileCache = $context->getConfig()->get( MainConfigNames::UseFileCache );
436 
437  # Get variables from query string
438  # As side effect this will load the revision and update the title
439  # in a revision ID is passed in the request, so this should remain
440  # the first call of this method even if $oldid is used way below.
441  $oldid = $this->getOldID();
442 
443  $authority = $context->getAuthority();
444  # Another check in case getOldID() is altering the title
445  $permissionStatus = PermissionStatus::newEmpty();
446  if ( !$authority
447  ->authorizeRead( 'read', $this->getTitle(), $permissionStatus )
448  ) {
449  wfDebug( __METHOD__ . ": denied on secondary read check" );
450  throw new PermissionsError( 'read', $permissionStatus );
451  }
452 
453  $outputPage = $context->getOutput();
454  # getOldID() may as well want us to redirect somewhere else
455  if ( $this->mRedirectUrl ) {
456  $outputPage->redirect( $this->mRedirectUrl );
457  wfDebug( __METHOD__ . ": redirecting due to oldid" );
458 
459  return;
460  }
461 
462  # If we got diff in the query, we want to see a diff page instead of the article.
463  if ( $context->getRequest()->getCheck( 'diff' ) ) {
464  wfDebug( __METHOD__ . ": showing diff page" );
465  $this->showDiffPage();
466 
467  return;
468  }
469 
470  # Set page title (may be overridden from ParserOutput if title conversion is enabled or DISPLAYTITLE is used)
472  str_replace( '_', ' ', $this->getTitle()->getNsText() ),
473  ':',
474  $this->getTitle()->getText()
475  ) );
476 
477  $outputPage->setArticleFlag( true );
478  # Allow frames by default
479  $outputPage->setPreventClickjacking( false );
480 
481  $parserOptions = $this->getParserOptions();
482  $poOptions = [];
483  # Allow extensions to vary parser options used for article rendering
484  Hooks::runner()->onArticleParserOptions( $this, $parserOptions );
485  # Render printable version, use printable version cache
486  if ( $outputPage->isPrintable() ) {
487  $parserOptions->setIsPrintable( true );
488  $poOptions['enableSectionEditLinks'] = false;
489  $outputPage->prependHTML(
491  $outputPage->msg( 'printableversion-deprecated-warning' )->escaped()
492  )
493  );
494  } elseif ( $this->viewIsRenderAction || !$this->isCurrent() ||
495  !$authority->probablyCan( 'edit', $this->getTitle() )
496  ) {
497  $poOptions['enableSectionEditLinks'] = false;
498  }
499 
500  # Try client and file cache
501  if ( $oldid === 0 && $this->mPage->checkTouched() ) {
502  # Try to stream the output from file cache
503  if ( $useFileCache && $this->tryFileCache() ) {
504  wfDebug( __METHOD__ . ": done file cache" );
505  # tell wgOut that output is taken care of
506  $outputPage->disable();
507  $this->mPage->doViewUpdates( $authority, $oldid );
508 
509  return;
510  }
511  }
512 
513  $this->showRedirectedFromHeader();
514  $this->showNamespaceHeader();
515 
516  if ( $this->viewIsRenderAction ) {
517  $poOptions += [ 'absoluteURLs' => true ];
518  }
519  $poOptions += [ 'includeDebugInfo' => true ];
520 
521  $continue =
522  $this->generateContentOutput( $authority, $parserOptions, $oldid, $outputPage, $poOptions );
523 
524  if ( !$continue ) {
525  return;
526  }
527 
528  # For the main page, overwrite the <title> element with the con-
529  # tents of 'pagetitle-view-mainpage' instead of the default (if
530  # that's not empty).
531  # This message always exists because it is in the i18n files
532  if ( $this->getTitle()->isMainPage() ) {
533  $msg = $context->msg( 'pagetitle-view-mainpage' )->inContentLanguage();
534  if ( !$msg->isDisabled() ) {
535  $outputPage->setHTMLTitle( $msg->text() );
536  }
537  }
538 
539  # Use adaptive TTLs for CDN so delayed/failed purges are noticed less often.
540  # This could use getTouched(), but that could be scary for major template edits.
541  $outputPage->adaptCdnTTL( $this->mPage->getTimestamp(), IExpiringStore::TTL_DAY );
542 
543  $this->showViewFooter();
544  $this->mPage->doViewUpdates( $authority, $oldid, $this->fetchRevisionRecord() );
545 
546  # Load the postEdit module if the user just saved this revision
547  # See also EditPage::setPostEditCookie
548  $request = $context->getRequest();
550  $postEdit = $request->getCookie( $cookieKey );
551  if ( $postEdit ) {
552  # Clear the cookie. This also prevents caching of the response.
553  $request->response()->clearCookie( $cookieKey );
554  $outputPage->addJsConfigVars( 'wgPostEdit', $postEdit );
555  $outputPage->addModules( 'mediawiki.action.view.postEdit' ); // FIXME: test this
556  }
557  }
558 
571  private function generateContentOutput(
572  Authority $performer,
573  ParserOptions $parserOptions,
574  int $oldid,
575  OutputPage $outputPage,
576  array $textOptions
577  ): bool {
578  # Should the parser cache be used?
579  $useParserCache = true;
580  $pOutput = null;
581  $parserOutputAccess = MediaWikiServices::getInstance()->getParserOutputAccess();
582 
583  // NOTE: $outputDone and $useParserCache may be changed by the hook
584  $this->getHookRunner()->onArticleViewHeader( $this, $outputDone, $useParserCache );
585  if ( $outputDone ) {
586  if ( $outputDone instanceof ParserOutput ) {
587  $pOutput = $outputDone;
588  }
589 
590  if ( $pOutput ) {
591  $this->doOutputMetaData( $pOutput, $outputPage );
592  }
593  return true;
594  }
595 
596  // Early abort if the page doesn't exist
597  if ( !$this->mPage->exists() ) {
598  wfDebug( __METHOD__ . ": showing missing article" );
599  $this->showMissingArticle();
600  $this->mPage->doViewUpdates( $performer );
601  return false; // skip all further output to OutputPage
602  }
603 
604  // Try the latest parser cache
605  // NOTE: try latest-revision cache first to avoid loading revision.
606  if ( $useParserCache && !$oldid ) {
607  $pOutput = $parserOutputAccess->getCachedParserOutput(
608  $this->getPage(),
609  $parserOptions,
610  null,
611  ParserOutputAccess::OPT_NO_AUDIENCE_CHECK // we already checked
612  );
613 
614  if ( $pOutput ) {
615  $this->doOutputFromParserCache( $pOutput, $outputPage, $textOptions );
616  $this->doOutputMetaData( $pOutput, $outputPage );
617  return true;
618  }
619  }
620 
621  $rev = $this->fetchRevisionRecord();
622  if ( !$this->fetchResult->isOK() ) {
623  $this->showViewError( $this->fetchResult->getWikiText(
624  false, false, $this->getContext()->getLanguage()
625  ) );
626  return true;
627  }
628 
629  # Are we looking at an old revision
630  if ( $oldid ) {
631  $this->setOldSubtitle( $oldid );
632 
633  if ( !$this->showDeletedRevisionHeader() ) {
634  wfDebug( __METHOD__ . ": cannot view deleted revision" );
635  return false; // skip all further output to OutputPage
636  }
637 
638  // Try the old revision parser cache
639  // NOTE: Repeating cache check for old revision to avoid fetching $rev
640  // before it's absolutely necessary.
641  if ( $useParserCache ) {
642  $pOutput = $parserOutputAccess->getCachedParserOutput(
643  $this->getPage(),
644  $parserOptions,
645  $rev,
646  ParserOutputAccess::OPT_NO_AUDIENCE_CHECK // we already checked in fetchRevisionRecord
647  );
648 
649  if ( $pOutput ) {
650  $this->doOutputFromParserCache( $pOutput, $outputPage, $textOptions );
651  $this->doOutputMetaData( $pOutput, $outputPage );
652  return true;
653  }
654  }
655  }
656 
657  # Ensure that UI elements requiring revision ID have
658  # the correct version information.
659  $outputPage->setRevisionId( $this->getRevIdFetched() );
660  $outputPage->setRevisionIsCurrent( $rev->isCurrent() );
661  # Preload timestamp to avoid a DB hit
662  $outputPage->setRevisionTimestamp( $rev->getTimestamp() );
663 
664  # Pages containing custom CSS or JavaScript get special treatment
665  if ( $this->getTitle()->isSiteConfigPage() || $this->getTitle()->isUserConfigPage() ) {
666  $dir = $this->getContext()->getLanguage()->getDir();
667  $lang = $this->getContext()->getLanguage()->getHtmlCode();
668 
669  $outputPage->wrapWikiMsg(
670  "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
671  'clearyourcache'
672  );
673  $outputPage->addModuleStyles( 'mediawiki.action.styles' );
674  } elseif ( !$this->getHookRunner()->onArticleRevisionViewCustom(
675  $rev,
676  $this->getTitle(),
677  $oldid,
678  $outputPage )
679  ) {
680  // NOTE: sync with hooks called in DifferenceEngine::renderNewRevision()
681  // Allow extensions do their own custom view for certain pages
682  $this->doOutputMetaData( $pOutput, $outputPage );
683  return true;
684  }
685 
686  # Run the parse, protected by a pool counter
687  wfDebug( __METHOD__ . ": doing uncached parse" );
688 
689  if ( !$rev ) {
690  // No revision, abort! Shouldn't happen.
691  return false;
692  }
693 
694  $opt = 0;
695 
696  // we already checked the cache in case 2, don't check again.
697  $opt |= ParserOutputAccess::OPT_NO_CHECK_CACHE;
698 
699  // we already checked in fetchRevisionRecord()
700  $opt |= ParserOutputAccess::OPT_NO_AUDIENCE_CHECK;
701 
702  if ( !$rev->getId() || !$useParserCache ) {
703  // fake revision or uncacheable options
704  $opt |= ParserOutputAccess::OPT_NO_CACHE;
705  }
706 
707  $renderStatus = $parserOutputAccess->getParserOutput(
708  $this->getPage(),
709  $parserOptions,
710  $rev,
711  $opt
712  );
713 
714  $this->doOutputFromRenderStatus(
715  $rev,
716  $renderStatus,
717  $outputPage,
718  $textOptions
719  );
720 
721  if ( !$renderStatus->isOK() ) {
722  return true;
723  }
724 
725  $pOutput = $renderStatus->getValue();
726  $this->doOutputMetaData( $pOutput, $outputPage );
727  return true;
728  }
729 
734  private function doOutputMetaData( ?ParserOutput $pOutput, OutputPage $outputPage ) {
735  # Adjust title for main page & pages with displaytitle
736  if ( $pOutput ) {
737  $this->adjustDisplayTitle( $pOutput );
738  }
739 
740  # Check for any __NOINDEX__ tags on the page using $pOutput
741  $policy = $this->getRobotPolicy( 'view', $pOutput ?: null );
742  $outputPage->setIndexPolicy( $policy['index'] );
743  $outputPage->setFollowPolicy( $policy['follow'] ); // FIXME: test this
744 
745  $this->mParserOutput = $pOutput;
746  }
747 
753  private function doOutputFromParserCache(
754  ParserOutput $pOutput,
755  OutputPage $outputPage,
756  array $textOptions
757  ) {
758  # Ensure that UI elements requiring revision ID have
759  # the correct version information.
760  $oldid = $pOutput->getCacheRevisionId() ?? $this->getRevIdFetched();
761  $outputPage->setRevisionId( $oldid );
762  $outputPage->setRevisionIsCurrent( $oldid === $this->mPage->getLatest() );
763  # Ensure that the skin has the necessary ToC information
764  # (and do this before OutputPage::addParserOutput() calls the
765  # OutputPageParserOutput hook)
766  $outputPage->setSections( $pOutput->getSections() );
767  $outputPage->addParserOutput( $pOutput, $textOptions );
768  # Preload timestamp to avoid a DB hit
769  $cachedTimestamp = $pOutput->getTimestamp();
770  if ( $cachedTimestamp !== null ) {
771  $outputPage->setRevisionTimestamp( $cachedTimestamp );
772  $this->mPage->setTimestamp( $cachedTimestamp );
773  }
774  }
775 
782  private function doOutputFromRenderStatus(
783  ?RevisionRecord $rev,
784  Status $renderStatus,
785  OutputPage $outputPage,
786  array $textOptions
787  ) {
788  $context = $this->getContext();
789  $cdnMaxageStale = $context->getConfig()->get( MainConfigNames::CdnMaxageStale );
790  $ok = $renderStatus->isOK();
791 
792  $pOutput = $ok ? $renderStatus->getValue() : null;
793 
794  // Cache stale ParserOutput object with a short expiry
795  if ( $ok && $renderStatus->hasMessage( 'view-pool-dirty-output' ) ) {
796  $outputPage->setCdnMaxage( $cdnMaxageStale );
797  $outputPage->setLastModified( $pOutput->getCacheTime() );
798  $staleReason = $renderStatus->hasMessage( 'view-pool-contention' )
799  ? $context->msg( 'view-pool-contention' )
800  : $context->msg( 'view-pool-timeout' );
801  $outputPage->addHTML( "<!-- parser cache is expired, " .
802  "sending anyway due to $staleReason-->\n" );
803  }
804 
805  if ( !$renderStatus->isOK() ) {
806  $this->showViewError( $renderStatus->getWikiText(
807  false, 'view-pool-error', $context->getLanguage()
808  ) );
809  return;
810  }
811 
812  if ( $pOutput ) {
813  $outputPage->addParserOutput( $pOutput, $textOptions );
814  $outputPage->setSections( $pOutput->getSections() );
815  }
816 
817  if ( $this->getRevisionRedirectTarget( $rev ) ) {
818  $outputPage->addSubtitle( "<span id=\"redirectsub\">" .
819  $context->msg( 'redirectpagesub' )->parse() . "</span>" );
820  }
821  }
822 
827  private function getRevisionRedirectTarget( RevisionRecord $revision ) {
828  // TODO: find a *good* place for the code that determines the redirect target for
829  // a given revision!
830  // NOTE: Use main slot content. Compare code in DerivedPageDataUpdater::revisionIsRedirect.
831  $content = $revision->getContent( SlotRecord::MAIN );
832  return $content ? $content->getRedirectTarget() : null;
833  }
834 
839  public function adjustDisplayTitle( ParserOutput $pOutput ) {
840  $out = $this->getContext()->getOutput();
841 
842  # Adjust the title if it was set by displaytitle, -{T|}- or language conversion
843  $titleText = $pOutput->getTitleText();
844  if ( strval( $titleText ) !== '' ) {
845  $out->setPageTitle( $titleText );
846  $out->setDisplayTitle( $titleText );
847  }
848  }
849 
854  protected function showDiffPage() {
855  $context = $this->getContext();
856  $request = $context->getRequest();
857  $diff = $request->getVal( 'diff' );
858  $rcid = $request->getInt( 'rcid' );
859  $purge = $request->getRawVal( 'action' ) === 'purge';
860  $unhide = $request->getInt( 'unhide' ) == 1;
861  $oldid = $this->getOldID();
862 
863  $rev = $this->fetchRevisionRecord();
864 
865  if ( !$rev ) {
866  // T213621: $rev maybe null due to either lack of permission to view the
867  // revision or actually not existing. So let's try loading it from the id
868  $rev = $this->revisionStore->getRevisionById( $oldid );
869  if ( $rev ) {
870  // Revision exists but $user lacks permission to diff it.
871  // Do nothing here.
872  // The $rev will later be used to create standard diff elements however.
873  } else {
874  $context->getOutput()->setPageTitle( $context->msg( 'errorpagetitle' ) );
875  $msg = $context->msg( 'difference-missing-revision' )
876  ->params( $oldid )
877  ->numParams( 1 )
878  ->parseAsBlock();
879  $context->getOutput()->addHTML( $msg );
880  return;
881  }
882  }
883 
884  $contentHandler = MediaWikiServices::getInstance()
885  ->getContentHandlerFactory()
886  ->getContentHandler(
887  $rev->getSlot( SlotRecord::MAIN, RevisionRecord::RAW )->getModel()
888  );
889  $de = $contentHandler->createDifferenceEngine(
890  $context,
891  $oldid,
892  $diff,
893  $rcid,
894  $purge,
895  $unhide
896  );
897  $de->setSlotDiffOptions( [
898  'diff-type' => $request->getVal( 'diff-type' ),
899  'expand-url' => $this->viewIsRenderAction
900  ] );
901  $de->showDiffPage( $this->isDiffOnlyView() );
902 
903  // Run view updates for the newer revision being diffed (and shown
904  // below the diff if not diffOnly).
905  list( $old, $new ) = $de->mapDiffPrevNext( $oldid, $diff );
906  // New can be false, convert it to 0 - this conveniently means the latest revision
907  $this->mPage->doViewUpdates( $context->getAuthority(), (int)$new );
908  }
909 
910  protected function isDiffOnlyView() {
911  return $this->getContext()->getRequest()->getBool(
912  'diffonly',
913  $this->userOptionsLookup->getBoolOption( $this->getContext()->getUser(), 'diffonly' )
914  );
915  }
916 
924  public function getRobotPolicy( $action, ParserOutput $pOutput = null ) {
925  $context = $this->getContext();
926  $mainConfig = $context->getConfig();
927  $articleRobotPolicies = $mainConfig->get( MainConfigNames::ArticleRobotPolicies );
928  $namespaceRobotPolicies = $mainConfig->get( MainConfigNames::NamespaceRobotPolicies );
929  $defaultRobotPolicy = $mainConfig->get( MainConfigNames::DefaultRobotPolicy );
930  $title = $this->getTitle();
931  $ns = $title->getNamespace();
932 
933  # Don't index user and user talk pages for blocked users (T13443)
934  if ( ( $ns === NS_USER || $ns === NS_USER_TALK ) && !$title->isSubpage() ) {
935  $specificTarget = null;
936  $vagueTarget = null;
937  $titleText = $title->getText();
938  if ( IPUtils::isValid( $titleText ) ) {
939  $vagueTarget = $titleText;
940  } else {
941  $specificTarget = $titleText;
942  }
943  if ( DatabaseBlock::newFromTarget( $specificTarget, $vagueTarget ) instanceof DatabaseBlock ) {
944  return [
945  'index' => 'noindex',
946  'follow' => 'nofollow'
947  ];
948  }
949  }
950 
951  if ( $this->mPage->getId() === 0 || $this->getOldID() ) {
952  # Non-articles (special pages etc), and old revisions
953  return [
954  'index' => 'noindex',
955  'follow' => 'nofollow'
956  ];
957  } elseif ( $context->getOutput()->isPrintable() ) {
958  # Discourage indexing of printable versions, but encourage following
959  return [
960  'index' => 'noindex',
961  'follow' => 'follow'
962  ];
963  } elseif ( $context->getRequest()->getInt( 'curid' ) ) {
964  # For ?curid=x urls, disallow indexing
965  return [
966  'index' => 'noindex',
967  'follow' => 'follow'
968  ];
969  }
970 
971  # Otherwise, construct the policy based on the various config variables.
972  $policy = self::formatRobotPolicy( $defaultRobotPolicy );
973 
974  if ( isset( $namespaceRobotPolicies[$ns] ) ) {
975  # Honour customised robot policies for this namespace
976  $policy = array_merge(
977  $policy,
978  self::formatRobotPolicy( $namespaceRobotPolicies[$ns] )
979  );
980  }
981  if ( $title->canUseNoindex() && is_object( $pOutput ) && $pOutput->getIndexPolicy() ) {
982  # __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates
983  # a final check that we have really got the parser output.
984  $policy = array_merge(
985  $policy,
986  [ 'index' => $pOutput->getIndexPolicy() ]
987  );
988  }
989 
990  if ( isset( $articleRobotPolicies[$title->getPrefixedText()] ) ) {
991  # (T16900) site config can override user-defined __INDEX__ or __NOINDEX__
992  $policy = array_merge(
993  $policy,
994  self::formatRobotPolicy( $articleRobotPolicies[$title->getPrefixedText()] )
995  );
996  }
997 
998  return $policy;
999  }
1000 
1008  public static function formatRobotPolicy( $policy ) {
1009  if ( is_array( $policy ) ) {
1010  return $policy;
1011  } elseif ( !$policy ) {
1012  return [];
1013  }
1014 
1015  $arr = [];
1016  foreach ( explode( ',', $policy ) as $var ) {
1017  $var = trim( $var );
1018  if ( $var === 'index' || $var === 'noindex' ) {
1019  $arr['index'] = $var;
1020  } elseif ( $var === 'follow' || $var === 'nofollow' ) {
1021  $arr['follow'] = $var;
1022  }
1023  }
1024 
1025  return $arr;
1026  }
1027 
1035  public function showRedirectedFromHeader() {
1036  $context = $this->getContext();
1037  $redirectSources = $context->getConfig()->get( MainConfigNames::RedirectSources );
1038  $outputPage = $context->getOutput();
1039  $request = $context->getRequest();
1040  $rdfrom = $request->getVal( 'rdfrom' );
1041 
1042  // Construct a URL for the current page view, but with the target title
1043  $query = $request->getValues();
1044  unset( $query['rdfrom'] );
1045  unset( $query['title'] );
1046  if ( $this->getTitle()->isRedirect() ) {
1047  // Prevent double redirects
1048  $query['redirect'] = 'no';
1049  }
1050  $redirectTargetUrl = $this->getTitle()->getLinkURL( $query );
1051 
1052  if ( isset( $this->mRedirectedFrom ) ) {
1053  // This is an internally redirected page view.
1054  // We'll need a backlink to the source page for navigation.
1055  if ( $this->getHookRunner()->onArticleViewRedirect( $this ) ) {
1056  $redir = $this->linkRenderer->makeKnownLink(
1057  $this->mRedirectedFrom,
1058  null,
1059  [],
1060  [ 'redirect' => 'no' ]
1061  );
1062 
1063  $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
1064  $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
1065  . "</span>" );
1066 
1067  // Add the script to update the displayed URL and
1068  // set the fragment if one was specified in the redirect
1069  $outputPage->addJsConfigVars( [
1070  'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1071  ] );
1072  $outputPage->addModules( 'mediawiki.action.view.redirect' );
1073 
1074  // Add a <link rel="canonical"> tag
1075  $outputPage->setCanonicalUrl( $this->getTitle()->getCanonicalURL() );
1076 
1077  // Tell the output object that the user arrived at this article through a redirect
1078  $outputPage->setRedirectedFrom( $this->mRedirectedFrom );
1079 
1080  return true;
1081  }
1082  } elseif ( $rdfrom ) {
1083  // This is an externally redirected view, from some other wiki.
1084  // If it was reported from a trusted site, supply a backlink.
1085  if ( $redirectSources && preg_match( $redirectSources, $rdfrom ) ) {
1086  $redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
1087  $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
1088  $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
1089  . "</span>" );
1090 
1091  // Add the script to update the displayed URL
1092  $outputPage->addJsConfigVars( [
1093  'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1094  ] );
1095  $outputPage->addModules( 'mediawiki.action.view.redirect' );
1096 
1097  return true;
1098  }
1099  }
1100 
1101  return false;
1102  }
1103 
1108  public function showNamespaceHeader() {
1109  if ( $this->getTitle()->isTalkPage() && !$this->getContext()->msg( 'talkpageheader' )->isDisabled() ) {
1110  $this->getContext()->getOutput()->wrapWikiMsg(
1111  "<div class=\"mw-talkpageheader\">\n$1\n</div>",
1112  [ 'talkpageheader' ]
1113  );
1114  }
1115  }
1116 
1120  public function showViewFooter() {
1121  # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
1122  if ( $this->getTitle()->getNamespace() === NS_USER_TALK
1123  && IPUtils::isValid( $this->getTitle()->getText() )
1124  ) {
1125  $this->getContext()->getOutput()->addWikiMsg( 'anontalkpagetext' );
1126  }
1127 
1128  // Show a footer allowing the user to patrol the shown revision or page if possible
1129  $patrolFooterShown = $this->showPatrolFooter();
1130 
1131  $this->getHookRunner()->onArticleViewFooter( $this, $patrolFooterShown );
1132  }
1133 
1144  public function showPatrolFooter() {
1145  $context = $this->getContext();
1146  $mainConfig = $context->getConfig();
1147  $useNPPatrol = $mainConfig->get( MainConfigNames::UseNPPatrol );
1148  $useRCPatrol = $mainConfig->get( MainConfigNames::UseRCPatrol );
1149  $useFilePatrol = $mainConfig->get( MainConfigNames::UseFilePatrol );
1150  // Allow hooks to decide whether to not output this at all
1151  if ( !$this->getHookRunner()->onArticleShowPatrolFooter( $this ) ) {
1152  return false;
1153  }
1154 
1155  $outputPage = $context->getOutput();
1156  $user = $context->getUser();
1157  $title = $this->getTitle();
1158  $rc = false;
1159 
1160  if ( !$context->getAuthority()->probablyCan( 'patrol', $title )
1161  || !( $useRCPatrol || $useNPPatrol
1162  || ( $useFilePatrol && $title->inNamespace( NS_FILE ) ) )
1163  ) {
1164  // Patrolling is disabled or the user isn't allowed to
1165  return false;
1166  }
1167 
1168  if ( $this->mRevisionRecord
1169  && !RecentChange::isInRCLifespan( $this->mRevisionRecord->getTimestamp(), 21600 )
1170  ) {
1171  // The current revision is already older than what could be in the RC table
1172  // 6h tolerance because the RC might not be cleaned out regularly
1173  return false;
1174  }
1175 
1176  // Check for cached results
1177  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1178  $key = $cache->makeKey( 'unpatrollable-page', $title->getArticleID() );
1179  if ( $cache->get( $key ) ) {
1180  return false;
1181  }
1182 
1183  $dbr = wfGetDB( DB_REPLICA );
1184  $oldestRevisionTimestamp = $dbr->selectField(
1185  'revision',
1186  'MIN( rev_timestamp )',
1187  [ 'rev_page' => $title->getArticleID() ],
1188  __METHOD__
1189  );
1190 
1191  // New page patrol: Get the timestamp of the oldest revision which
1192  // the revision table holds for the given page. Then we look
1193  // whether it's within the RC lifespan and if it is, we try
1194  // to get the recentchanges row belonging to that entry
1195  // (with rc_new = 1).
1196  $recentPageCreation = false;
1197  if ( $oldestRevisionTimestamp
1198  && RecentChange::isInRCLifespan( $oldestRevisionTimestamp, 21600 )
1199  ) {
1200  // 6h tolerance because the RC might not be cleaned out regularly
1201  $recentPageCreation = true;
1203  [
1204  'rc_new' => 1,
1205  'rc_timestamp' => $oldestRevisionTimestamp,
1206  'rc_namespace' => $title->getNamespace(),
1207  'rc_cur_id' => $title->getArticleID()
1208  ],
1209  __METHOD__
1210  );
1211  if ( $rc ) {
1212  // Use generic patrol message for new pages
1213  $markPatrolledMsg = $context->msg( 'markaspatrolledtext' );
1214  }
1215  }
1216 
1217  // File patrol: Get the timestamp of the latest upload for this page,
1218  // check whether it is within the RC lifespan and if it is, we try
1219  // to get the recentchanges row belonging to that entry
1220  // (with rc_type = RC_LOG, rc_log_type = upload).
1221  $recentFileUpload = false;
1222  if ( ( !$rc || $rc->getAttribute( 'rc_patrolled' ) ) && $useFilePatrol
1223  && $title->getNamespace() === NS_FILE ) {
1224  // Retrieve timestamp of most recent upload
1225  $newestUploadTimestamp = $dbr->selectField(
1226  'image',
1227  'MAX( img_timestamp )',
1228  [ 'img_name' => $title->getDBkey() ],
1229  __METHOD__
1230  );
1231  if ( $newestUploadTimestamp
1232  && RecentChange::isInRCLifespan( $newestUploadTimestamp, 21600 )
1233  ) {
1234  // 6h tolerance because the RC might not be cleaned out regularly
1235  $recentFileUpload = true;
1237  [
1238  'rc_type' => RC_LOG,
1239  'rc_log_type' => 'upload',
1240  'rc_timestamp' => $newestUploadTimestamp,
1241  'rc_namespace' => NS_FILE,
1242  'rc_cur_id' => $title->getArticleID()
1243  ],
1244  __METHOD__
1245  );
1246  if ( $rc ) {
1247  // Use patrol message specific to files
1248  $markPatrolledMsg = $context->msg( 'markaspatrolledtext-file' );
1249  }
1250  }
1251  }
1252 
1253  if ( !$recentPageCreation && !$recentFileUpload ) {
1254  // Page creation and latest upload (for files) is too old to be in RC
1255 
1256  // We definitely can't patrol so cache the information
1257  // When a new file version is uploaded, the cache is cleared
1258  $cache->set( $key, '1' );
1259 
1260  return false;
1261  }
1262 
1263  if ( !$rc ) {
1264  // Don't cache: This can be hit if the page gets accessed very fast after
1265  // its creation / latest upload or in case we have high replica DB lag. In case
1266  // the revision is too old, we will already return above.
1267  return false;
1268  }
1269 
1270  if ( $rc->getAttribute( 'rc_patrolled' ) ) {
1271  // Patrolled RC entry around
1272 
1273  // Cache the information we gathered above in case we can't patrol
1274  // Don't cache in case we can patrol as this could change
1275  $cache->set( $key, '1' );
1276 
1277  return false;
1278  }
1279 
1280  if ( $rc->getPerformerIdentity()->equals( $user ) ) {
1281  // Don't show a patrol link for own creations/uploads. If the user could
1282  // patrol them, they already would be patrolled
1283  return false;
1284  }
1285 
1286  $outputPage->setPreventClickjacking( true );
1287  if ( $context->getAuthority()->isAllowed( 'writeapi' ) ) {
1288  $outputPage->addModules( 'mediawiki.misc-authed-curate' );
1289  }
1290 
1291  $link = $this->linkRenderer->makeKnownLink(
1292  $title,
1293  // @phan-suppress-next-line PhanPossiblyUndeclaredVariable $markPatrolledMsg is always set when $rc is set
1294  $markPatrolledMsg->text(),
1295  [],
1296  [
1297  'action' => 'markpatrolled',
1298  'rcid' => $rc->getAttribute( 'rc_id' ),
1299  ]
1300  );
1301 
1302  $outputPage->addModuleStyles( 'mediawiki.action.styles' );
1303  $outputPage->addHTML(
1304  "<div class='patrollink' data-mw='interface'>" .
1305  $context->msg( 'markaspatrolledlink' )->rawParams( $link )->escaped() .
1306  '</div>'
1307  );
1308 
1309  return true;
1310  }
1311 
1318  public static function purgePatrolFooterCache( $articleID ) {
1319  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1320  $cache->delete( $cache->makeKey( 'unpatrollable-page', $articleID ) );
1321  }
1322 
1327  public function showMissingArticle() {
1328  $context = $this->getContext();
1329  $send404Code = $context->getConfig()->get( MainConfigNames::Send404Code );
1330 
1331  $outputPage = $context->getOutput();
1332  // Whether the page is a root user page of an existing user (but not a subpage)
1333  $validUserPage = false;
1334 
1335  $title = $this->getTitle();
1336 
1337  $services = MediaWikiServices::getInstance();
1338 
1339  $contextUser = $context->getUser();
1340 
1341  # Show info in user (talk) namespace. Does the user exist? Is he blocked?
1342  if ( $title->getNamespace() === NS_USER
1343  || $title->getNamespace() === NS_USER_TALK
1344  ) {
1345  $rootPart = explode( '/', $title->getText() )[0];
1346  $user = User::newFromName( $rootPart, false /* allow IP users */ );
1347  $ip = $this->userNameUtils->isIP( $rootPart );
1348  $block = DatabaseBlock::newFromTarget( $user, $user );
1349 
1350  if ( $user && $user->isRegistered() && $user->isHidden() &&
1351  !$context->getAuthority()->isAllowed( 'hideuser' )
1352  ) {
1353  // T120883 if the user is hidden and the viewer cannot see hidden
1354  // users, pretend like it does not exist at all.
1355  $user = false;
1356  }
1357 
1358  if ( !( $user && $user->isRegistered() ) && !$ip ) { # User does not exist
1359  $outputPage->addHtml( Html::warningBox(
1360  $context->msg( 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) )->parse(),
1361  'mw-userpage-userdoesnotexist'
1362  ) );
1363  } elseif (
1364  $block !== null &&
1365  $block->getType() != DatabaseBlock::TYPE_AUTO &&
1366  (
1367  $block->isSitewide() ||
1368  $user->isBlockedFrom( $title, true )
1369  )
1370  ) {
1371  // Show log extract if the user is sitewide blocked or is partially
1372  // blocked and not allowed to edit their user page or user talk page
1374  $outputPage,
1375  'block',
1376  $services->getNamespaceInfo()->getCanonicalName( NS_USER ) . ':' .
1377  $block->getTargetName(),
1378  '',
1379  [
1380  'lim' => 1,
1381  'showIfEmpty' => false,
1382  'msgKey' => [
1383  'blocked-notice-logextract',
1384  $user->getName() # Support GENDER in notice
1385  ]
1386  ]
1387  );
1388  $validUserPage = !$title->isSubpage();
1389  } else {
1390  $validUserPage = !$title->isSubpage();
1391  }
1392  }
1393 
1394  $this->getHookRunner()->onShowMissingArticle( $this );
1395 
1396  # Show delete and move logs if there were any such events.
1397  # The logging query can DOS the site when bots/crawlers cause 404 floods,
1398  # so be careful showing this. 404 pages must be cheap as they are hard to cache.
1399  $dbCache = MediaWikiServices::getInstance()->getMainObjectStash();
1400  $key = $dbCache->makeKey( 'page-recent-delete', md5( $title->getPrefixedText() ) );
1401  $isRegistered = $contextUser->isRegistered();
1402  $sessionExists = $context->getRequest()->getSession()->isPersistent();
1403 
1404  if ( $isRegistered || $dbCache->get( $key ) || $sessionExists ) {
1405  $logTypes = [ 'delete', 'move', 'protect' ];
1406 
1407  $dbr = wfGetDB( DB_REPLICA );
1408 
1409  $conds = [ 'log_action != ' . $dbr->addQuotes( 'revision' ) ];
1410  // Give extensions a chance to hide their (unrelated) log entries
1411  $this->getHookRunner()->onArticle__MissingArticleConditions( $conds, $logTypes );
1413  $outputPage,
1414  $logTypes,
1415  $title,
1416  '',
1417  [
1418  'lim' => 10,
1419  'conds' => $conds,
1420  'showIfEmpty' => false,
1421  'msgKey' => [ $isRegistered || $sessionExists
1422  ? 'moveddeleted-notice'
1423  : 'moveddeleted-notice-recent'
1424  ]
1425  ]
1426  );
1427  }
1428 
1429  if ( !$this->mPage->hasViewableContent() && $send404Code && !$validUserPage ) {
1430  // If there's no backing content, send a 404 Not Found
1431  // for better machine handling of broken links.
1432  $context->getRequest()->response()->statusHeader( 404 );
1433  }
1434 
1435  // Also apply the robot policy for nonexisting pages (even if a 404 was used)
1436  $policy = $this->getRobotPolicy( 'view' );
1437  $outputPage->setIndexPolicy( $policy['index'] );
1438  $outputPage->setFollowPolicy( $policy['follow'] );
1439 
1440  $hookResult = $this->getHookRunner()->onBeforeDisplayNoArticleText( $this );
1441 
1442  if ( !$hookResult ) {
1443  return;
1444  }
1445 
1446  # Show error message
1447  $oldid = $this->getOldID();
1448  if ( !$oldid && $title->getNamespace() === NS_MEDIAWIKI && $title->hasSourceText() ) {
1449  $text = $this->getTitle()->getDefaultMessageText() ?? '';
1450  $outputPage->addWikiTextAsContent( $text );
1451  } else {
1452  if ( $oldid ) {
1453  // T251066: Try loading the revision from the archive table.
1454  // Show link to view it if it exists and the user has permission to view it.
1455  $pa = new PageArchive( $title );
1456  $revRecord = $pa->getArchivedRevisionRecord( $oldid );
1457  if ( $revRecord && $revRecord->userCan(
1458  RevisionRecord::DELETED_TEXT,
1459  $context->getAuthority()
1460  ) ) {
1461  $text = $context->msg(
1462  'missing-revision-permission', $oldid,
1463  $revRecord->getTimestamp(),
1464  $title->getPrefixedDBkey()
1465  )->plain();
1466  } else {
1467  $text = $context->msg( 'missing-revision', $oldid )->plain();
1468  }
1469 
1470  } elseif ( $context->getAuthority()->probablyCan( 'edit', $title ) ) {
1471  $message = $isRegistered ? 'noarticletext' : 'noarticletextanon';
1472  $text = $context->msg( $message )->plain();
1473  } else {
1474  $text = $context->msg( 'noarticletext-nopermission' )->plain();
1475  }
1476 
1477  $dir = $context->getLanguage()->getDir();
1478  $lang = $context->getLanguage()->getHtmlCode();
1479  $outputPage->addWikiTextAsInterface( Xml::openElement( 'div', [
1480  'class' => "noarticletext mw-content-$dir",
1481  'dir' => $dir,
1482  'lang' => $lang,
1483  ] ) . "\n$text\n</div>" );
1484  }
1485  }
1486 
1491  private function showViewError( string $errortext ) {
1492  $outputPage = $this->getContext()->getOutput();
1493  $outputPage->setPageTitle( $this->getContext()->msg( 'errorpagetitle' ) );
1494  $outputPage->disableClientCache();
1495  $outputPage->setRobotPolicy( 'noindex,nofollow' );
1496  $outputPage->clearHTML();
1497  $outputPage->addHTML( Html::errorBox( $outputPage->parseAsContent( $errortext ) ) );
1498  }
1499 
1506  public function showDeletedRevisionHeader() {
1507  if ( !$this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1508  // Not deleted
1509  return true;
1510  }
1511  $outputPage = $this->getContext()->getOutput();
1512  // Used in wikilinks, should not contain whitespaces
1513  $titleText = $this->getTitle()->getPrefixedDBkey();
1514  // If the user is not allowed to see it...
1515  if ( !$this->mRevisionRecord->userCan(
1516  RevisionRecord::DELETED_TEXT,
1517  $this->getContext()->getAuthority()
1518  ) ) {
1519  $outputPage->addHtml(
1521  $outputPage->msg( 'rev-deleted-text-permission', $titleText )->parse(),
1522  'plainlinks'
1523  )
1524  );
1525 
1526  return false;
1527  // If the user needs to confirm that they want to see it...
1528  } elseif ( $this->getContext()->getRequest()->getInt( 'unhide' ) != 1 ) {
1529  # Give explanation and add a link to view the revision...
1530  $oldid = intval( $this->getOldID() );
1531  $link = $this->getTitle()->getFullURL( "oldid={$oldid}&unhide=1" );
1532  $msg = $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ?
1533  'rev-suppressed-text-unhide' : 'rev-deleted-text-unhide';
1534  $outputPage->addHtml(
1536  $outputPage->msg( $msg, $link )->parse(),
1537  'plainlinks'
1538  )
1539  );
1540 
1541  return false;
1542  // We are allowed to see...
1543  } else {
1544  $msg = $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED )
1545  ? [ 'rev-suppressed-text-view', $titleText ]
1546  : [ 'rev-deleted-text-view', $titleText ];
1547  $outputPage->addHtml(
1549  $outputPage->msg( $msg[0], $msg[1] )->parse(),
1550  'plainlinks'
1551  )
1552  );
1553 
1554  return true;
1555  }
1556  }
1557 
1566  public function setOldSubtitle( $oldid = 0 ) {
1567  if ( !$this->getHookRunner()->onDisplayOldSubtitle( $this, $oldid ) ) {
1568  return;
1569  }
1570 
1571  $context = $this->getContext();
1572  $unhide = $context->getRequest()->getInt( 'unhide' ) == 1;
1573 
1574  # Cascade unhide param in links for easy deletion browsing
1575  $extraParams = [];
1576  if ( $unhide ) {
1577  $extraParams['unhide'] = 1;
1578  }
1579 
1580  if ( $this->mRevisionRecord && $this->mRevisionRecord->getId() === $oldid ) {
1581  $revisionRecord = $this->mRevisionRecord;
1582  } else {
1583  $revisionRecord = $this->revisionStore->getRevisionById( $oldid );
1584  }
1585 
1586  $timestamp = $revisionRecord->getTimestamp();
1587 
1588  $current = ( $oldid == $this->mPage->getLatest() );
1589  $language = $context->getLanguage();
1590  $user = $context->getUser();
1591 
1592  $td = $language->userTimeAndDate( $timestamp, $user );
1593  $tddate = $language->userDate( $timestamp, $user );
1594  $tdtime = $language->userTime( $timestamp, $user );
1595 
1596  # Show user links if allowed to see them. If hidden, then show them only if requested...
1597  // @phan-suppress-next-line PhanTypeMismatchArgumentNullable revisionRecord known to exists
1598  $userlinks = Linker::revUserTools( $revisionRecord, !$unhide );
1599 
1600  $infomsg = $current && !$context->msg( 'revision-info-current' )->isDisabled()
1601  ? 'revision-info-current'
1602  : 'revision-info';
1603 
1604  $outputPage = $context->getOutput();
1605  $outputPage->addModuleStyles( [
1606  'mediawiki.action.styles',
1607  'mediawiki.interface.helpers.styles'
1608  ] );
1609 
1610  $revisionUser = $revisionRecord->getUser();
1611  $revisionInfo = "<div id=\"mw-{$infomsg}\">" .
1612  $context->msg( $infomsg, $td )
1613  ->rawParams( $userlinks )
1614  ->params(
1615  $revisionRecord->getId(),
1616  $tddate,
1617  $tdtime,
1618  $revisionUser ? $revisionUser->getName() : ''
1619  )
1620  ->rawParams( Linker::revComment(
1621  // @phan-suppress-next-line PhanTypeMismatchArgumentNullable revisionRecord known to exists
1622  $revisionRecord,
1623  true,
1624  true
1625  ) )
1626  ->parse() .
1627  "</div>";
1628 
1629  $lnk = $current
1630  ? $context->msg( 'currentrevisionlink' )->escaped()
1631  : $this->linkRenderer->makeKnownLink(
1632  $this->getTitle(),
1633  $context->msg( 'currentrevisionlink' )->text(),
1634  [],
1635  $extraParams
1636  );
1637  $curdiff = $current
1638  ? $context->msg( 'diff' )->escaped()
1639  : $this->linkRenderer->makeKnownLink(
1640  $this->getTitle(),
1641  $context->msg( 'diff' )->text(),
1642  [],
1643  [
1644  'diff' => 'cur',
1645  'oldid' => $oldid
1646  ] + $extraParams
1647  );
1648  // @phan-suppress-next-line PhanTypeMismatchArgumentNullable revisionRecord known to exists
1649  $prevExist = (bool)$this->revisionStore->getPreviousRevision( $revisionRecord );
1650  $prevlink = $prevExist
1651  ? $this->linkRenderer->makeKnownLink(
1652  $this->getTitle(),
1653  $context->msg( 'previousrevision' )->text(),
1654  [],
1655  [
1656  'direction' => 'prev',
1657  'oldid' => $oldid
1658  ] + $extraParams
1659  )
1660  : $context->msg( 'previousrevision' )->escaped();
1661  $prevdiff = $prevExist
1662  ? $this->linkRenderer->makeKnownLink(
1663  $this->getTitle(),
1664  $context->msg( 'diff' )->text(),
1665  [],
1666  [
1667  'diff' => 'prev',
1668  'oldid' => $oldid
1669  ] + $extraParams
1670  )
1671  : $context->msg( 'diff' )->escaped();
1672  $nextlink = $current
1673  ? $context->msg( 'nextrevision' )->escaped()
1674  : $this->linkRenderer->makeKnownLink(
1675  $this->getTitle(),
1676  $context->msg( 'nextrevision' )->text(),
1677  [],
1678  [
1679  'direction' => 'next',
1680  'oldid' => $oldid
1681  ] + $extraParams
1682  );
1683  $nextdiff = $current
1684  ? $context->msg( 'diff' )->escaped()
1685  : $this->linkRenderer->makeKnownLink(
1686  $this->getTitle(),
1687  $context->msg( 'diff' )->text(),
1688  [],
1689  [
1690  'diff' => 'next',
1691  'oldid' => $oldid
1692  ] + $extraParams
1693  );
1694 
1695  $cdel = Linker::getRevDeleteLink(
1696  $context->getAuthority(),
1697  // @phan-suppress-next-line PhanTypeMismatchArgumentNullable revisionRecord known to exists
1698  $revisionRecord,
1699  $this->getTitle()
1700  );
1701  if ( $cdel !== '' ) {
1702  $cdel .= ' ';
1703  }
1704 
1705  // the outer div is need for styling the revision info and nav in MobileFrontend
1706  $outputPage->addSubtitle(
1708  $revisionInfo .
1709  "<div id=\"mw-revision-nav\">" . $cdel .
1710  $context->msg( 'revision-nav' )->rawParams(
1711  $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff
1712  )->escaped() . "</div>",
1713  'mw-revision'
1714  )
1715  );
1716  }
1717 
1731  public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) {
1732  wfDeprecated( __METHOD__, '1.30' );
1733  $lang = $this->getTitle()->getPageLanguage();
1734  $out = $this->getContext()->getOutput();
1735  if ( $appendSubtitle ) {
1736  $out->addSubtitle( $this->getContext()->msg( 'redirectpagesub' ) );
1737  }
1738  $out->addModuleStyles( 'mediawiki.action.view.redirectPage' );
1739  return static::getRedirectHeaderHtml( $lang, $target, $forceKnown );
1740  }
1741 
1754  public static function getRedirectHeaderHtml( Language $lang, $target, $forceKnown = false ) {
1755  if ( is_array( $target ) ) {
1756  // Up until 1.39, $target was allowed to be an array.
1757  wfDeprecatedMsg( 'The $target parameter can no longer be an array', '1.39' );
1758  $target = reset( $target ); // There really can only be one element (T296430)
1759  }
1760 
1761  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1762 
1763  $html = '<ul class="redirectText">';
1764  if ( $forceKnown ) {
1765  $link = $linkRenderer->makeKnownLink(
1766  $target,
1767  $target->getFullText(),
1768  [],
1769  // Make sure wiki page redirects are not followed
1770  $target->isRedirect() ? [ 'redirect' => 'no' ] : []
1771  );
1772  } else {
1773  $link = $linkRenderer->makeLink(
1774  $target,
1775  $target->getFullText(),
1776  [],
1777  // Make sure wiki page redirects are not followed
1778  $target->isRedirect() ? [ 'redirect' => 'no' ] : []
1779  );
1780  }
1781  $html .= '<li>' . $link . '</li>';
1782  $html .= '</ul>';
1783 
1784  $redirectToText = wfMessage( 'redirectto' )->inLanguage( $lang )->escaped();
1785 
1786  return '<div class="redirectMsg">' .
1787  '<p>' . $redirectToText . '</p>' .
1788  $html .
1789  '</div>';
1790  }
1791 
1800  public function addHelpLink( $to, $overrideBaseUrl = false ) {
1801  $out = $this->getContext()->getOutput();
1802  $msg = $out->msg( 'namespace-' . $this->getTitle()->getNamespace() . '-helppage' );
1803 
1804  if ( !$msg->isDisabled() ) {
1805  $title = Title::newFromText( $msg->plain() );
1806  if ( $title instanceof Title ) {
1807  $out->addHelpLink( $title->getLocalURL(), true );
1808  }
1809  } else {
1810  $out->addHelpLink( $to, $overrideBaseUrl );
1811  }
1812  }
1813 
1817  public function render() {
1818  $this->getContext()->getRequest()->response()->header( 'X-Robots-Tag: noindex' );
1819  $this->getContext()->getOutput()->setArticleBodyOnly( true );
1820  // We later set 'enableSectionEditLinks=false' based on this; also used by ImagePage
1821  $this->viewIsRenderAction = true;
1822  $this->view();
1823  }
1824 
1828  public function protect() {
1829  $form = new ProtectionForm( $this );
1830  $form->execute();
1831  }
1832 
1836  public function unprotect() {
1837  $this->protect();
1838  }
1839 
1853  public function doDelete( $reason, $suppress = false, $immediate = false ) {
1854  wfDeprecated( __METHOD__, '1.37' );
1855  $error = '';
1856  $context = $this->getContext();
1857  $outputPage = $context->getOutput();
1858  $user = $context->getUser();
1859  $status = $this->mPage->doDeleteArticleReal(
1860  $reason, $user, $suppress, null, $error,
1861  null, [], 'delete', $immediate
1862  );
1863 
1864  if ( $status->isOK() ) {
1865  $deleted = $this->getTitle()->getPrefixedText();
1866 
1867  $outputPage->setPageTitle( $context->msg( 'actioncomplete' ) );
1868  $outputPage->setRobotPolicy( 'noindex,nofollow' );
1869 
1870  if ( $status->isGood() ) {
1871  $loglink = '[[Special:Log/delete|' . $context->msg( 'deletionlog' )->text() . ']]';
1872  $outputPage->addWikiMsg( 'deletedtext', wfEscapeWikiText( $deleted ), $loglink );
1873  $this->getHookRunner()->onArticleDeleteAfterSuccess( $this->getTitle(), $outputPage );
1874  } else {
1875  $outputPage->addWikiMsg( 'delete-scheduled', wfEscapeWikiText( $deleted ) );
1876  }
1877 
1878  $outputPage->returnToMain( false );
1879  } else {
1880  $outputPage->setPageTitle(
1881  $context->msg( 'cannotdelete-title',
1882  $this->getTitle()->getPrefixedText() )
1883  );
1884 
1885  if ( $error == '' ) {
1886  $outputPage->wrapWikiTextAsInterface(
1887  'error mw-error-cannotdelete',
1888  $status->getWikiText( false, false, $context->getLanguage() )
1889  );
1890  $deleteLogPage = new LogPage( 'delete' );
1891  $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
1892 
1894  $outputPage,
1895  'delete',
1896  $this->getTitle()
1897  );
1898  } else {
1899  $outputPage->addHTML( $error );
1900  }
1901  }
1902  }
1903 
1904  /* Caching functions */
1905 
1913  protected function tryFileCache() {
1914  static $called = false;
1915 
1916  if ( $called ) {
1917  wfDebug( "Article::tryFileCache(): called twice!?" );
1918  return false;
1919  }
1920 
1921  $called = true;
1922  if ( $this->isFileCacheable() ) {
1923  $cache = new HTMLFileCache( $this->getTitle(), 'view' );
1924  if ( $cache->isCacheGood( $this->mPage->getTouched() ) ) {
1925  wfDebug( "Article::tryFileCache(): about to load file" );
1926  $cache->loadFromFileCache( $this->getContext() );
1927  return true;
1928  } else {
1929  wfDebug( "Article::tryFileCache(): starting buffer" );
1930  ob_start( [ &$cache, 'saveToFileCache' ] );
1931  }
1932  } else {
1933  wfDebug( "Article::tryFileCache(): not cacheable" );
1934  }
1935 
1936  return false;
1937  }
1938 
1944  public function isFileCacheable( $mode = HTMLFileCache::MODE_NORMAL ) {
1945  $cacheable = false;
1946 
1947  if ( HTMLFileCache::useFileCache( $this->getContext(), $mode ) ) {
1948  $cacheable = $this->mPage->getId()
1949  && !$this->mRedirectedFrom && !$this->getTitle()->isRedirect();
1950  // Extension may have reason to disable file caching on some pages.
1951  if ( $cacheable ) {
1952  $cacheable = $this->getHookRunner()->onIsFileCacheable( $this ) ?? false;
1953  }
1954  }
1955 
1956  return $cacheable;
1957  }
1958 
1972  public function getParserOutput( $oldid = null, UserIdentity $user = null ) {
1973  if ( $user === null ) {
1974  $parserOptions = $this->getParserOptions();
1975  } else {
1976  $parserOptions = $this->mPage->makeParserOptions( $user );
1977  }
1978 
1979  return $this->mPage->getParserOutput( $parserOptions, $oldid );
1980  }
1981 
1986  public function getParserOptions() {
1987  return $this->mPage->makeParserOptions( $this->getContext() );
1988  }
1989 
1996  public function setContext( $context ) {
1997  $this->mContext = $context;
1998  }
1999 
2006  public function getContext() {
2007  if ( $this->mContext instanceof IContextSource ) {
2008  return $this->mContext;
2009  } else {
2010  wfDebug( __METHOD__ . " called and \$mContext is null. " .
2011  "Return RequestContext::getMain()" );
2012  return RequestContext::getMain();
2013  }
2014  }
2015 
2025  public function __get( $fname ) {
2026  wfDeprecatedMsg( "Accessing Article::\$$fname is deprecated since MediaWiki 1.35",
2027  '1.35' );
2028 
2029  if ( property_exists( $this->mPage, $fname ) ) {
2030  return $this->mPage->$fname;
2031  }
2032  trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
2033  }
2034 
2044  public function __set( $fname, $fvalue ) {
2045  wfDeprecatedMsg( "Setting Article::\$$fname is deprecated since MediaWiki 1.35",
2046  '1.35' );
2047 
2048  if ( property_exists( $this->mPage, $fname ) ) {
2049  $this->mPage->$fname = $fvalue;
2050  // Note: extensions may want to toss on new fields
2051  } elseif ( !in_array( $fname, [ 'mContext', 'mPage' ] ) ) {
2052  $this->mPage->$fname = $fvalue;
2053  } else {
2054  trigger_error( 'Inaccessible property via __set(): ' . $fname, E_USER_NOTICE );
2055  }
2056  }
2057 
2063  public function getActionOverrides() {
2064  return $this->mPage->getActionOverrides();
2065  }
2066 
2072  public function getTimestamp() {
2073  wfDeprecated( __METHOD__, '1.35' );
2074  return $this->mPage->getTimestamp();
2075  }
2076 }
const NS_USER
Definition: Defines.php:66
const NS_FILE
Definition: Defines.php:70
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()
Legacy class representing an editable page and handling UI for some page actions.
Definition: Article.php:48
static newFromWikiPage(WikiPage $page, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition: Article.php:201
getContext()
Gets the context this Article is executed in.
Definition: Article.php:2006
getOldIDFromRequest()
Sets $this->mRedirectUrl to a correct URL if the query parameters are incorrect.
Definition: Article.php:275
getRedirectedFrom()
Get the page this view was redirected from.
Definition: Article.php:212
Title null $mRedirectedFrom
Title from which we were redirected here, if any.
Definition: Article.php:69
bool $viewIsRenderAction
Whether render() was called.
Definition: Article.php:92
view()
This is the default action of the index.php entry point: just view the page of the given title.
Definition: Article.php:433
__construct(Title $title, $oldId=null)
Definition: Article.php:131
getRobotPolicy( $action, ParserOutput $pOutput=null)
Get the robot policy to be used for the current view.
Definition: Article.php:924
static purgePatrolFooterCache( $articleID)
Purge the cache used to check if it is worth showing the patrol footer For example,...
Definition: Article.php:1318
doDelete( $reason, $suppress=false, $immediate=false)
Perform a deletion and output success or failure messages.
Definition: Article.php:1853
ParserOutput null false $mParserOutput
The ParserOutput generated for viewing the page, initialized by view().
Definition: Article.php:85
getOldID()
Definition: Article.php:262
LinkRenderer $linkRenderer
Definition: Article.php:97
getTitle()
Get the title object of the article.
Definition: Article.php:230
getActionOverrides()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2063
isDiffOnlyView()
Definition: Article.php:910
adjustDisplayTitle(ParserOutput $pOutput)
Adjust title for pages with displaytitle, -{T|}- or language conversion.
Definition: Article.php:839
showDeletedRevisionHeader()
If the revision requested for view is deleted, check permissions.
Definition: Article.php:1506
getParserOptions()
Get parser options suitable for rendering the primary article wikitext.
Definition: Article.php:1986
clear()
Definition: Article.php:244
IContextSource null $mContext
The context this Article is executed in.
Definition: Article.php:57
getParserOutput( $oldid=null, UserIdentity $user=null)
#-
Definition: Article.php:1972
static getRedirectHeaderHtml(Language $lang, $target, $forceKnown=false)
Return the HTML for the top of a redirect page.
Definition: Article.php:1754
protect()
action=protect handler
Definition: Article.php:1828
isCurrent()
Returns true if the currently-referenced revision is the current edit to this page (and it exists).
Definition: Article.php:402
showMissingArticle()
Show the error text for a missing article.
Definition: Article.php:1327
__set( $fname, $fvalue)
Definition: Article.php:2044
unprotect()
action=unprotect handler (alias)
Definition: Article.php:1836
newPage(Title $title)
Definition: Article.php:147
getPage()
Get the WikiPage object of this instance.
Definition: Article.php:240
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Definition: Article.php:1800
string bool $mRedirectUrl
URL to redirect to or false if none.
Definition: Article.php:72
getTimestamp()
Definition: Article.php:2072
static newFromID( $id)
Constructor from a page id.
Definition: Article.php:156
int null $mOldId
The oldid of the article that was requested to be shown, 0 for the current revision.
Definition: Article.php:66
static formatRobotPolicy( $policy)
Converts a String robot policy into an associative array, to allow merging of several policies using ...
Definition: Article.php:1008
fetchRevisionRecord()
Fetches the revision to work on.
Definition: Article.php:344
viewRedirect( $target, $appendSubtitle=true, $forceKnown=false)
Return the HTML for the top of a redirect page.
Definition: Article.php:1731
showPatrolFooter()
If patrol is possible, output a patrol UI box.
Definition: Article.php:1144
setOldSubtitle( $oldid=0)
Generate the navigation links when browsing through an article revisions It shows the information as:...
Definition: Article.php:1566
showViewFooter()
Show the footer section of an ordinary page view.
Definition: Article.php:1120
WikiPage $mPage
The WikiPage object of this instance.
Definition: Article.php:60
setRedirectedFrom(Title $from)
Tell the page view functions that this view was redirected from another page on the wiki.
Definition: Article.php:221
isFileCacheable( $mode=HTMLFileCache::MODE_NORMAL)
Check if the page can be cached.
Definition: Article.php:1944
tryFileCache()
checkLastModified returns true if it has taken care of all output to the client that is necessary for...
Definition: Article.php:1913
getRevIdFetched()
Use this to fetch the rev ID used on page views.
Definition: Article.php:421
showNamespaceHeader()
Show a header specific to the namespace currently being viewed, like [[MediaWiki:Talkpagetext]].
Definition: Article.php:1108
__get( $fname)
Definition: Article.php:2025
static newFromTitle( $title, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition: Article.php:168
showDiffPage()
Show a diff page according to current request variables.
Definition: Article.php:854
render()
Handle action=render.
Definition: Article.php:1817
showRedirectedFromHeader()
If this request is a redirect view, send "redirected from" subtitle to the output.
Definition: Article.php:1035
setContext( $context)
Sets the context this Article is executed in.
Definition: Article.php:1996
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()
const POST_EDIT_COOKIE_KEY_PREFIX
Prefix of key for cookie used to pass post-edit state.
Definition: EditPage.php:120
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.
static runner()
Get a HookRunner instance for calling hooks using the new interfaces.
Definition: Hooks.php:173
static warningBox( $html, $className='')
Return a warning box.
Definition: Html.php:775
static errorBox( $html, $heading='', $className='')
Return an error box.
Definition: Html.php:788
Rendering of file description pages.
Definition: ImagePage.php:31
Base class for language-specific code.
Definition: Language.php:53
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:1563
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:2109
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:1327
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition: Linker.php:1018
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
Class to simplify the use of log pages.
Definition: LogPage.php:39
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
Class that generates HTML anchor link elements for pages.
makeKnownLink( $target, $text=null, array $extraAttribs=[], array $query=[])
makeLink( $target, $text=null, array $extraAttribs=[], array $query=[])
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.
A StatusValue for permission errors.
Page revision base class.
isCurrent()
Checks whether the revision record is a stored current revision.
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
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:55
disable()
Disable output completely, i.e.
addWikiMsg(... $args)
Add a wikitext-formatted message to the output.
wrapWikiTextAsInterface( $wrapperClass, $text)
Convert wikitext in the user interface language to HTML and add it to the buffer with a <div class="$...
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:924
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:416
setLastModified( $timestamp)
Override the last modified timestamp.
Definition: OutputPage.php:913
setSections(array $sections)
Adds sections to OutputPage from ParserOutput.
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:639
setHTMLTitle( $name)
"HTML title" means the contents of "<title>".
returnToMain( $unused=null, $returnto=null, $returntoquery=null)
Add a "return to" link pointing to a specified title, or the title indicated in the request,...
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:499
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:613
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:31
Set options of the Parser.
setIsPrintable( $x)
Parsing the printable version of the page?
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:6511
Show an error when a user tries to do something they do not have the necessary permissions for.
Handles the page protection UI and backend.
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:44
getWikiText( $shortContext=false, $longContext=false, $lang=null)
Get the error list as a wikitext formatted list.
Definition: Status.php:189
Represents a title within MediaWiki.
Definition: Title.php:49
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:518
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:370
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:638
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:70
static newFromName( $name, $validate='valid')
Definition: User.php:598
Base representation for an editable wiki page.
Definition: WikiPage.php:62
getTitle()
Get the title object of the article.
Definition: WikiPage.php:303
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:112
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:43
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:29
$cache
Definition: mcc.php:33
const DB_REPLICA
Definition: defines.php:26
$content
Definition: router.php:76
return true
Definition: router.php:90
if(!isset( $args[0])) $lang