MediaWiki  master
Article.php
Go to the documentation of this file.
1 <?php
25 use MediaWiki\HookContainer\ProtectedHookAccessorTrait;
33 use Wikimedia\IPUtils;
34 use Wikimedia\NonSerializable\NonSerializableTrait;
36 
47 class Article implements Page {
48  use ProtectedHookAccessorTrait;
49  use NonSerializableTrait;
50 
56  protected $mContext;
57 
59  protected $mPage;
60 
66 
76 
83  public $mContentLoaded = false;
84 
90  public $mOldId;
91 
93  public $mRedirectedFrom = null;
94 
96  public $mRedirectUrl = false;
97 
103  public $mRevIdFetched = 0;
104 
113  private $fetchResult = null;
114 
120  public $mParserOutput = null;
121 
127  protected $viewIsRenderAction = false;
128 
132  protected $linkRenderer;
133 
137  private $permManager;
138 
142  private $revisionStore;
143 
155  private $mRevisionRecord = null;
156 
161  public function __construct( Title $title, $oldId = null ) {
162  $this->mOldId = $oldId;
163  $this->mPage = $this->newPage( $title );
164 
165  $services = MediaWikiServices::getInstance();
166  $this->linkRenderer = $services->getLinkRenderer();
167  $this->permManager = $services->getPermissionManager();
168  $this->revisionStore = $services->getRevisionStore();
169  }
170 
175  protected function newPage( Title $title ) {
176  return new WikiPage( $title );
177  }
178 
184  public static function newFromID( $id ) {
185  $t = Title::newFromID( $id );
186  return $t == null ? null : new static( $t );
187  }
188 
196  public static function newFromTitle( $title, IContextSource $context ) {
197  if ( $title->getNamespace() === NS_MEDIA ) {
198  // XXX: This should not be here, but where should it go?
199  $title = Title::makeTitle( NS_FILE, $title->getDBkey() );
200  }
201 
202  $page = null;
203  Hooks::runner()->onArticleFromTitle( $title, $page, $context );
204  if ( !$page ) {
205  switch ( $title->getNamespace() ) {
206  case NS_FILE:
207  $page = new ImagePage( $title );
208  break;
209  case NS_CATEGORY:
210  $page = new CategoryPage( $title );
211  break;
212  default:
213  $page = new Article( $title );
214  }
215  }
216  $page->setContext( $context );
217 
218  return $page;
219  }
220 
228  public static function newFromWikiPage( WikiPage $page, IContextSource $context ) {
229  $article = self::newFromTitle( $page->getTitle(), $context );
230  $article->mPage = $page; // override to keep process cached vars
231  return $article;
232  }
233 
239  public function getRedirectedFrom() {
240  return $this->mRedirectedFrom;
241  }
242 
248  public function setRedirectedFrom( Title $from ) {
249  $this->mRedirectedFrom = $from;
250  }
251 
257  public function getTitle() {
258  return $this->mPage->getTitle();
259  }
260 
267  public function getPage() {
268  return $this->mPage;
269  }
270 
274  public function clear() {
275  $this->mContentLoaded = false;
276 
277  $this->mRedirectedFrom = null; # Title object if set
278  $this->mRevIdFetched = 0;
279  $this->mRedirectUrl = false;
280  $this->mRevisionRecord = null;
281  $this->mContentObject = null;
282  $this->fetchResult = null;
283 
284  // TODO hard-deprecate direct access to public fields
285 
286  $this->mPage->clear();
287  }
288 
306  protected function getContentObject() {
307  if ( $this->mPage->getId() === 0 ) {
308  $content = $this->getSubstituteContent();
309  } else {
310  $this->fetchContentObject();
312  }
313 
314  return $content;
315  }
316 
322  private function getSubstituteContent() {
323  # If this is a MediaWiki:x message, then load the messages
324  # and return the message value for x.
325  if ( $this->getTitle()->getNamespace() === NS_MEDIAWIKI ) {
326  $text = $this->getTitle()->getDefaultMessageText();
327  if ( $text === false ) {
328  $text = '';
329  }
330 
331  $content = ContentHandler::makeContent( $text, $this->getTitle() );
332  } else {
333  $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
334  $content = new MessageContent( $message, null );
335  }
336 
337  return $content;
338  }
339 
349  protected function getEmptyPageParserOutput( ParserOptions $options ) {
350  $content = $this->getSubstituteContent();
351 
352  return $content->getParserOutput( $this->getTitle(), 0, $options );
353  }
354 
362  public function getOldID() {
363  if ( $this->mOldId === null ) {
364  $this->mOldId = $this->getOldIDFromRequest();
365  }
366 
367  return $this->mOldId;
368  }
369 
375  public function getOldIDFromRequest() {
376  $this->mRedirectUrl = false;
377 
378  $request = $this->getContext()->getRequest();
379  $oldid = $request->getIntOrNull( 'oldid' );
380 
381  if ( $oldid === null ) {
382  return 0;
383  }
384 
385  if ( $oldid !== 0 ) {
386  # Load the given revision and check whether the page is another one.
387  # In that case, update this instance to reflect the change.
388  if ( $oldid === $this->mPage->getLatest() ) {
389  $this->mRevisionRecord = $this->mPage->getRevisionRecord();
390  } else {
391  $this->mRevisionRecord = $this->revisionStore->getRevisionById( $oldid );
392  if ( $this->mRevisionRecord !== null ) {
393  $revPageId = $this->mRevisionRecord->getPageId();
394  // Revision title doesn't match the page title given?
395  if ( $this->mPage->getId() != $revPageId ) {
396  $function = get_class( $this->mPage ) . '::newFromID';
397  $this->mPage = $function( $revPageId );
398  }
399  }
400  }
401  }
402 
403  $oldRev = $this->mRevisionRecord;
404  if ( $request->getVal( 'direction' ) == 'next' ) {
405  $nextid = 0;
406  if ( $oldRev ) {
407  $nextRev = $this->revisionStore->getNextRevision( $oldRev );
408  if ( $nextRev ) {
409  $nextid = $nextRev->getId();
410  }
411  }
412  if ( $nextid ) {
413  $oldid = $nextid;
414  $this->mRevisionRecord = null;
415  } else {
416  $this->mRedirectUrl = $this->getTitle()->getFullURL( 'redirect=no' );
417  }
418  } elseif ( $request->getVal( 'direction' ) == 'prev' ) {
419  $previd = 0;
420  if ( $oldRev ) {
421  $prevRev = $this->revisionStore->getPreviousRevision( $oldRev );
422  if ( $prevRev ) {
423  $previd = $prevRev->getId();
424  }
425  }
426  if ( $previd ) {
427  $oldid = $previd;
428  $this->mRevisionRecord = null;
429  }
430  }
431 
432  $this->mRevIdFetched = $this->mRevisionRecord ? $this->mRevisionRecord->getId() : 0;
433 
434  return $oldid;
435  }
436 
450  protected function fetchContentObject() {
451  if ( !$this->mContentLoaded ) {
452  $this->fetchRevisionRecord();
453  }
454 
455  return $this->mContentObject;
456  }
457 
469  public function fetchRevisionRecord() {
470  if ( $this->fetchResult ) {
471  return $this->mRevisionRecord;
472  }
473 
474  $this->mContentLoaded = true;
475  $this->mContentObject = null;
476 
477  $oldid = $this->getOldID();
478 
479  // $this->mRevisionRecord might already be fetched by getOldIDFromRequest()
480  if ( !$this->mRevisionRecord ) {
481  if ( !$oldid ) {
482  $this->mRevisionRecord = $this->mPage->getRevisionRecord();
483 
484  if ( !$this->mRevisionRecord ) {
485  wfDebug( __METHOD__ . " failed to find page data for title " .
486  $this->getTitle()->getPrefixedText() );
487 
488  // Just for sanity, output for this case is done by showMissingArticle().
489  $this->fetchResult = Status::newFatal( 'noarticletext' );
490  $this->applyContentOverride( $this->makeFetchErrorContent() );
491  return null;
492  }
493  } else {
494  $this->mRevisionRecord = $this->revisionStore->getRevisionById( $oldid );
495 
496  if ( !$this->mRevisionRecord ) {
497  wfDebug( __METHOD__ . " failed to load revision, rev_id $oldid" );
498 
499  $this->fetchResult = Status::newFatal( 'missing-revision', $oldid );
500  $this->applyContentOverride( $this->makeFetchErrorContent() );
501  return null;
502  }
503  }
504  }
505 
506  $this->mRevIdFetched = $this->mRevisionRecord->getId();
507  $this->fetchResult = Status::newGood( $this->mRevisionRecord );
508 
509  if ( !RevisionRecord::userCanBitfield(
510  $this->mRevisionRecord->getVisibility(),
511  RevisionRecord::DELETED_TEXT,
512  $this->getContext()->getUser()
513  ) ) {
514  wfDebug( __METHOD__ . " failed to retrieve content of revision " .
515  $this->mRevisionRecord->getId() );
516 
517  // Just for sanity, output for this case is done by showDeletedRevisionHeader().
518  // title used in wikilinks, should not contain whitespaces
519  $this->fetchResult = Status::newFatal(
520  'rev-deleted-text-permission', $this->getTitle()->getPrefixedDBkey() );
521  $this->applyContentOverride( $this->makeFetchErrorContent() );
522  return null;
523  }
524 
525  // For B/C only
526  $this->mContentObject = $this->mRevisionRecord->getContent(
527  SlotRecord::MAIN,
528  RevisionRecord::FOR_THIS_USER,
529  $this->getContext()->getUser()
530  );
531 
532  return $this->mRevisionRecord;
533  }
534 
541  private function makeFetchErrorContent() {
542  if ( !$this->fetchResult || $this->fetchResult->isOK() ) {
543  return null;
544  }
545 
546  return new MessageContent( $this->fetchResult->getMessage() );
547  }
548 
562  private function applyContentOverride( Content $override ) {
563  // Construct a fake revision
564  $rev = new MutableRevisionRecord( $this->getTitle() );
565  $rev->setContent( SlotRecord::MAIN, $override );
566 
567  $this->mRevisionRecord = $rev;
568 
569  // For B/C only
570  $this->mContentObject = $override;
571  }
572 
578  public function isCurrent() {
579  # If no oldid, this is the current version.
580  if ( $this->getOldID() == 0 ) {
581  return true;
582  }
583 
584  return $this->mPage->exists() &&
585  $this->mRevisionRecord &&
586  $this->mRevisionRecord->isCurrent();
587  }
588 
600  public function getRevisionFetched() {
601  wfDeprecated( __METHOD__, '1.35' );
602  $revRecord = $this->fetchRevisionRecord();
603 
604  return $revRecord ? new Revision( $revRecord ) : null;
605  }
606 
615  public function getRevIdFetched() {
616  if ( $this->fetchResult && $this->fetchResult->isOK() ) {
617  return $this->fetchResult->value->getId();
618  } else {
619  return $this->mPage->getLatest();
620  }
621  }
622 
627  public function view() {
629 
630  # Get variables from query string
631  # As side effect this will load the revision and update the title
632  # in a revision ID is passed in the request, so this should remain
633  # the first call of this method even if $oldid is used way below.
634  $oldid = $this->getOldID();
635 
636  $user = $this->getContext()->getUser();
637  # Another whitelist check in case getOldID() is altering the title
638  $permErrors = $this->permManager->getPermissionErrors(
639  'read',
640  $user,
641  $this->getTitle()
642  );
643  if ( count( $permErrors ) ) {
644  wfDebug( __METHOD__ . ": denied on secondary read check" );
645  throw new PermissionsError( 'read', $permErrors );
646  }
647 
648  $outputPage = $this->getContext()->getOutput();
649  # getOldID() may as well want us to redirect somewhere else
650  if ( $this->mRedirectUrl ) {
651  $outputPage->redirect( $this->mRedirectUrl );
652  wfDebug( __METHOD__ . ": redirecting due to oldid" );
653 
654  return;
655  }
656 
657  # If we got diff in the query, we want to see a diff page instead of the article.
658  if ( $this->getContext()->getRequest()->getCheck( 'diff' ) ) {
659  wfDebug( __METHOD__ . ": showing diff page" );
660  $this->showDiffPage();
661 
662  return;
663  }
664 
665  # Set page title (may be overridden by DISPLAYTITLE)
666  $outputPage->setPageTitle( $this->getTitle()->getPrefixedText() );
667 
668  $outputPage->setArticleFlag( true );
669  # Allow frames by default
670  $outputPage->allowClickjacking();
671 
672  $parserCache = MediaWikiServices::getInstance()->getParserCache();
673 
674  $parserOptions = $this->getParserOptions();
675  $poOptions = [];
676  # Render printable version, use printable version cache
677  if ( $outputPage->isPrintable() ) {
678  $parserOptions->setIsPrintable( true );
679  $poOptions['enableSectionEditLinks'] = false;
680  $outputPage->prependHTML(
682  $outputPage->msg( 'printableversion-deprecated-warning' )->escaped()
683  )
684  );
685  } elseif ( $this->viewIsRenderAction || !$this->isCurrent() ||
686  !$this->permManager->quickUserCan( 'edit', $user, $this->getTitle() )
687  ) {
688  $poOptions['enableSectionEditLinks'] = false;
689  }
690 
691  # Try client and file cache
692  if ( $oldid === 0 && $this->mPage->checkTouched() ) {
693  # Try to stream the output from file cache
694  if ( $wgUseFileCache && $this->tryFileCache() ) {
695  wfDebug( __METHOD__ . ": done file cache" );
696  # tell wgOut that output is taken care of
697  $outputPage->disable();
698  $this->mPage->doViewUpdates( $user, $oldid );
699 
700  return;
701  }
702  }
703 
704  # Should the parser cache be used?
705  $useParserCache = $this->mPage->shouldCheckParserCache( $parserOptions, $oldid );
706  wfDebug( 'Article::view using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) );
707  if ( $user->getStubThreshold() ) {
708  MediaWikiServices::getInstance()->getStatsdDataFactory()->increment( 'pcache_miss_stub' );
709  }
710 
711  $this->showRedirectedFromHeader();
712  $this->showNamespaceHeader();
713 
714  # Iterate through the possible ways of constructing the output text.
715  # Keep going until $outputDone is set, or we run out of things to do.
716  $pass = 0;
717  $outputDone = false;
718  $this->mParserOutput = false;
719 
720  while ( !$outputDone && ++$pass ) {
721  switch ( $pass ) {
722  case 1:
723  $this->getHookRunner()->onArticleViewHeader( $this, $outputDone, $useParserCache );
724  break;
725  case 2:
726  # Early abort if the page doesn't exist
727  if ( !$this->mPage->exists() ) {
728  wfDebug( __METHOD__ . ": showing missing article" );
729  $this->showMissingArticle();
730  $this->mPage->doViewUpdates( $user );
731  return;
732  }
733 
734  # Try the parser cache
735  if ( $useParserCache ) {
736  $this->mParserOutput = $parserCache->get( $this->getPage(), $parserOptions );
737 
738  if ( $this->mParserOutput !== false ) {
739  if ( $oldid ) {
740  wfDebug( __METHOD__ . ": showing parser cache contents for current rev permalink" );
741  $this->setOldSubtitle( $oldid );
742  } else {
743  wfDebug( __METHOD__ . ": showing parser cache contents" );
744  }
745  $outputPage->addParserOutput( $this->mParserOutput, $poOptions );
746  # Ensure that UI elements requiring revision ID have
747  # the correct version information.
748  $outputPage->setRevisionId( $this->mPage->getLatest() );
749  # Preload timestamp to avoid a DB hit
750  $cachedTimestamp = $this->mParserOutput->getTimestamp();
751  if ( $cachedTimestamp !== null ) {
752  $outputPage->setRevisionTimestamp( $cachedTimestamp );
753  $this->mPage->setTimestamp( $cachedTimestamp );
754  }
755  $outputDone = true;
756  }
757  }
758  break;
759  case 3:
760  # Are we looking at an old revision
761  $rev = $this->fetchRevisionRecord();
762  if ( $oldid && $this->fetchResult->isOK() ) {
763  $this->setOldSubtitle( $oldid );
764 
765  if ( !$this->showDeletedRevisionHeader() ) {
766  wfDebug( __METHOD__ . ": cannot view deleted revision" );
767  return;
768  }
769  }
770 
771  # Ensure that UI elements requiring revision ID have
772  # the correct version information.
773  $outputPage->setRevisionId( $this->getRevIdFetched() );
774  # Preload timestamp to avoid a DB hit
775  $outputPage->setRevisionTimestamp( $this->mPage->getTimestamp() );
776 
777  # Pages containing custom CSS or JavaScript get special treatment
778  if ( $this->getTitle()->isSiteConfigPage() || $this->getTitle()->isUserConfigPage() ) {
779  $dir = $this->getContext()->getLanguage()->getDir();
780  $lang = $this->getContext()->getLanguage()->getHtmlCode();
781 
782  $outputPage->wrapWikiMsg(
783  "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
784  'clearyourcache'
785  );
786  } elseif ( !$this->getHookRunner()->onArticleRevisionViewCustom(
787  $rev,
788  $this->getTitle(),
789  $oldid,
790  $outputPage )
791  ) {
792  // NOTE: sync with hooks called in DifferenceEngine::renderNewRevision()
793  // Allow extensions do their own custom view for certain pages
794  $outputDone = true;
795  }
796  break;
797  case 4:
798  # Run the parse, protected by a pool counter
799  wfDebug( __METHOD__ . ": doing uncached parse" );
800 
801  $rev = $this->fetchRevisionRecord();
802  $error = null;
803 
804  if ( $rev ) {
805  $poolArticleView = new PoolWorkArticleView(
806  $this->getPage(),
807  $parserOptions,
808  $this->getRevIdFetched(),
809  $useParserCache,
810  $rev,
811  // permission checking was done earlier via showDeletedRevisionHeader()
812  RevisionRecord::RAW
813  );
814  $ok = $poolArticleView->execute();
815  $error = $poolArticleView->getError();
816  $this->mParserOutput = $poolArticleView->getParserOutput() ?: null;
817 
818  # Cache stale ParserOutput object with a short expiry
819  if ( $poolArticleView->getIsDirty() ) {
820  $outputPage->setCdnMaxage( $wgCdnMaxageStale );
821  $outputPage->setLastModified( $this->mParserOutput->getCacheTime() );
822  $staleReason = $poolArticleView->getIsFastStale()
823  ? 'pool contention' : 'pool overload';
824  $outputPage->addHTML( "<!-- parser cache is expired, " .
825  "sending anyway due to $staleReason-->\n" );
826  }
827  } else {
828  $ok = false;
829  }
830 
831  if ( !$ok ) {
832  if ( $error ) {
833  $outputPage->clearHTML(); // for release() errors
834  $outputPage->enableClientCache( false );
835  $outputPage->setRobotPolicy( 'noindex,nofollow' );
836 
837  $errortext = $error->getWikiText(
838  false, 'view-pool-error', $this->getContext()->getLanguage()
839  );
840  $outputPage->wrapWikiTextAsInterface( 'errorbox', $errortext );
841  }
842  # Connection or timeout error
843  return;
844  }
845 
846  if ( $this->mParserOutput ) {
847  $outputPage->addParserOutput( $this->mParserOutput, $poOptions );
848  }
849 
850  if ( $rev && $this->getRevisionRedirectTarget( $rev ) ) {
851  $outputPage->addSubtitle( "<span id=\"redirectsub\">" .
852  $this->getContext()->msg( 'redirectpagesub' )->parse() . "</span>" );
853  }
854 
855  $outputDone = true;
856  break;
857  # Should be unreachable, but just in case...
858  default:
859  break 2;
860  }
861  }
862 
863  // Get the ParserOutput actually *displayed* here.
864  // Note that $this->mParserOutput is the *current*/oldid version output.
865  // Note that the ArticleViewHeader hook is allowed to set $outputDone to a
866  // ParserOutput instance.
867  $pOutput = ( $outputDone instanceof ParserOutput )
868  ? $outputDone // object fetched by hook
869  : ( $this->mParserOutput ?: null ); // ParserOutput or null, avoid false
870 
871  # Adjust title for main page & pages with displaytitle
872  if ( $pOutput ) {
873  $this->adjustDisplayTitle( $pOutput );
874  }
875 
876  # For the main page, overwrite the <title> element with the con-
877  # tents of 'pagetitle-view-mainpage' instead of the default (if
878  # that's not empty).
879  # This message always exists because it is in the i18n files
880  if ( $this->getTitle()->isMainPage() ) {
881  $msg = wfMessage( 'pagetitle-view-mainpage' )->inContentLanguage();
882  if ( !$msg->isDisabled() ) {
883  $outputPage->setHTMLTitle( $msg->title( $this->getTitle() )->text() );
884  }
885  }
886 
887  # Use adaptive TTLs for CDN so delayed/failed purges are noticed less often.
888  # This could use getTouched(), but that could be scary for major template edits.
889  $outputPage->adaptCdnTTL( $this->mPage->getTimestamp(), IExpiringStore::TTL_DAY );
890 
891  # Check for any __NOINDEX__ tags on the page using $pOutput
892  $policy = $this->getRobotPolicy( 'view', $pOutput ?: null );
893  $outputPage->setIndexPolicy( $policy['index'] );
894  $outputPage->setFollowPolicy( $policy['follow'] ); // FIXME: test this
895 
896  $this->showViewFooter();
897  $this->mPage->doViewUpdates( $user, $oldid ); // FIXME: test this
898 
899  # Load the postEdit module if the user just saved this revision
900  # See also EditPage::setPostEditCookie
901  $request = $this->getContext()->getRequest();
903  $postEdit = $request->getCookie( $cookieKey );
904  if ( $postEdit ) {
905  # Clear the cookie. This also prevents caching of the response.
906  $request->response()->clearCookie( $cookieKey );
907  $outputPage->addJsConfigVars( 'wgPostEdit', $postEdit );
908  $outputPage->addModules( 'mediawiki.action.view.postEdit' ); // FIXME: test this
909  }
910  }
911 
916  private function getRevisionRedirectTarget( RevisionRecord $revision ) {
917  // TODO: find a *good* place for the code that determines the redirect target for
918  // a given revision!
919  // NOTE: Use main slot content. Compare code in DerivedPageDataUpdater::revisionIsRedirect.
920  $content = $revision->getContent( SlotRecord::MAIN );
921  return $content ? $content->getRedirectTarget() : null;
922  }
923 
928  public function adjustDisplayTitle( ParserOutput $pOutput ) {
929  $out = $this->getContext()->getOutput();
930 
931  # Adjust the title if it was set by displaytitle, -{T|}- or language conversion
932  $titleText = $pOutput->getTitleText();
933  if ( strval( $titleText ) !== '' ) {
934  $out->setPageTitle( $titleText );
935  $out->setDisplayTitle( $titleText );
936  }
937  }
938 
943  protected function showDiffPage() {
944  $request = $this->getContext()->getRequest();
945  $user = $this->getContext()->getUser();
946  $diff = $request->getVal( 'diff' );
947  $rcid = $request->getVal( 'rcid' );
948  $diffOnly = $request->getBool( 'diffonly', $user->getOption( 'diffonly' ) );
949  $purge = $request->getVal( 'action' ) == 'purge';
950  $unhide = $request->getInt( 'unhide' ) == 1;
951  $oldid = $this->getOldID();
952 
953  $rev = $this->fetchRevisionRecord();
954 
955  if ( !$rev ) {
956  // T213621: $rev maybe null due to either lack of permission to view the
957  // revision or actually not existing. So let's try loading it from the id
958  $services = MediaWikiServices::getInstance();
959  $rev = $services->getRevisionLookup()->getRevisionById( $oldid );
960  if ( $rev ) {
961  // Revision exists but $user lacks permission to diff it.
962  // Do nothing here.
963  // The $rev will later be used to create standard diff elements however.
964  } else {
965  $this->getContext()->getOutput()->setPageTitle( wfMessage( 'errorpagetitle' ) );
966  $msg = $this->getContext()->msg( 'difference-missing-revision' )
967  ->params( $oldid )
968  ->numParams( 1 )
969  ->parseAsBlock();
970  $this->getContext()->getOutput()->addHTML( $msg );
971  return;
972  }
973  }
974 
975  $contentHandler = MediaWikiServices::getInstance()
976  ->getContentHandlerFactory()
977  ->getContentHandler(
978  $rev->getSlot( SlotRecord::MAIN, RevisionRecord::RAW )->getModel()
979  );
980  $de = $contentHandler->createDifferenceEngine(
981  $this->getContext(),
982  $oldid,
983  $diff,
984  $rcid,
985  $purge,
986  $unhide
987  );
988  $de->setSlotDiffOptions( [
989  'diff-type' => $request->getVal( 'diff-type' )
990  ] );
991 
992  // DifferenceEngine directly fetched the revision:
993  $this->mRevIdFetched = $de->getNewid();
994  $de->showDiffPage( $diffOnly );
995 
996  // Run view updates for the newer revision being diffed (and shown
997  // below the diff if not $diffOnly).
998  list( $old, $new ) = $de->mapDiffPrevNext( $oldid, $diff );
999  // New can be false, convert it to 0 - this conveniently means the latest revision
1000  $this->mPage->doViewUpdates( $user, (int)$new );
1001  }
1002 
1010  public function getRobotPolicy( $action, ParserOutput $pOutput = null ) {
1012 
1013  $ns = $this->getTitle()->getNamespace();
1014 
1015  # Don't index user and user talk pages for blocked users (T13443)
1016  if ( ( $ns === NS_USER || $ns === NS_USER_TALK ) && !$this->getTitle()->isSubpage() ) {
1017  $specificTarget = null;
1018  $vagueTarget = null;
1019  $titleText = $this->getTitle()->getText();
1020  if ( IPUtils::isValid( $titleText ) ) {
1021  $vagueTarget = $titleText;
1022  } else {
1023  $specificTarget = $titleText;
1024  }
1025  if ( DatabaseBlock::newFromTarget( $specificTarget, $vagueTarget ) instanceof DatabaseBlock ) {
1026  return [
1027  'index' => 'noindex',
1028  'follow' => 'nofollow'
1029  ];
1030  }
1031  }
1032 
1033  if ( $this->mPage->getId() === 0 || $this->getOldID() ) {
1034  # Non-articles (special pages etc), and old revisions
1035  return [
1036  'index' => 'noindex',
1037  'follow' => 'nofollow'
1038  ];
1039  } elseif ( $this->getContext()->getOutput()->isPrintable() ) {
1040  # Discourage indexing of printable versions, but encourage following
1041  return [
1042  'index' => 'noindex',
1043  'follow' => 'follow'
1044  ];
1045  } elseif ( $this->getContext()->getRequest()->getInt( 'curid' ) ) {
1046  # For ?curid=x urls, disallow indexing
1047  return [
1048  'index' => 'noindex',
1049  'follow' => 'follow'
1050  ];
1051  }
1052 
1053  # Otherwise, construct the policy based on the various config variables.
1055 
1056  if ( isset( $wgNamespaceRobotPolicies[$ns] ) ) {
1057  # Honour customised robot policies for this namespace
1058  $policy = array_merge(
1059  $policy,
1060  self::formatRobotPolicy( $wgNamespaceRobotPolicies[$ns] )
1061  );
1062  }
1063  if ( $this->getTitle()->canUseNoindex() && is_object( $pOutput ) && $pOutput->getIndexPolicy() ) {
1064  # __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates
1065  # a final sanity check that we have really got the parser output.
1066  $policy = array_merge(
1067  $policy,
1068  [ 'index' => $pOutput->getIndexPolicy() ]
1069  );
1070  }
1071 
1072  if ( isset( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] ) ) {
1073  # (T16900) site config can override user-defined __INDEX__ or __NOINDEX__
1074  $policy = array_merge(
1075  $policy,
1076  self::formatRobotPolicy( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] )
1077  );
1078  }
1079 
1080  return $policy;
1081  }
1082 
1090  public static function formatRobotPolicy( $policy ) {
1091  if ( is_array( $policy ) ) {
1092  return $policy;
1093  } elseif ( !$policy ) {
1094  return [];
1095  }
1096 
1097  $policy = explode( ',', $policy );
1098  $policy = array_map( 'trim', $policy );
1099 
1100  $arr = [];
1101  foreach ( $policy as $var ) {
1102  if ( in_array( $var, [ 'index', 'noindex' ] ) ) {
1103  $arr['index'] = $var;
1104  } elseif ( in_array( $var, [ 'follow', 'nofollow' ] ) ) {
1105  $arr['follow'] = $var;
1106  }
1107  }
1108 
1109  return $arr;
1110  }
1111 
1119  public function showRedirectedFromHeader() {
1120  global $wgRedirectSources;
1121 
1122  $context = $this->getContext();
1123  $outputPage = $context->getOutput();
1124  $request = $context->getRequest();
1125  $rdfrom = $request->getVal( 'rdfrom' );
1126 
1127  // Construct a URL for the current page view, but with the target title
1128  $query = $request->getValues();
1129  unset( $query['rdfrom'] );
1130  unset( $query['title'] );
1131  if ( $this->getTitle()->isRedirect() ) {
1132  // Prevent double redirects
1133  $query['redirect'] = 'no';
1134  }
1135  $redirectTargetUrl = $this->getTitle()->getLinkURL( $query );
1136 
1137  if ( isset( $this->mRedirectedFrom ) ) {
1138  // This is an internally redirected page view.
1139  // We'll need a backlink to the source page for navigation.
1140  if ( $this->getHookRunner()->onArticleViewRedirect( $this ) ) {
1141  $redir = $this->linkRenderer->makeKnownLink(
1142  $this->mRedirectedFrom,
1143  null,
1144  [],
1145  [ 'redirect' => 'no' ]
1146  );
1147 
1148  $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
1149  $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
1150  . "</span>" );
1151 
1152  // Add the script to update the displayed URL and
1153  // set the fragment if one was specified in the redirect
1154  $outputPage->addJsConfigVars( [
1155  'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1156  ] );
1157  $outputPage->addModules( 'mediawiki.action.view.redirect' );
1158 
1159  // Add a <link rel="canonical"> tag
1160  $outputPage->setCanonicalUrl( $this->getTitle()->getCanonicalURL() );
1161 
1162  // Tell the output object that the user arrived at this article through a redirect
1163  $outputPage->setRedirectedFrom( $this->mRedirectedFrom );
1164 
1165  return true;
1166  }
1167  } elseif ( $rdfrom ) {
1168  // This is an externally redirected view, from some other wiki.
1169  // If it was reported from a trusted site, supply a backlink.
1170  if ( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) {
1171  $redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
1172  $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
1173  $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
1174  . "</span>" );
1175 
1176  // Add the script to update the displayed URL
1177  $outputPage->addJsConfigVars( [
1178  'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1179  ] );
1180  $outputPage->addModules( 'mediawiki.action.view.redirect' );
1181 
1182  return true;
1183  }
1184  }
1185 
1186  return false;
1187  }
1188 
1193  public function showNamespaceHeader() {
1194  if ( $this->getTitle()->isTalkPage() && !wfMessage( 'talkpageheader' )->isDisabled() ) {
1195  $this->getContext()->getOutput()->wrapWikiMsg(
1196  "<div class=\"mw-talkpageheader\">\n$1\n</div>",
1197  [ 'talkpageheader' ]
1198  );
1199  }
1200  }
1201 
1205  public function showViewFooter() {
1206  # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
1207  if ( $this->getTitle()->getNamespace() === NS_USER_TALK
1208  && IPUtils::isValid( $this->getTitle()->getText() )
1209  ) {
1210  $this->getContext()->getOutput()->addWikiMsg( 'anontalkpagetext' );
1211  }
1212 
1213  // Show a footer allowing the user to patrol the shown revision or page if possible
1214  $patrolFooterShown = $this->showPatrolFooter();
1215 
1216  $this->getHookRunner()->onArticleViewFooter( $this, $patrolFooterShown );
1217  }
1218 
1229  public function showPatrolFooter() {
1231 
1232  // Allow hooks to decide whether to not output this at all
1233  if ( !$this->getHookRunner()->onArticleShowPatrolFooter( $this ) ) {
1234  return false;
1235  }
1236 
1237  $outputPage = $this->getContext()->getOutput();
1238  $user = $this->getContext()->getUser();
1239  $title = $this->getTitle();
1240  $rc = false;
1241 
1242  if ( !$this->permManager->quickUserCan( 'patrol', $user, $title )
1244  || ( $wgUseFilePatrol && $title->inNamespace( NS_FILE ) ) )
1245  ) {
1246  // Patrolling is disabled or the user isn't allowed to
1247  return false;
1248  }
1249 
1250  if ( $this->mRevisionRecord
1251  && !RecentChange::isInRCLifespan( $this->mRevisionRecord->getTimestamp(), 21600 )
1252  ) {
1253  // The current revision is already older than what could be in the RC table
1254  // 6h tolerance because the RC might not be cleaned out regularly
1255  return false;
1256  }
1257 
1258  // Check for cached results
1259  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1260  $key = $cache->makeKey( 'unpatrollable-page', $title->getArticleID() );
1261  if ( $cache->get( $key ) ) {
1262  return false;
1263  }
1264 
1265  $dbr = wfGetDB( DB_REPLICA );
1266  $oldestRevisionTimestamp = $dbr->selectField(
1267  'revision',
1268  'MIN( rev_timestamp )',
1269  [ 'rev_page' => $title->getArticleID() ],
1270  __METHOD__
1271  );
1272 
1273  // New page patrol: Get the timestamp of the oldest revison which
1274  // the revision table holds for the given page. Then we look
1275  // whether it's within the RC lifespan and if it is, we try
1276  // to get the recentchanges row belonging to that entry
1277  // (with rc_new = 1).
1278  $recentPageCreation = false;
1279  if ( $oldestRevisionTimestamp
1280  && RecentChange::isInRCLifespan( $oldestRevisionTimestamp, 21600 )
1281  ) {
1282  // 6h tolerance because the RC might not be cleaned out regularly
1283  $recentPageCreation = true;
1285  [
1286  'rc_new' => 1,
1287  'rc_timestamp' => $oldestRevisionTimestamp,
1288  'rc_namespace' => $title->getNamespace(),
1289  'rc_cur_id' => $title->getArticleID()
1290  ],
1291  __METHOD__
1292  );
1293  if ( $rc ) {
1294  // Use generic patrol message for new pages
1295  $markPatrolledMsg = wfMessage( 'markaspatrolledtext' );
1296  }
1297  }
1298 
1299  // File patrol: Get the timestamp of the latest upload for this page,
1300  // check whether it is within the RC lifespan and if it is, we try
1301  // to get the recentchanges row belonging to that entry
1302  // (with rc_type = RC_LOG, rc_log_type = upload).
1303  $recentFileUpload = false;
1304  if ( ( !$rc || $rc->getAttribute( 'rc_patrolled' ) ) && $wgUseFilePatrol
1305  && $title->getNamespace() === NS_FILE ) {
1306  // Retrieve timestamp of most recent upload
1307  $newestUploadTimestamp = $dbr->selectField(
1308  'image',
1309  'MAX( img_timestamp )',
1310  [ 'img_name' => $title->getDBkey() ],
1311  __METHOD__
1312  );
1313  if ( $newestUploadTimestamp
1314  && RecentChange::isInRCLifespan( $newestUploadTimestamp, 21600 )
1315  ) {
1316  // 6h tolerance because the RC might not be cleaned out regularly
1317  $recentFileUpload = true;
1319  [
1320  'rc_type' => RC_LOG,
1321  'rc_log_type' => 'upload',
1322  'rc_timestamp' => $newestUploadTimestamp,
1323  'rc_namespace' => NS_FILE,
1324  'rc_cur_id' => $title->getArticleID()
1325  ],
1326  __METHOD__
1327  );
1328  if ( $rc ) {
1329  // Use patrol message specific to files
1330  $markPatrolledMsg = wfMessage( 'markaspatrolledtext-file' );
1331  }
1332  }
1333  }
1334 
1335  if ( !$recentPageCreation && !$recentFileUpload ) {
1336  // Page creation and latest upload (for files) is too old to be in RC
1337 
1338  // We definitely can't patrol so cache the information
1339  // When a new file version is uploaded, the cache is cleared
1340  $cache->set( $key, '1' );
1341 
1342  return false;
1343  }
1344 
1345  if ( !$rc ) {
1346  // Don't cache: This can be hit if the page gets accessed very fast after
1347  // its creation / latest upload or in case we have high replica DB lag. In case
1348  // the revision is too old, we will already return above.
1349  return false;
1350  }
1351 
1352  if ( $rc->getAttribute( 'rc_patrolled' ) ) {
1353  // Patrolled RC entry around
1354 
1355  // Cache the information we gathered above in case we can't patrol
1356  // Don't cache in case we can patrol as this could change
1357  $cache->set( $key, '1' );
1358 
1359  return false;
1360  }
1361 
1362  if ( $rc->getPerformer()->equals( $user ) ) {
1363  // Don't show a patrol link for own creations/uploads. If the user could
1364  // patrol them, they already would be patrolled
1365  return false;
1366  }
1367 
1368  $outputPage->preventClickjacking();
1369  if ( $this->permManager->userHasRight( $user, 'writeapi' ) ) {
1370  $outputPage->addModules( 'mediawiki.misc-authed-curate' );
1371  }
1372 
1373  $link = $this->linkRenderer->makeKnownLink(
1374  $title,
1375  $markPatrolledMsg->text(),
1376  [],
1377  [
1378  'action' => 'markpatrolled',
1379  'rcid' => $rc->getAttribute( 'rc_id' ),
1380  ]
1381  );
1382 
1383  $outputPage->addHTML(
1384  "<div class='patrollink' data-mw='interface'>" .
1385  wfMessage( 'markaspatrolledlink' )->rawParams( $link )->escaped() .
1386  '</div>'
1387  );
1388 
1389  return true;
1390  }
1391 
1398  public static function purgePatrolFooterCache( $articleID ) {
1399  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1400  $cache->delete( $cache->makeKey( 'unpatrollable-page', $articleID ) );
1401  }
1402 
1407  public function showMissingArticle() {
1408  global $wgSend404Code;
1409 
1410  $outputPage = $this->getContext()->getOutput();
1411  // Whether the page is a root user page of an existing user (but not a subpage)
1412  $validUserPage = false;
1413 
1414  $title = $this->getTitle();
1415 
1416  $services = MediaWikiServices::getInstance();
1417 
1418  # Show info in user (talk) namespace. Does the user exist? Is he blocked?
1419  if ( $title->getNamespace() === NS_USER
1420  || $title->getNamespace() === NS_USER_TALK
1421  ) {
1422  $rootPart = explode( '/', $title->getText() )[0];
1423  $user = User::newFromName( $rootPart, false /* allow IP users */ );
1424  $ip = User::isIP( $rootPart );
1425  $block = DatabaseBlock::newFromTarget( $user, $user );
1426 
1427  if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
1428  $outputPage->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
1429  [ 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) ] );
1430  } elseif (
1431  $block !== null &&
1432  $block->getType() != DatabaseBlock::TYPE_AUTO &&
1433  ( $block->isSitewide() || $user->isBlockedFrom( $title ) )
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->getTarget(),
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 = ObjectCache::getInstance( 'db-replicated' );
1464  $key = $dbCache->makeKey( 'page-recent-delete', md5( $title->getPrefixedText() ) );
1465  $loggedIn = $this->getContext()->getUser()->isLoggedIn();
1466  $sessionExists = $this->getContext()->getRequest()->getSession()->isPersistent();
1467  if ( $loggedIn || $dbCache->get( $key ) || $sessionExists ) {
1468  $logTypes = [ 'delete', 'move', 'protect' ];
1469 
1470  $dbr = wfGetDB( DB_REPLICA );
1471 
1472  $conds = [ 'log_action != ' . $dbr->addQuotes( 'revision' ) ];
1473  // Give extensions a chance to hide their (unrelated) log entries
1474  $this->getHookRunner()->onArticle__MissingArticleConditions( $conds, $logTypes );
1476  $outputPage,
1477  $logTypes,
1478  $title,
1479  '',
1480  [
1481  'lim' => 10,
1482  'conds' => $conds,
1483  'showIfEmpty' => false,
1484  'msgKey' => [ $loggedIn || $sessionExists
1485  ? 'moveddeleted-notice'
1486  : 'moveddeleted-notice-recent'
1487  ]
1488  ]
1489  );
1490  }
1491 
1492  if ( !$this->mPage->hasViewableContent() && $wgSend404Code && !$validUserPage ) {
1493  // If there's no backing content, send a 404 Not Found
1494  // for better machine handling of broken links.
1495  $this->getContext()->getRequest()->response()->statusHeader( 404 );
1496  }
1497 
1498  // Also apply the robot policy for nonexisting pages (even if a 404 was used for sanity)
1499  $policy = $this->getRobotPolicy( 'view' );
1500  $outputPage->setIndexPolicy( $policy['index'] );
1501  $outputPage->setFollowPolicy( $policy['follow'] );
1502 
1503  $hookResult = $this->getHookRunner()->onBeforeDisplayNoArticleText( $this );
1504 
1505  if ( !$hookResult ) {
1506  return;
1507  }
1508 
1509  # Show error message
1510  $oldid = $this->getOldID();
1511  $pm = $this->permManager;
1512  if ( !$oldid && $title->getNamespace() === NS_MEDIAWIKI && $title->hasSourceText() ) {
1513  // use fake Content object for system message
1514  $parserOptions = ParserOptions::newCanonical( 'canonical' );
1515  $outputPage->addParserOutput( $this->getEmptyPageParserOutput( $parserOptions ) );
1516  } else {
1517  if ( $oldid ) {
1518  // T251066: Try loading the revision from the archive table.
1519  // Show link to view it if it exists and the user has permission to view it.
1520  $pa = new PageArchive( $title, $this->getContext()->getConfig() );
1521  $revRecord = $pa->getArchivedRevisionRecord( $oldid );
1522  if ( $revRecord && $revRecord->audienceCan(
1523  RevisionRecord::DELETED_TEXT,
1524  RevisionRecord::FOR_THIS_USER,
1525  $this->getContext()->getUser()
1526  ) ) {
1527  $text = wfMessage(
1528  'missing-revision-permission', $oldid,
1529  $revRecord->getTimestamp(),
1530  $title->getPrefixedDBkey()
1531  )->plain();
1532  } else {
1533  $text = wfMessage( 'missing-revision', $oldid )->plain();
1534  }
1535 
1536  } elseif ( $pm->quickUserCan( 'create', $this->getContext()->getUser(), $title ) &&
1537  $pm->quickUserCan( 'edit', $this->getContext()->getUser(), $title )
1538  ) {
1539  $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
1540  $text = wfMessage( $message )->plain();
1541  } else {
1542  $text = wfMessage( 'noarticletext-nopermission' )->plain();
1543  }
1544 
1545  $dir = $this->getContext()->getLanguage()->getDir();
1546  $lang = $this->getContext()->getLanguage()->getHtmlCode();
1547  $outputPage->addWikiTextAsInterface( Xml::openElement( 'div', [
1548  'class' => "noarticletext mw-content-$dir",
1549  'dir' => $dir,
1550  'lang' => $lang,
1551  ] ) . "\n$text\n</div>" );
1552  }
1553  }
1554 
1561  public function showDeletedRevisionHeader() {
1562  if ( !$this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1563  // Not deleted
1564  return true;
1565  }
1566 
1567  $outputPage = $this->getContext()->getOutput();
1568  $user = $this->getContext()->getUser();
1569  // Used in wikilinks, should not contain whitespaces
1570  $titleText = $this->getTitle()->getPrefixedDBkey();
1571  // If the user is not allowed to see it...
1572  if ( !RevisionRecord::userCanBitfield(
1573  $this->mRevisionRecord->getVisibility(),
1574  RevisionRecord::DELETED_TEXT,
1575  $user
1576  ) ) {
1577  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1578  [ 'rev-deleted-text-permission', $titleText ] );
1579 
1580  return false;
1581  // If the user needs to confirm that they want to see it...
1582  } elseif ( $this->getContext()->getRequest()->getInt( 'unhide' ) != 1 ) {
1583  # Give explanation and add a link to view the revision...
1584  $oldid = intval( $this->getOldID() );
1585  $link = $this->getTitle()->getFullURL( "oldid={$oldid}&unhide=1" );
1586  $msg = $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ?
1587  'rev-suppressed-text-unhide' : 'rev-deleted-text-unhide';
1588  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", [ $msg, $link ] );
1589 
1590  return false;
1591  // We are allowed to see...
1592  } else {
1593  $msg = $this->mRevisionRecord->isDeleted( RevisionRecord::DELETED_RESTRICTED )
1594  ? [ 'rev-suppressed-text-view', $titleText ]
1595  : [ 'rev-deleted-text-view', $titleText ];
1596  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", $msg );
1597 
1598  return true;
1599  }
1600  }
1601 
1610  public function setOldSubtitle( $oldid = 0 ) {
1611  if ( !$this->getHookRunner()->onDisplayOldSubtitle( $this, $oldid ) ) {
1612  return;
1613  }
1614 
1615  $context = $this->getContext();
1616  $unhide = $context->getRequest()->getInt( 'unhide' ) == 1;
1617 
1618  # Cascade unhide param in links for easy deletion browsing
1619  $extraParams = [];
1620  if ( $unhide ) {
1621  $extraParams['unhide'] = 1;
1622  }
1623 
1624  if ( $this->mRevisionRecord && $this->mRevisionRecord->getId() === $oldid ) {
1625  $revisionRecord = $this->mRevisionRecord;
1626  } else {
1627  $revisionRecord = $this->revisionStore->getRevisionById( $oldid );
1628  }
1629 
1630  $timestamp = $revisionRecord->getTimestamp();
1631 
1632  $current = ( $oldid == $this->mPage->getLatest() );
1633  $language = $context->getLanguage();
1634  $user = $context->getUser();
1635 
1636  $td = $language->userTimeAndDate( $timestamp, $user );
1637  $tddate = $language->userDate( $timestamp, $user );
1638  $tdtime = $language->userTime( $timestamp, $user );
1639 
1640  # Show user links if allowed to see them. If hidden, then show them only if requested...
1641  $userlinks = Linker::revUserTools( $revisionRecord, !$unhide );
1642 
1643  $infomsg = $current && !$context->msg( 'revision-info-current' )->isDisabled()
1644  ? 'revision-info-current'
1645  : 'revision-info';
1646 
1647  $outputPage = $context->getOutput();
1648  $revisionUser = $revisionRecord->getUser();
1649  $revisionInfo = "<div id=\"mw-{$infomsg}\">" .
1650  $context->msg( $infomsg, $td )
1651  ->rawParams( $userlinks )
1652  ->params(
1653  $revisionRecord->getId(),
1654  $tddate,
1655  $tdtime,
1656  $revisionUser ? $revisionUser->getName() : ''
1657  )
1658  ->rawParams( Linker::revComment(
1659  $revisionRecord,
1660  true,
1661  true
1662  ) )
1663  ->parse() .
1664  "</div>";
1665 
1666  $lnk = $current
1667  ? $context->msg( 'currentrevisionlink' )->escaped()
1668  : $this->linkRenderer->makeKnownLink(
1669  $this->getTitle(),
1670  $context->msg( 'currentrevisionlink' )->text(),
1671  [],
1672  $extraParams
1673  );
1674  $curdiff = $current
1675  ? $context->msg( 'diff' )->escaped()
1676  : $this->linkRenderer->makeKnownLink(
1677  $this->getTitle(),
1678  $context->msg( 'diff' )->text(),
1679  [],
1680  [
1681  'diff' => 'cur',
1682  'oldid' => $oldid
1683  ] + $extraParams
1684  );
1685  $prevExist = (bool)$this->revisionStore->getPreviousRevision( $revisionRecord );
1686  $prevlink = $prevExist
1687  ? $this->linkRenderer->makeKnownLink(
1688  $this->getTitle(),
1689  $context->msg( 'previousrevision' )->text(),
1690  [],
1691  [
1692  'direction' => 'prev',
1693  'oldid' => $oldid
1694  ] + $extraParams
1695  )
1696  : $context->msg( 'previousrevision' )->escaped();
1697  $prevdiff = $prevExist
1698  ? $this->linkRenderer->makeKnownLink(
1699  $this->getTitle(),
1700  $context->msg( 'diff' )->text(),
1701  [],
1702  [
1703  'diff' => 'prev',
1704  'oldid' => $oldid
1705  ] + $extraParams
1706  )
1707  : $context->msg( 'diff' )->escaped();
1708  $nextlink = $current
1709  ? $context->msg( 'nextrevision' )->escaped()
1710  : $this->linkRenderer->makeKnownLink(
1711  $this->getTitle(),
1712  $context->msg( 'nextrevision' )->text(),
1713  [],
1714  [
1715  'direction' => 'next',
1716  'oldid' => $oldid
1717  ] + $extraParams
1718  );
1719  $nextdiff = $current
1720  ? $context->msg( 'diff' )->escaped()
1721  : $this->linkRenderer->makeKnownLink(
1722  $this->getTitle(),
1723  $context->msg( 'diff' )->text(),
1724  [],
1725  [
1726  'diff' => 'next',
1727  'oldid' => $oldid
1728  ] + $extraParams
1729  );
1730 
1731  $cdel = Linker::getRevDeleteLink(
1732  $user,
1733  $revisionRecord,
1734  $this->getTitle()
1735  );
1736  if ( $cdel !== '' ) {
1737  $cdel .= ' ';
1738  }
1739 
1740  // the outer div is need for styling the revision info and nav in MobileFrontend
1741  $outputPage->addSubtitle( "<div class=\"mw-revision warningbox\">" . $revisionInfo .
1742  "<div id=\"mw-revision-nav\">" . $cdel .
1743  $context->msg( 'revision-nav' )->rawParams(
1744  $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff
1745  )->escaped() . "</div></div>" );
1746  }
1747 
1761  public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) {
1762  $lang = $this->getTitle()->getPageLanguage();
1763  $out = $this->getContext()->getOutput();
1764  if ( $appendSubtitle ) {
1765  $out->addSubtitle( wfMessage( 'redirectpagesub' ) );
1766  }
1767  $out->addModuleStyles( 'mediawiki.action.view.redirectPage' );
1768  return static::getRedirectHeaderHtml( $lang, $target, $forceKnown );
1769  }
1770 
1783  public static function getRedirectHeaderHtml( Language $lang, $target, $forceKnown = false ) {
1784  if ( !is_array( $target ) ) {
1785  $target = [ $target ];
1786  }
1787 
1788  $linkRenderer = MediaWikiServices::getInstance()->getLinkRenderer();
1789 
1790  $html = '<ul class="redirectText">';
1792  foreach ( $target as $title ) {
1793  if ( $forceKnown ) {
1794  $link = $linkRenderer->makeKnownLink(
1795  $title,
1796  $title->getFullText(),
1797  [],
1798  // Make sure wiki page redirects are not followed
1799  $title->isRedirect() ? [ 'redirect' => 'no' ] : []
1800  );
1801  } else {
1802  $link = $linkRenderer->makeLink(
1803  $title,
1804  $title->getFullText(),
1805  [],
1806  // Make sure wiki page redirects are not followed
1807  $title->isRedirect() ? [ 'redirect' => 'no' ] : []
1808  );
1809  }
1810  $html .= '<li>' . $link . '</li>';
1811  }
1812  $html .= '</ul>';
1813 
1814  $redirectToText = wfMessage( 'redirectto' )->inLanguage( $lang )->escaped();
1815 
1816  return '<div class="redirectMsg">' .
1817  '<p>' . $redirectToText . '</p>' .
1818  $html .
1819  '</div>';
1820  }
1821 
1830  public function addHelpLink( $to, $overrideBaseUrl = false ) {
1831  $out = $this->getContext()->getOutput();
1832  $msg = $out->msg( 'namespace-' . $this->getTitle()->getNamespace() . '-helppage' );
1833 
1834  if ( !$msg->isDisabled() ) {
1835  $title = Title::newFromText( $msg->plain() );
1836  if ( $title instanceof Title ) {
1837  $out->addHelpLink( $title->getLocalURL(), true );
1838  }
1839  } else {
1840  $out->addHelpLink( $to, $overrideBaseUrl );
1841  }
1842  }
1843 
1847  public function render() {
1848  $this->getContext()->getRequest()->response()->header( 'X-Robots-Tag: noindex' );
1849  $this->getContext()->getOutput()->setArticleBodyOnly( true );
1850  // We later set 'enableSectionEditLinks=false' based on this; also used by ImagePage
1851  $this->viewIsRenderAction = true;
1852  $this->view();
1853  }
1854 
1858  public function protect() {
1859  $form = new ProtectionForm( $this );
1860  $form->execute();
1861  }
1862 
1866  public function unprotect() {
1867  $this->protect();
1868  }
1869 
1873  public function delete() {
1874  # This code desperately needs to be totally rewritten
1875 
1876  $title = $this->getTitle();
1877  $context = $this->getContext();
1878  $user = $context->getUser();
1879  $request = $context->getRequest();
1880 
1881  # Check permissions
1882  $permissionErrors = $this->permManager->getPermissionErrors( 'delete', $user, $title );
1883  if ( count( $permissionErrors ) ) {
1884  throw new PermissionsError( 'delete', $permissionErrors );
1885  }
1886 
1887  # Read-only check...
1888  if ( wfReadOnly() ) {
1889  throw new ReadOnlyError;
1890  }
1891 
1892  # Better double-check that it hasn't been deleted yet!
1893  $this->mPage->loadPageData(
1894  $request->wasPosted() ? WikiPage::READ_LATEST : WikiPage::READ_NORMAL
1895  );
1896  if ( !$this->mPage->exists() ) {
1897  $deleteLogPage = new LogPage( 'delete' );
1898  $outputPage = $context->getOutput();
1899  $outputPage->setPageTitle( $context->msg( 'cannotdelete-title', $title->getPrefixedText() ) );
1900  $outputPage->wrapWikiMsg( "<div class=\"error mw-error-cannotdelete\">\n$1\n</div>",
1901  [ 'cannotdelete', wfEscapeWikiText( $title->getPrefixedText() ) ]
1902  );
1903  $outputPage->addHTML(
1904  Xml::element( 'h2', null, $deleteLogPage->getName()->text() )
1905  );
1907  $outputPage,
1908  'delete',
1909  $title
1910  );
1911 
1912  return;
1913  }
1914 
1915  $deleteReasonList = $request->getText( 'wpDeleteReasonList', 'other' );
1916  $deleteReason = $request->getText( 'wpReason' );
1917 
1918  if ( $deleteReasonList == 'other' ) {
1919  $reason = $deleteReason;
1920  } elseif ( $deleteReason != '' ) {
1921  // Entry from drop down menu + additional comment
1922  $colonseparator = wfMessage( 'colon-separator' )->inContentLanguage()->text();
1923  $reason = $deleteReasonList . $colonseparator . $deleteReason;
1924  } else {
1925  $reason = $deleteReasonList;
1926  }
1927 
1928  if ( $request->wasPosted() && $user->matchEditToken( $request->getVal( 'wpEditToken' ),
1929  [ 'delete', $this->getTitle()->getPrefixedText() ] )
1930  ) {
1931  # Flag to hide all contents of the archived revisions
1932 
1933  $suppress = $request->getCheck( 'wpSuppress' ) &&
1934  $this->permManager->userHasRight( $user, 'suppressrevision' );
1935 
1936  $this->doDelete( $reason, $suppress );
1937 
1938  WatchAction::doWatchOrUnwatch( $request->getCheck( 'wpWatch' ), $title, $user );
1939 
1940  return;
1941  }
1942 
1943  // Generate deletion reason
1944  $hasHistory = false;
1945  if ( !$reason ) {
1946  try {
1947  $reason = $this->getPage()
1948  ->getAutoDeleteReason( $hasHistory );
1949  } catch ( Exception $e ) {
1950  # if a page is horribly broken, we still want to be able to
1951  # delete it. So be lenient about errors here.
1952  wfDebug( "Error while building auto delete summary: $e" );
1953  $reason = '';
1954  }
1955  }
1956 
1957  // If the page has a history, insert a warning
1958  if ( $hasHistory ) {
1959  $title = $this->getTitle();
1960 
1961  // The following can use the real revision count as this is only being shown for users
1962  // that can delete this page.
1963  // This, as a side-effect, also makes sure that the following query isn't being run for
1964  // pages with a larger history, unless the user has the 'bigdelete' right
1965  // (and is about to delete this page).
1966  $dbr = wfGetDB( DB_REPLICA );
1967  $revisions = $edits = (int)$dbr->selectField(
1968  'revision',
1969  'COUNT(rev_page)',
1970  [ 'rev_page' => $title->getArticleID() ],
1971  __METHOD__
1972  );
1973 
1974  // @todo i18n issue/patchwork message
1975  $context->getOutput()->addHTML(
1976  '<strong class="mw-delete-warning-revisions">' .
1977  $context->msg( 'historywarning' )->numParams( $revisions )->parse() .
1978  $context->msg( 'word-separator' )->escaped() . $this->linkRenderer->makeKnownLink(
1979  $title,
1980  $context->msg( 'history' )->text(),
1981  [],
1982  [ 'action' => 'history' ] ) .
1983  '</strong>'
1984  );
1985 
1986  if ( $title->isBigDeletion() ) {
1987  global $wgDeleteRevisionsLimit;
1988  $context->getOutput()->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
1989  [
1990  'delete-warning-toobig',
1991  $context->getLanguage()->formatNum( $wgDeleteRevisionsLimit )
1992  ]
1993  );
1994  }
1995  }
1996 
1997  $this->confirmDelete( $reason );
1998  }
1999 
2005  public function confirmDelete( $reason ) {
2006  wfDebug( "Article::confirmDelete" );
2007 
2008  $title = $this->getTitle();
2009  $ctx = $this->getContext();
2010  $outputPage = $ctx->getOutput();
2011  $outputPage->setPageTitle( wfMessage( 'delete-confirm', $title->getPrefixedText() ) );
2012  $outputPage->addBacklinkSubtitle( $title );
2013  $outputPage->setRobotPolicy( 'noindex,nofollow' );
2014  $outputPage->addModules( 'mediawiki.action.delete' );
2015 
2016  $backlinkCache = $title->getBacklinkCache();
2017  if ( $backlinkCache->hasLinks( 'pagelinks' ) || $backlinkCache->hasLinks( 'templatelinks' ) ) {
2018  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
2019  'deleting-backlinks-warning' );
2020  }
2021 
2022  $subpageQueryLimit = 51;
2023  $subpages = $title->getSubpages( $subpageQueryLimit );
2024  $subpageCount = count( $subpages );
2025  if ( $subpageCount > 0 ) {
2026  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
2027  [ 'deleting-subpages-warning', Message::numParam( $subpageCount ) ] );
2028  }
2029  $outputPage->addWikiMsg( 'confirmdeletetext' );
2030 
2031  $this->getHookRunner()->onArticleConfirmDelete( $this, $outputPage, $reason );
2032 
2033  $user = $this->getContext()->getUser();
2034  $checkWatch = $user->getBoolOption( 'watchdeletion' ) || $user->isWatched( $title );
2035 
2036  $outputPage->enableOOUI();
2037 
2038  $fields = [];
2039 
2040  $suppressAllowed = $this->permManager->userHasRight( $user, 'suppressrevision' );
2041  $dropDownReason = $ctx->msg( 'deletereason-dropdown' )->inContentLanguage()->text();
2042  // Add additional specific reasons for suppress
2043  if ( $suppressAllowed ) {
2044  $dropDownReason .= "\n" . $ctx->msg( 'deletereason-dropdown-suppress' )
2045  ->inContentLanguage()->text();
2046  }
2047 
2048  $options = Xml::listDropDownOptions(
2049  $dropDownReason,
2050  [ 'other' => $ctx->msg( 'deletereasonotherlist' )->inContentLanguage()->text() ]
2051  );
2052  $options = Xml::listDropDownOptionsOoui( $options );
2053 
2054  $fields[] = new OOUI\FieldLayout(
2055  new OOUI\DropdownInputWidget( [
2056  'name' => 'wpDeleteReasonList',
2057  'inputId' => 'wpDeleteReasonList',
2058  'tabIndex' => 1,
2059  'infusable' => true,
2060  'value' => '',
2061  'options' => $options
2062  ] ),
2063  [
2064  'label' => $ctx->msg( 'deletecomment' )->text(),
2065  'align' => 'top',
2066  ]
2067  );
2068 
2069  // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
2070  // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
2071  // Unicode codepoints.
2072  $fields[] = new OOUI\FieldLayout(
2073  new OOUI\TextInputWidget( [
2074  'name' => 'wpReason',
2075  'inputId' => 'wpReason',
2076  'tabIndex' => 2,
2078  'infusable' => true,
2079  'value' => $reason,
2080  'autofocus' => true,
2081  ] ),
2082  [
2083  'label' => $ctx->msg( 'deleteotherreason' )->text(),
2084  'align' => 'top',
2085  ]
2086  );
2087 
2088  if ( $user->isLoggedIn() ) {
2089  $fields[] = new OOUI\FieldLayout(
2090  new OOUI\CheckboxInputWidget( [
2091  'name' => 'wpWatch',
2092  'inputId' => 'wpWatch',
2093  'tabIndex' => 3,
2094  'selected' => $checkWatch,
2095  ] ),
2096  [
2097  'label' => $ctx->msg( 'watchthis' )->text(),
2098  'align' => 'inline',
2099  'infusable' => true,
2100  ]
2101  );
2102  }
2103  if ( $suppressAllowed ) {
2104  $fields[] = new OOUI\FieldLayout(
2105  new OOUI\CheckboxInputWidget( [
2106  'name' => 'wpSuppress',
2107  'inputId' => 'wpSuppress',
2108  'tabIndex' => 4,
2109  ] ),
2110  [
2111  'label' => $ctx->msg( 'revdelete-suppress' )->text(),
2112  'align' => 'inline',
2113  'infusable' => true,
2114  ]
2115  );
2116  }
2117 
2118  $fields[] = new OOUI\FieldLayout(
2119  new OOUI\ButtonInputWidget( [
2120  'name' => 'wpConfirmB',
2121  'inputId' => 'wpConfirmB',
2122  'tabIndex' => 5,
2123  'value' => $ctx->msg( 'deletepage' )->text(),
2124  'label' => $ctx->msg( 'deletepage' )->text(),
2125  'flags' => [ 'primary', 'destructive' ],
2126  'type' => 'submit',
2127  ] ),
2128  [
2129  'align' => 'top',
2130  ]
2131  );
2132 
2133  $fieldset = new OOUI\FieldsetLayout( [
2134  'label' => $ctx->msg( 'delete-legend' )->text(),
2135  'id' => 'mw-delete-table',
2136  'items' => $fields,
2137  ] );
2138 
2139  $form = new OOUI\FormLayout( [
2140  'method' => 'post',
2141  'action' => $title->getLocalURL( 'action=delete' ),
2142  'id' => 'deleteconfirm',
2143  ] );
2144  $form->appendContent(
2145  $fieldset,
2146  new OOUI\HtmlSnippet(
2147  Html::hidden( 'wpEditToken', $user->getEditToken( [ 'delete', $title->getPrefixedText() ] ) )
2148  )
2149  );
2150 
2151  $outputPage->addHTML(
2152  new OOUI\PanelLayout( [
2153  'classes' => [ 'deletepage-wrapper' ],
2154  'expanded' => false,
2155  'padded' => true,
2156  'framed' => true,
2157  'content' => $form,
2158  ] )
2159  );
2160 
2161  if ( $this->permManager->userHasRight( $user, 'editinterface' ) ) {
2162  $link = '';
2163  if ( $suppressAllowed ) {
2164  $link .= $this->linkRenderer->makeKnownLink(
2165  $ctx->msg( 'deletereason-dropdown-suppress' )->inContentLanguage()->getTitle(),
2166  $ctx->msg( 'delete-edit-reasonlist-suppress' )->text(),
2167  [],
2168  [ 'action' => 'edit' ]
2169  );
2170  $link .= $ctx->msg( 'pipe-separator' )->escaped();
2171  }
2172  $link .= $this->linkRenderer->makeKnownLink(
2173  $ctx->msg( 'deletereason-dropdown' )->inContentLanguage()->getTitle(),
2174  $ctx->msg( 'delete-edit-reasonlist' )->text(),
2175  [],
2176  [ 'action' => 'edit' ]
2177  );
2178  $outputPage->addHTML( '<p class="mw-delete-editreasons">' . $link . '</p>' );
2179  }
2180 
2181  $deleteLogPage = new LogPage( 'delete' );
2182  $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
2183  LogEventsList::showLogExtract( $outputPage, 'delete', $title );
2184  }
2185 
2194  public function doDelete( $reason, $suppress = false, $immediate = false ) {
2195  $error = '';
2196  $context = $this->getContext();
2197  $outputPage = $context->getOutput();
2198  $user = $context->getUser();
2199  $status = $this->mPage->doDeleteArticleReal(
2200  $reason, $user, $suppress, null, $error,
2201  null, [], 'delete', $immediate
2202  );
2203 
2204  if ( $status->isOK() ) {
2205  $deleted = $this->getTitle()->getPrefixedText();
2206 
2207  $outputPage->setPageTitle( wfMessage( 'actioncomplete' ) );
2208  $outputPage->setRobotPolicy( 'noindex,nofollow' );
2209 
2210  if ( $status->isGood() ) {
2211  $loglink = '[[Special:Log/delete|' . wfMessage( 'deletionlog' )->text() . ']]';
2212  $outputPage->addWikiMsg( 'deletedtext', wfEscapeWikiText( $deleted ), $loglink );
2213  $this->getHookRunner()->onArticleDeleteAfterSuccess( $this->getTitle(), $outputPage );
2214  } else {
2215  $outputPage->addWikiMsg( 'delete-scheduled', wfEscapeWikiText( $deleted ) );
2216  }
2217 
2218  $outputPage->returnToMain( false );
2219  } else {
2220  $outputPage->setPageTitle(
2221  wfMessage( 'cannotdelete-title',
2222  $this->getTitle()->getPrefixedText() )
2223  );
2224 
2225  if ( $error == '' ) {
2226  $outputPage->wrapWikiTextAsInterface(
2227  'error mw-error-cannotdelete',
2228  $status->getWikiText( false, false, $context->getLanguage() )
2229  );
2230  $deleteLogPage = new LogPage( 'delete' );
2231  $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
2232 
2234  $outputPage,
2235  'delete',
2236  $this->getTitle()
2237  );
2238  } else {
2239  $outputPage->addHTML( $error );
2240  }
2241  }
2242  }
2243 
2244  /* Caching functions */
2245 
2253  protected function tryFileCache() {
2254  static $called = false;
2255 
2256  if ( $called ) {
2257  wfDebug( "Article::tryFileCache(): called twice!?" );
2258  return false;
2259  }
2260 
2261  $called = true;
2262  if ( $this->isFileCacheable() ) {
2263  $cache = new HTMLFileCache( $this->getTitle(), 'view' );
2264  if ( $cache->isCacheGood( $this->mPage->getTouched() ) ) {
2265  wfDebug( "Article::tryFileCache(): about to load file" );
2266  $cache->loadFromFileCache( $this->getContext() );
2267  return true;
2268  } else {
2269  wfDebug( "Article::tryFileCache(): starting buffer" );
2270  ob_start( [ &$cache, 'saveToFileCache' ] );
2271  }
2272  } else {
2273  wfDebug( "Article::tryFileCache(): not cacheable" );
2274  }
2275 
2276  return false;
2277  }
2278 
2284  public function isFileCacheable( $mode = HTMLFileCache::MODE_NORMAL ) {
2285  $cacheable = false;
2286 
2287  if ( HTMLFileCache::useFileCache( $this->getContext(), $mode ) ) {
2288  $cacheable = $this->mPage->getId()
2289  && !$this->mRedirectedFrom && !$this->getTitle()->isRedirect();
2290  // Extension may have reason to disable file caching on some pages.
2291  if ( $cacheable ) {
2292  $cacheable = $this->getHookRunner()->onIsFileCacheable( $this );
2293  }
2294  }
2295 
2296  return $cacheable;
2297  }
2298 
2312  public function getParserOutput( $oldid = null, User $user = null ) {
2313  // XXX: bypasses mParserOptions and thus setParserOptions()
2314 
2315  if ( $user === null ) {
2316  $parserOptions = $this->getParserOptions();
2317  } else {
2318  $parserOptions = $this->mPage->makeParserOptions( $user );
2319  }
2320 
2321  return $this->mPage->getParserOutput( $parserOptions, $oldid );
2322  }
2323 
2330  public function setParserOptions( ParserOptions $options ) {
2331  if ( $this->mParserOptions ) {
2332  throw new MWException( "can't change parser options after they have already been set" );
2333  }
2334 
2335  // clone, so if $options is modified later, it doesn't confuse the parser cache.
2336  $this->mParserOptions = clone $options;
2337  }
2338 
2343  public function getParserOptions() {
2344  if ( !$this->mParserOptions ) {
2345  $this->mParserOptions = $this->mPage->makeParserOptions( $this->getContext() );
2346  }
2347  // Clone to allow modifications of the return value without affecting cache
2348  return clone $this->mParserOptions;
2349  }
2350 
2357  public function setContext( $context ) {
2358  $this->mContext = $context;
2359  }
2360 
2367  public function getContext() {
2368  if ( $this->mContext instanceof IContextSource ) {
2369  return $this->mContext;
2370  } else {
2371  wfDebug( __METHOD__ . " called and \$mContext is null. " .
2372  "Return RequestContext::getMain(); for sanity" );
2373  return RequestContext::getMain();
2374  }
2375  }
2376 
2386  public function __get( $fname ) {
2387  wfDeprecatedMsg( "Accessing Article::\$$fname is deprecated since MediaWiki 1.35",
2388  '1.35' );
2389 
2390  if ( $fname === 'mRevision' ) {
2391  $record = $this->fetchRevisionRecord(); // Ensure that it is loaded
2392  return $record ? new Revision( $record ) : null;
2393  }
2394 
2395  if ( property_exists( $this->mPage, $fname ) ) {
2396  return $this->mPage->$fname;
2397  }
2398  trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
2399  }
2400 
2410  public function __set( $fname, $fvalue ) {
2411  wfDeprecatedMsg( "Setting Article::\$$fname is deprecated since MediaWiki 1.35",
2412  '1.35' );
2413 
2414  if ( $fname === 'mRevision' ) {
2415  $this->mRevisionRecord = $fvalue ?
2416  $fvalue->getRevisionRecord() :
2417  null;
2418  return;
2419  }
2420 
2421  if ( property_exists( $this->mPage, $fname ) ) {
2422  $this->mPage->$fname = $fvalue;
2423  // Note: extensions may want to toss on new fields
2424  } elseif ( !in_array( $fname, [ 'mContext', 'mPage' ] ) ) {
2425  $this->mPage->$fname = $fvalue;
2426  } else {
2427  trigger_error( 'Inaccessible property via __set(): ' . $fname, E_USER_NOTICE );
2428  }
2429  }
2430 
2442  public function checkFlags( $flags ) {
2443  wfDeprecated( __METHOD__, '1.35' );
2444  return $this->mPage->checkFlags( $flags );
2445  }
2446 
2452  public function checkTouched() {
2453  wfDeprecated( __METHOD__, '1.35' );
2454  return $this->mPage->checkTouched();
2455  }
2456 
2462  public function clearPreparedEdit() {
2463  wfDeprecated( __METHOD__, '1.35' );
2464  $this->mPage->clearPreparedEdit();
2465  }
2466 
2475  public function doDeleteUpdates(
2476  $id,
2477  Content $content = null,
2478  $revision = null,
2479  User $user = null
2480  ) {
2481  wfDeprecated( __METHOD__, '1.35' );
2482  $this->mPage->doDeleteUpdates( $id, $content, $revision, $user );
2483  }
2484 
2494  public function doEditUpdates( Revision $revision, User $user, array $options = [] ) {
2495  wfDeprecated( __METHOD__, '1.35' );
2496  $this->mPage->doEditUpdates( $revision, $user, $options );
2497  }
2498 
2506  public function doPurge() {
2507  wfDeprecated( __METHOD__, '1.35' );
2508  return $this->mPage->doPurge();
2509  }
2510 
2517  public function doViewUpdates( User $user, $oldid = 0 ) {
2518  wfDeprecated( __METHOD__, '1.35' );
2519  $this->mPage->doViewUpdates( $user, $oldid );
2520  }
2521 
2527  public function exists() {
2528  wfDeprecated( __METHOD__, '1.35' );
2529  return $this->mPage->exists();
2530  }
2531 
2537  public function followRedirect() {
2538  wfDeprecated( __METHOD__, '1.35' );
2539  return $this->mPage->followRedirect();
2540  }
2541 
2547  public function getActionOverrides() {
2548  return $this->mPage->getActionOverrides();
2549  }
2550 
2557  public function getAutoDeleteReason( &$hasHistory ) {
2558  wfDeprecated( __METHOD__, '1.35' );
2559  return $this->mPage->getAutoDeleteReason( $hasHistory );
2560  }
2561 
2567  public function getCategories() {
2568  wfDeprecated( __METHOD__, '1.35' );
2569  return $this->mPage->getCategories();
2570  }
2571 
2577  public function getContentHandler() {
2578  wfDeprecated( __METHOD__, '1.35' );
2579  return $this->mPage->getContentHandler();
2580  }
2581 
2587  public function getContentModel() {
2588  wfDeprecated( __METHOD__, '1.35' );
2589  return $this->mPage->getContentModel();
2590  }
2591 
2597  public function getContributors() {
2598  wfDeprecated( __METHOD__, '1.35' );
2599  return $this->mPage->getContributors();
2600  }
2601 
2608  public function getDeletionUpdates( Content $content = null ) {
2609  wfDeprecated( __METHOD__, '1.35' );
2610  return $this->mPage->getDeletionUpdates( $content );
2611  }
2612 
2618  public function getHiddenCategories() {
2619  wfDeprecated( __METHOD__, '1.35' );
2620  return $this->mPage->getHiddenCategories();
2621  }
2622 
2628  public function getId() {
2629  wfDeprecated( __METHOD__, '1.35' );
2630  return $this->mPage->getId();
2631  }
2632 
2638  public function getLatest() {
2639  wfDeprecated( __METHOD__, '1.35' );
2640  return $this->mPage->getLatest();
2641  }
2642 
2648  public function getLinksTimestamp() {
2649  wfDeprecated( __METHOD__, '1.35' );
2650  return $this->mPage->getLinksTimestamp();
2651  }
2652 
2658  public function getMinorEdit() {
2659  wfDeprecated( __METHOD__, '1.35' );
2660  return $this->mPage->getMinorEdit();
2661  }
2662 
2667  public function getOldestRevision() {
2668  wfDeprecated( __METHOD__, '1.35' );
2669  return $this->mPage->getOldestRevision();
2670  }
2671 
2677  public function getRedirectTarget() {
2678  wfDeprecated( __METHOD__, '1.35' );
2679  return $this->mPage->getRedirectTarget();
2680  }
2681 
2688  public function getRedirectURL( $rt ) {
2689  wfDeprecated( __METHOD__, '1.35' );
2690  return $this->mPage->getRedirectURL( $rt );
2691  }
2692 
2699  public function getRevision() {
2700  wfDeprecated( __METHOD__, '1.35' );
2701  return $this->mPage->getRevision();
2702  }
2703 
2709  public function getTimestamp() {
2710  wfDeprecated( __METHOD__, '1.35' );
2711  return $this->mPage->getTimestamp();
2712  }
2713 
2719  public function getTouched() {
2720  wfDeprecated( __METHOD__, '1.35' );
2721  return $this->mPage->getTouched();
2722  }
2723 
2732  public function getUndoContent( Revision $undo, Revision $undoafter = null ) {
2733  wfDeprecated( __METHOD__, '1.35' );
2734  return $this->mPage->getUndoContent( $undo, $undoafter );
2735  }
2736 
2742  public function hasViewableContent() {
2743  wfDeprecated( __METHOD__, '1.35' );
2744  return $this->mPage->hasViewableContent();
2745  }
2746 
2754  public function insertOn( $dbw, $pageId = null ) {
2755  wfDeprecated( __METHOD__, '1.35' );
2756  return $this->mPage->insertOn( $dbw, $pageId );
2757  }
2758 
2764  public function insertRedirect() {
2765  wfDeprecated( __METHOD__, '1.35' );
2766  return $this->mPage->insertRedirect();
2767  }
2768 
2776  public function insertRedirectEntry( Title $rt, $oldLatest = null ) {
2777  wfDeprecated( __METHOD__, '1.35' );
2778  return $this->mPage->insertRedirectEntry( $rt, $oldLatest );
2779  }
2780 
2787  public function isCountable( $editInfo = false ) {
2788  wfDeprecated( __METHOD__, '1.35' );
2789  return $this->mPage->isCountable( $editInfo );
2790  }
2791 
2797  public function isRedirect() {
2798  wfDeprecated( __METHOD__, '1.35' );
2799  return $this->mPage->isRedirect();
2800  }
2801 
2808  public function loadFromRow( $data, $from ) {
2809  wfDeprecated( __METHOD__, '1.35' );
2810  $this->mPage->loadFromRow( $data, $from );
2811  }
2812 
2818  public function loadPageData( $from = 'fromdb' ) {
2819  wfDeprecated( __METHOD__, '1.35' );
2820  $this->mPage->loadPageData( $from );
2821  }
2822 
2828  public function lockAndGetLatest() {
2829  wfDeprecated( __METHOD__, '1.35' );
2830  return $this->mPage->lockAndGetLatest();
2831  }
2832 
2839  public function makeParserOptions( $context ) {
2840  wfDeprecated( __METHOD__, '1.35' );
2841  return $this->mPage->makeParserOptions( $context );
2842  }
2843 
2852  public function pageDataFromId( $dbr, $id, $options = [] ) {
2853  wfDeprecated( __METHOD__, '1.35' );
2854  return $this->mPage->pageDataFromId( $dbr, $id, $options );
2855  }
2856 
2865  public function pageDataFromTitle( $dbr, $title, $options = [] ) {
2866  wfDeprecated( __METHOD__, '1.35' );
2867  return $this->mPage->pageDataFromTitle( $dbr, $title, $options );
2868  }
2869 
2883  public function prepareContentForEdit(
2884  Content $content, $revision = null, User $user = null,
2885  $serialFormat = null, $useCache = true
2886  ) {
2887  wfDeprecated( __METHOD__, '1.35' );
2888  return $this->mPage->prepareContentForEdit(
2889  $content, $revision, $user,
2890  $serialFormat, $useCache
2891  );
2892  }
2893 
2901  public function protectDescription( array $limit, array $expiry ) {
2902  wfDeprecated( __METHOD__, '1.35' );
2903  return $this->mPage->protectDescription( $limit, $expiry );
2904  }
2905 
2913  public function protectDescriptionLog( array $limit, array $expiry ) {
2914  wfDeprecated( __METHOD__, '1.35' );
2915  return $this->mPage->protectDescriptionLog( $limit, $expiry );
2916  }
2917 
2927  public function replaceSectionAtRev( $sectionId, Content $sectionContent,
2928  $sectionTitle = '', $baseRevId = null
2929  ) {
2930  wfDeprecated( __METHOD__, '1.35' );
2931  return $this->mPage->replaceSectionAtRev( $sectionId, $sectionContent,
2932  $sectionTitle, $baseRevId
2933  );
2934  }
2935 
2947  public function replaceSectionContent(
2948  $sectionId, Content $sectionContent, $sectionTitle = '', $edittime = null
2949  ) {
2950  wfDeprecated( __METHOD__, '1.35' );
2951  return $this->mPage->replaceSectionContent(
2952  $sectionId, $sectionContent, $sectionTitle, $edittime
2953  );
2954  }
2955 
2961  public function setTimestamp( $ts ) {
2962  wfDeprecated( __METHOD__, '1.35' );
2963  $this->mPage->setTimestamp( $ts );
2964  }
2965 
2973  public function shouldCheckParserCache( ParserOptions $parserOptions, $oldId ) {
2974  wfDeprecated( __METHOD__, '1.35' );
2975  return $this->mPage->shouldCheckParserCache( $parserOptions, $oldId );
2976  }
2977 
2983  public function supportsSections() {
2984  wfDeprecated( __METHOD__, '1.35' );
2985  return $this->mPage->supportsSections();
2986  }
2987 
2993  public function triggerOpportunisticLinksUpdate( ParserOutput $parserOutput ) {
2994  wfDeprecated( __METHOD__, '1.35' );
2995  $this->mPage->triggerOpportunisticLinksUpdate( $parserOutput );
2996  }
2997 
3005  public function updateCategoryCounts( array $added, array $deleted, $id = 0 ) {
3006  wfDeprecated( __METHOD__, '1.35' );
3007  $this->mPage->updateCategoryCounts( $added, $deleted, $id );
3008  }
3009 
3018  public function updateIfNewerOn( $dbw, $revision ) {
3019  wfDeprecated( __METHOD__, '1.35' );
3020  return $this->mPage->updateIfNewerOn( $dbw, $revision );
3021  }
3022 
3031  public function updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect = null ) {
3032  wfDeprecated( __METHOD__, '1.35' );
3033  return $this->mPage->updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect );
3034  }
3035 
3045  public function updateRevisionOn( $dbw, $revision, $lastRevision = null,
3046  $lastRevIsRedirect = null
3047  ) {
3048  wfDeprecated( __METHOD__, '1.35' );
3049  return $this->mPage->updateRevisionOn( $dbw, $revision, $lastRevision,
3050  $lastRevIsRedirect
3051  );
3052  }
3053 
3063  public function doUpdateRestrictions( array $limit, array $expiry, &$cascade,
3064  $reason, User $user
3065  ) {
3066  wfDeprecated( __METHOD__, '1.35' );
3067  return $this->mPage->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $user );
3068  }
3069 
3078  public function updateRestrictions( $limit = [], $reason = '',
3079  &$cascade = 0, $expiry = []
3080  ) {
3081  wfDeprecated( __METHOD__, '1.35' );
3082  return $this->mPage->doUpdateRestrictions(
3083  $limit,
3084  $expiry,
3085  $cascade,
3086  $reason,
3087  $this->getContext()->getUser()
3088  );
3089  }
3090 
3101  public function doRollback(
3102  $fromP,
3103  $summary,
3104  $token,
3105  $bot,
3106  &$resultDetails,
3107  User $user = null
3108  ) {
3109  wfDeprecated( __METHOD__, '1.35' );
3110  if ( !$user ) {
3111  $user = $this->getContext()->getUser();
3112  }
3113 
3114  return $this->mPage->doRollback( $fromP, $summary, $token, $bot, $resultDetails, $user );
3115  }
3116 
3127  public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser = null ) {
3128  wfDeprecated( __METHOD__, '1.35' );
3129  if ( !$guser ) {
3130  $guser = $this->getContext()->getUser();
3131  }
3132 
3133  return $this->mPage->commitRollback( $fromP, $summary, $bot, $resultDetails, $guser );
3134  }
3135 
3142  public function generateReason( &$hasHistory ) {
3143  wfDeprecated( __METHOD__, '1.35' );
3144  return $this->getPage()->getAutoDeleteReason( $hasHistory );
3145  }
3146 }
ReadOnlyError
Show an error when the wiki is locked/read-only and the user tries to do something that requires writ...
Definition: ReadOnlyError.php:29
$wgSend404Code
$wgSend404Code
Some web hosts attempt to rewrite all responses with a 404 (not found) status code,...
Definition: DefaultSettings.php:3661
Article\showMissingArticle
showMissingArticle()
Show the error text for a missing article.
Definition: Article.php:1407
Article\checkFlags
checkFlags( $flags)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2442
$wgCdnMaxageStale
$wgCdnMaxageStale
Cache timeout when delivering a stale ParserCache response due to PoolCounter contention.
Definition: DefaultSettings.php:2936
ParserOptions
Set options of the Parser.
Definition: ParserOptions.php:44
Article\$mContentObject
Content null $mContentObject
Content of the main slot of $this->mRevisionRecord.
Definition: Article.php:75
HTMLFileCache\useFileCache
static useFileCache(IContextSource $context, $mode=self::MODE_NORMAL)
Check if pages can be cached for this request/user.
Definition: HTMLFileCache.php:93
Article\$mContext
IContextSource null $mContext
The context this Article is executed in.
Definition: Article.php:56
Message\numParam
static numParam( $num)
Definition: Message.php:1035
Page
Interface for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
Definition: Page.php:29
Title\newFromText
static newFromText( $text, $defaultNamespace=NS_MAIN)
Create a new Title from text, such as what one would find in a link.
Definition: Title.php:328
Article\isRedirect
isRedirect()
Definition: Article.php:2797
Article\getCategories
getCategories()
Definition: Article.php:2567
Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:46
ParserOutput
Definition: ParserOutput.php:27
Article\formatRobotPolicy
static formatRobotPolicy( $policy)
Converts a String robot policy into an associative array, to allow merging of several policies using ...
Definition: Article.php:1090
StatusValue\newFatal
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:70
Article\$viewIsRenderAction
bool $viewIsRenderAction
Whether render() was called.
Definition: Article.php:127
Article\getRedirectTarget
getRedirectTarget()
Definition: Article.php:2677
Article\view
view()
This is the default action of the index.php entry point: just view the page of the given title.
Definition: Article.php:627
Xml\listDropDownOptionsOoui
static listDropDownOptionsOoui( $options)
Convert options for a drop-down box into a format accepted by OOUI\DropdownInputWidget etc.
Definition: Xml.php:588
Article\getContentModel
getContentModel()
Definition: Article.php:2587
PageArchive
Used to show archived pages and eventually restore them.
Definition: PageArchive.php:32
Article\getLinksTimestamp
getLinksTimestamp()
Definition: Article.php:2648
Article\getOldIDFromRequest
getOldIDFromRequest()
Sets $this->mRedirectUrl to a correct URL if the query parameters are incorrect.
Definition: Article.php:375
MediaWiki\MediaWikiServices
MediaWikiServices is the service locator for the application scope of MediaWiki.
Definition: MediaWikiServices.php:162
Article\showPatrolFooter
showPatrolFooter()
If patrol is possible, output a patrol UI box.
Definition: Article.php:1229
$lang
if(!isset( $args[0])) $lang
Definition: testCompression.php:37
RecentChange\newFromConds
static newFromConds( $conds, $fname=__METHOD__, $dbType=DB_REPLICA)
Find the first recent change matching some specific conditions.
Definition: RecentChange.php:220
Article\tryFileCache
tryFileCache()
checkLastModified returns true if it has taken care of all output to the client that is necessary for...
Definition: Article.php:2253
Article\getContentHandler
getContentHandler()
Definition: Article.php:2577
Article\makeFetchErrorContent
makeFetchErrorContent()
Returns a Content object representing any error in $this->fetchContent, or null if there is no such e...
Definition: Article.php:541
Article\clearPreparedEdit
clearPreparedEdit()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2462
Article\$revisionStore
RevisionStore $revisionStore
Definition: Article.php:142
HTMLFileCache
Page view caching in the file system.
Definition: HTMLFileCache.php:33
Revision\RevisionStore
Service for looking up page revisions.
Definition: RevisionStore.php:81
Article\lockAndGetLatest
lockAndGetLatest()
Definition: Article.php:2828
MediaWiki\Linker\LinkRenderer
Class that generates HTML links for pages.
Definition: LinkRenderer.php:41
getUser
getUser()
Article\supportsSections
supportsSections()
Definition: Article.php:2983
Article\getOldestRevision
getOldestRevision()
Definition: Article.php:2667
Article\getDeletionUpdates
getDeletionUpdates(Content $content=null)
Definition: Article.php:2608
Article\checkTouched
checkTouched()
Definition: Article.php:2452
Article\getRevision
getRevision()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2699
RC_LOG
const RC_LOG
Definition: Defines.php:133
CategoryPage
Special handling for category description pages, showing pages, subcategories and file that belong to...
Definition: CategoryPage.php:30
Revision\RevisionRecord\getTimestamp
getTimestamp()
MCR migration note: this replaces Revision::getTimestamp.
Definition: RevisionRecord.php:434
WikiPage
Class representing a MediaWiki article and history.
Definition: WikiPage.php:54
Linker\revComment
static revComment( $rev, $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:1605
Linker\getRevDeleteLink
static getRevDeleteLink(User $user, $rev, LinkTarget $title)
Get a revision-deletion link, or disabled link, or nothing, depending on user permissions & the setti...
Definition: Linker.php:2195
PoolWorkArticleView
Definition: PoolWorkArticleView.php:28
NS_FILE
const NS_FILE
Definition: Defines.php:75
wfReadOnly
wfReadOnly()
Check whether the wiki is in read-only mode.
Definition: GlobalFunctions.php:1126
User\newFromName
static newFromName( $name, $validate='valid')
Definition: User.php:545
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1220
Article\showDiffPage
showDiffPage()
Show a diff page according to current request variables.
Definition: Article.php:943
$wgArticleRobotPolicies
$wgArticleRobotPolicies
Robot policies per article.
Definition: DefaultSettings.php:8500
ImagePage
Class for viewing MediaWiki file description pages.
Definition: ImagePage.php:33
PermissionsError
Show an error when a user tries to do something they do not have the necessary permissions for.
Definition: PermissionsError.php:31
Article\confirmDelete
confirmDelete( $reason)
Output deletion confirmation dialog.
Definition: Article.php:2005
Article\updateCategoryCounts
updateCategoryCounts(array $added, array $deleted, $id=0)
Definition: Article.php:3005
Html\warningBox
static warningBox( $html, $className='')
Return a warning box.
Definition: Html.php:726
Article\adjustDisplayTitle
adjustDisplayTitle(ParserOutput $pOutput)
Adjust title for pages with displaytitle, -{T|}- or language conversion.
Definition: Article.php:928
Article\showNamespaceHeader
showNamespaceHeader()
Show a header specific to the namespace currently being viewed, like [[MediaWiki:Talkpagetext]].
Definition: Article.php:1193
Article\shouldCheckParserCache
shouldCheckParserCache(ParserOptions $parserOptions, $oldId)
Definition: Article.php:2973
$wgUseRCPatrol
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
Definition: DefaultSettings.php:7342
Xml\openElement
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:108
$wgUseNPPatrol
$wgUseNPPatrol
Use new page patrolling to check new pages on Special:Newpages.
Definition: DefaultSettings.php:7358
Article\$linkRenderer
LinkRenderer $linkRenderer
Definition: Article.php:132
ProtectionForm
Handles the page protection UI and backend.
Definition: ProtectionForm.php:33
Article\doDelete
doDelete( $reason, $suppress=false, $immediate=false)
Perform a deletion and output success or failure messages.
Definition: Article.php:2194
Article\protectDescriptionLog
protectDescriptionLog(array $limit, array $expiry)
Definition: Article.php:2913
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
Article\getRevisionRedirectTarget
getRevisionRedirectTarget(RevisionRecord $revision)
Definition: Article.php:916
Article\$mRedirectedFrom
Title null $mRedirectedFrom
Title from which we were redirected here, if any.
Definition: Article.php:93
$dbr
$dbr
Definition: testCompression.php:54
Status
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:44
Article\getSubstituteContent
getSubstituteContent()
Returns Content object to use when the page does not exist.
Definition: Article.php:322
Revision
Definition: Revision.php:40
Article\unprotect
unprotect()
action=unprotect handler (alias)
Definition: Article.php:1866
Article\insertRedirect
insertRedirect()
Definition: Article.php:2764
MediaWiki\Block\DatabaseBlock
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
Definition: DatabaseBlock.php:50
Article\getRedirectedFrom
getRedirectedFrom()
Get the page this view was redirected from.
Definition: Article.php:239
Article\fetchContentObject
fetchContentObject()
Get text content object Does NOT follow redirects.
Definition: Article.php:450
Article\clear
clear()
Clear the object.
Definition: Article.php:274
Article\updateRedirectOn
updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect=null)
Definition: Article.php:3031
Article\newFromWikiPage
static newFromWikiPage(WikiPage $page, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition: Article.php:228
wfDeprecatedMsg
wfDeprecatedMsg( $msg, $version=false, $component=false, $callerOffset=2)
Log a deprecation warning with arbitrary message text.
Definition: GlobalFunctions.php:1059
Article\getTouched
getTouched()
Definition: Article.php:2719
MWException
MediaWiki exception.
Definition: MWException.php:29
Article\getTitle
getTitle()
Get the title object of the article.
Definition: Article.php:257
Article\updateRevisionOn
updateRevisionOn( $dbw, $revision, $lastRevision=null, $lastRevIsRedirect=null)
Definition: Article.php:3045
Article\render
render()
Handle action=render.
Definition: Article.php:1847
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Logs a warning that $function is deprecated.
Definition: GlobalFunctions.php:1027
Article\$permManager
PermissionManager $permManager
Definition: Article.php:137
Article\$mParserOutput
ParserOutput null false $mParserOutput
The ParserOutput generated for viewing the page, initialized by view().
Definition: Article.php:120
Article\addHelpLink
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Definition: Article.php:1830
Article\exists
exists()
Definition: Article.php:2527
Article\setRedirectedFrom
setRedirectedFrom(Title $from)
Tell the page view functions that this view was redirected from another page on the wiki.
Definition: Article.php:248
$wgUseFileCache
$wgUseFileCache
This will cache static pages for non-logged-in users to reduce database traffic on public sites.
Definition: DefaultSettings.php:2791
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2475
Article\applyContentOverride
applyContentOverride(Content $override)
Applies a content override by constructing a fake Revision object and assigning it to mRevisionRecord...
Definition: Article.php:562
Article\__construct
__construct(Title $title, $oldId=null)
Definition: Article.php:161
Article\showDeletedRevisionHeader
showDeletedRevisionHeader()
If the revision requested for view is deleted, check permissions.
Definition: Article.php:1561
Article\isFileCacheable
isFileCacheable( $mode=HTMLFileCache::MODE_NORMAL)
Check if the page can be cached.
Definition: Article.php:2284
Article\doRollback
doRollback( $fromP, $summary, $token, $bot, &$resultDetails, User $user=null)
Definition: Article.php:3101
WikiPage\getTitle
getTitle()
Get the title object of the article.
Definition: WikiPage.php:284
LogPage
Class to simplify the use of log pages.
Definition: LogPage.php:37
Article\setOldSubtitle
setOldSubtitle( $oldid=0)
Generate the navigation links when browsing through an article revisions It shows the information as:...
Definition: Article.php:1610
Xml\element
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:41
ObjectCache\getInstance
static getInstance( $id)
Get a cached instance of the specified type of cache object.
Definition: ObjectCache.php:78
Article\getPage
getPage()
Get the WikiPage object of this instance.
Definition: Article.php:267
Article\getContext
getContext()
Gets the context this Article is executed in.
Definition: Article.php:2367
User\isIP
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:931
Article\$mParserOptions
ParserOptions null $mParserOptions
ParserOptions object for output of articles.
Definition: Article.php:65
Article\pageDataFromId
pageDataFromId( $dbr, $id, $options=[])
Definition: Article.php:2852
$title
$title
Definition: testCompression.php:38
Article\getRobotPolicy
getRobotPolicy( $action, ParserOutput $pOutput=null)
Get the robot policy to be used for the current view.
Definition: Article.php:1010
Article\insertOn
insertOn( $dbw, $pageId=null)
Definition: Article.php:2754
Linker\makeExternalLink
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition: Linker.php:846
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:591
Article\getRedirectURL
getRedirectURL( $rt)
Definition: Article.php:2688
LogEventsList\showLogExtract
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
Definition: LogEventsList.php:609
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
Article\newFromID
static newFromID( $id)
Constructor from a page id.
Definition: Article.php:184
Article\newPage
newPage(Title $title)
Definition: Article.php:175
NS_CATEGORY
const NS_CATEGORY
Definition: Defines.php:83
Article\getUndoContent
getUndoContent(Revision $undo, Revision $undoafter=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2732
Article\prepareContentForEdit
prepareContentForEdit(Content $content, $revision=null, User $user=null, $serialFormat=null, $useCache=true)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2883
$wgDeleteRevisionsLimit
$wgDeleteRevisionsLimit
Optional to restrict deletion of pages with higher revision counts to users with the 'bigdelete' perm...
Definition: DefaultSettings.php:5957
Linker\revUserTools
static revUserTools( $rev, $isPublic=false, $useParentheses=true)
Generate a user tool link cluster if the current user is allowed to view it.
Definition: Linker.php:1142
Article\$mRevIdFetched
int $mRevIdFetched
Revision ID of revision that was loaded.
Definition: Article.php:103
$wgUseFilePatrol
$wgUseFilePatrol
Use file patrolling to check new files on Special:Newfiles.
Definition: DefaultSettings.php:7369
Article\getLatest
getLatest()
Definition: Article.php:2638
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:910
Article\isCurrent
isCurrent()
Returns true if the currently-referenced revision is the current edit to this page (and it exists).
Definition: Article.php:578
ContentHandler\makeContent
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
Definition: ContentHandler.php:142
Article\getAutoDeleteReason
getAutoDeleteReason(&$hasHistory)
Definition: Article.php:2557
Article\$mContentLoaded
bool $mContentLoaded
Is the target revision loaded? Set by fetchRevisionRecord().
Definition: Article.php:83
Article\setParserOptions
setParserOptions(ParserOptions $options)
Override the ParserOptions used to render the primary article wikitext.
Definition: Article.php:2330
Article\isCountable
isCountable( $editInfo=false)
Definition: Article.php:2787
Revision\RevisionRecord\getId
getId()
Get revision ID.
Definition: RevisionRecord.php:271
Html\hidden
static hidden( $name, $value, array $attribs=[])
Convenience function to produce an input element with type=hidden.
Definition: Html.php:802
ParserOutput\getTitleText
getTitleText()
Definition: ParserOutput.php:592
Article\doPurge
doPurge()
Definition: Article.php:2506
MediaWiki\Permissions\PermissionManager
A service class for checking permissions To obtain an instance, use MediaWikiServices::getInstance()-...
Definition: PermissionManager.php:49
$content
$content
Definition: router.php:76
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:72
Article\hasViewableContent
hasViewableContent()
Definition: Article.php:2742
Article\$mOldId
int null $mOldId
The oldid of the article that was requested to be shown, 0 for the current revision.
Definition: Article.php:90
NS_MEDIA
const NS_MEDIA
Definition: Defines.php:57
Article\getContributors
getContributors()
Definition: Article.php:2597
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:82
Revision\MutableRevisionRecord
Definition: MutableRevisionRecord.php:45
HTMLFileCache\MODE_NORMAL
const MODE_NORMAL
Definition: HTMLFileCache.php:34
Article\loadFromRow
loadFromRow( $data, $from)
Definition: Article.php:2808
Article\getEmptyPageParserOutput
getEmptyPageParserOutput(ParserOptions $options)
Returns ParserOutput to use when a page does not exist.
Definition: Article.php:349
Article\protect
protect()
action=protect handler
Definition: Article.php:1858
Hooks\runner
static runner()
Get a HookRunner instance for calling hooks using the new interfaces.
Definition: Hooks.php:172
Article\getTimestamp
getTimestamp()
Definition: Article.php:2709
ParserOptions\newCanonical
static newCanonical( $context=null, $userLang=null)
Creates a "canonical" ParserOptions object.
Definition: ParserOptions.php:1124
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:1494
Article\getMinorEdit
getMinorEdit()
Definition: Article.php:2658
Article\commitRollback
commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser=null)
Definition: Article.php:3127
$wgNamespaceRobotPolicies
$wgNamespaceRobotPolicies
Robot policies per namespaces.
Definition: DefaultSettings.php:8472
Article\getParserOutput
getParserOutput( $oldid=null, User $user=null)
#-
Definition: Article.php:2312
Article\getContentObject
getContentObject()
Returns a Content object representing the pages effective display content, not necessarily the revisi...
Definition: Article.php:306
Article\followRedirect
followRedirect()
Definition: Article.php:2537
Article\getHiddenCategories
getHiddenCategories()
Definition: Article.php:2618
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:454
WatchAction\doWatchOrUnwatch
static doWatchOrUnwatch( $watch, Title $title, User $user, string $expiry=null)
Watch or unwatch a page.
Definition: WatchAction.php:208
Article\setTimestamp
setTimestamp( $ts)
Definition: Article.php:2961
IContextSource
Interface for objects which can provide a MediaWiki context on request.
Definition: IContextSource.php:55
Article\__get
__get( $fname)
Definition: Article.php:2386
Content
Base interface for content objects.
Definition: Content.php:35
CommentStore\COMMENT_CHARACTER_LIMIT
const COMMENT_CHARACTER_LIMIT
Maximum length of a comment in UTF-8 characters.
Definition: CommentStore.php:48
Article\replaceSectionAtRev
replaceSectionAtRev( $sectionId, Content $sectionContent, $sectionTitle='', $baseRevId=null)
Definition: Article.php:2927
Article\fetchRevisionRecord
fetchRevisionRecord()
Fetches the revision to work on.
Definition: Article.php:469
Title
Represents a title within MediaWiki.
Definition: Title.php:41
$cache
$cache
Definition: mcc.php:33
Article\replaceSectionContent
replaceSectionContent( $sectionId, Content $sectionContent, $sectionTitle='', $edittime=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2947
MediaWiki\Linker\LinkRenderer\makeKnownLink
makeKnownLink(LinkTarget $target, $text=null, array $extraAttribs=[], array $query=[])
Definition: LinkRenderer.php:225
Article\getRevIdFetched
getRevIdFetched()
Use this to fetch the rev ID used on page views.
Definition: Article.php:615
MessageContent
Wrapper allowing us to handle a system message as a Content object.
Definition: MessageContent.php:36
Article\makeParserOptions
makeParserOptions( $context)
Definition: Article.php:2839
Article\showRedirectedFromHeader
showRedirectedFromHeader()
If this request is a redirect view, send "redirected from" subtitle to the output.
Definition: Article.php:1119
RecentChange\isInRCLifespan
static isInRCLifespan( $timestamp, $tolerance=0)
Check whether the given timestamp is new enough to have a RC row with a given tolerance as the recent...
Definition: RecentChange.php:1160
Revision\RevisionRecord\getContent
getContent( $role, $audience=self::FOR_PUBLIC, User $user=null)
Returns the Content of the given slot of this revision.
Definition: RevisionRecord.php:159
NS_USER
const NS_USER
Definition: Defines.php:71
Article\getId
getId()
Definition: Article.php:2628
Article\getActionOverrides
getActionOverrides()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2547
Article\$mRedirectUrl
string bool $mRedirectUrl
URL to redirect to or false if none.
Definition: Article.php:96
Article\doUpdateRestrictions
doUpdateRestrictions(array $limit, array $expiry, &$cascade, $reason, User $user)
Definition: Article.php:3063
Article\updateRestrictions
updateRestrictions( $limit=[], $reason='', &$cascade=0, $expiry=[])
Definition: Article.php:3078
Article\protectDescription
protectDescription(array $limit, array $expiry)
Definition: Article.php:2901
Article\getParserOptions
getParserOptions()
Get parser options suitable for rendering the primary article wikitext.
Definition: Article.php:2343
MediaWiki\Edit\PreparedEdit
Represents information returned by WikiPage::prepareContentForEdit()
Definition: PreparedEdit.php:35
$wgRedirectSources
$wgRedirectSources
If local interwikis are set up which allow redirects, set this regexp to restrict URLs which will be ...
Definition: DefaultSettings.php:4414
Article\updateIfNewerOn
updateIfNewerOn( $dbw, $revision)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:3018
NS_MEDIAWIKI
const NS_MEDIAWIKI
Definition: Defines.php:77
$t
$t
Definition: testCompression.php:74
Article
Class for viewing MediaWiki article and history.
Definition: Article.php:47
Article\doDeleteUpdates
doDeleteUpdates( $id, Content $content=null, $revision=null, User $user=null)
Definition: Article.php:2475
Article\pageDataFromTitle
pageDataFromTitle( $dbr, $title, $options=[])
Definition: Article.php:2865
Article\triggerOpportunisticLinksUpdate
triggerOpportunisticLinksUpdate(ParserOutput $parserOutput)
Definition: Article.php:2993
Article\getOldID
getOldID()
Definition: Article.php:362
EditPage\POST_EDIT_COOKIE_KEY_PREFIX
const POST_EDIT_COOKIE_KEY_PREFIX
Prefix of key for cookie used to pass post-edit state.
Definition: EditPage.php:80
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:56
Article\setContext
setContext( $context)
Sets the context this Article is executed in.
Definition: Article.php:2357
Title\newFromID
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:472
Article\insertRedirectEntry
insertRedirectEntry(Title $rt, $oldLatest=null)
Definition: Article.php:2776
MediaWiki\Linker\LinkRenderer\makeLink
makeLink(LinkTarget $target, $text=null, array $extraAttribs=[], array $query=[])
Definition: LinkRenderer.php:160
$wgDefaultRobotPolicy
$wgDefaultRobotPolicy
Default robot policy.
Definition: DefaultSettings.php:8456
Article\getRevisionFetched
getRevisionFetched()
Get the fetched Revision object depending on request parameters or null on failure.
Definition: Article.php:600
Article\viewRedirect
viewRedirect( $target, $appendSubtitle=true, $forceKnown=false)
Return the HTML for the top of a redirect page.
Definition: Article.php:1761
Article\loadPageData
loadPageData( $from='fromdb')
Definition: Article.php:2818
Language
Internationalisation code See https://www.mediawiki.org/wiki/Special:MyLanguage/Localisation for more...
Definition: Language.php:41
Article\doEditUpdates
doEditUpdates(Revision $revision, User $user, array $options=[])
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2494
Article\$mRevisionRecord
RevisionRecord null $mRevisionRecord
Revision to be shown.
Definition: Article.php:155
Article\__set
__set( $fname, $fvalue)
Definition: Article.php:2410
Article\showViewFooter
showViewFooter()
Show the footer section of an ordinary page view.
Definition: Article.php:1205
Revision\SlotRecord
Value object representing a content slot associated with a page revision.
Definition: SlotRecord.php:40
Article\$mPage
WikiPage $mPage
The WikiPage object of this instance.
Definition: Article.php:59
Article\getRedirectHeaderHtml
static getRedirectHeaderHtml(Language $lang, $target, $forceKnown=false)
Return the HTML for the top of a redirect page.
Definition: Article.php:1783
Xml\listDropDownOptions
static listDropDownOptions( $list, $params=[])
Build options for a drop-down box from a textual list.
Definition: Xml.php:543
Article\newFromTitle
static newFromTitle( $title, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition: Article.php:196
Article\generateReason
generateReason(&$hasHistory)
Definition: Article.php:3142
Article\purgePatrolFooterCache
static purgePatrolFooterCache( $articleID)
Purge the cache used to check if it is worth showing the patrol footer For example,...
Definition: Article.php:1398
Article\doViewUpdates
doViewUpdates(User $user, $oldid=0)
Definition: Article.php:2517
Article\$fetchResult
Status null $fetchResult
represents the outcome of fetchRevisionRecord().
Definition: Article.php:113