MediaWiki  master
Article.php
Go to the documentation of this file.
1 <?php
27 
38 class Article implements Page {
43  protected $mContext;
44 
46  protected $mPage;
47 
53 
63 
70  public $mContentLoaded = false;
71 
77  public $mOldId;
78 
81 
83  public $mRedirectUrl = false;
84 
90  public $mRevIdFetched = 0;
91 
100  private $fetchResult = null;
101 
109  public $mRevision = null;
110 
117 
123  protected $viewIsRenderAction = false;
124 
130  public function __construct( Title $title, $oldId = null ) {
131  $this->mOldId = $oldId;
132  $this->mPage = $this->newPage( $title );
133  }
134 
139  protected function newPage( Title $title ) {
140  return new WikiPage( $title );
141  }
142 
148  public static function newFromID( $id ) {
149  $t = Title::newFromID( $id );
150  return $t == null ? null : new static( $t );
151  }
152 
160  public static function newFromTitle( $title, IContextSource $context ) {
161  if ( NS_MEDIA == $title->getNamespace() ) {
162  // XXX: This should not be here, but where should it go?
163  $title = Title::makeTitle( NS_FILE, $title->getDBkey() );
164  }
165 
166  $page = null;
167  Hooks::run( 'ArticleFromTitle', [ &$title, &$page, $context ] );
168  if ( !$page ) {
169  switch ( $title->getNamespace() ) {
170  case NS_FILE:
171  $page = new ImagePage( $title );
172  break;
173  case NS_CATEGORY:
174  $page = new CategoryPage( $title );
175  break;
176  default:
177  $page = new Article( $title );
178  }
179  }
180  $page->setContext( $context );
181 
182  return $page;
183  }
184 
192  public static function newFromWikiPage( WikiPage $page, IContextSource $context ) {
193  $article = self::newFromTitle( $page->getTitle(), $context );
194  $article->mPage = $page; // override to keep process cached vars
195  return $article;
196  }
197 
203  public function getRedirectedFrom() {
204  return $this->mRedirectedFrom;
205  }
206 
212  public function setRedirectedFrom( Title $from ) {
213  $this->mRedirectedFrom = $from;
214  }
215 
221  public function getTitle() {
222  return $this->mPage->getTitle();
223  }
224 
231  public function getPage() {
232  return $this->mPage;
233  }
234 
238  public function clear() {
239  $this->mContentLoaded = false;
240 
241  $this->mRedirectedFrom = null; # Title object if set
242  $this->mRevIdFetched = 0;
243  $this->mRedirectUrl = false;
244  $this->mRevision = null;
245  $this->mContentObject = null;
246  $this->fetchResult = null;
247 
248  // TODO hard-deprecate direct access to public fields
249 
250  $this->mPage->clear();
251  }
252 
270  protected function getContentObject() {
271  if ( $this->mPage->getId() === 0 ) {
272  $content = $this->getSubstituteContent();
273  } else {
274  $this->fetchContentObject();
276  }
277 
278  return $content;
279  }
280 
286  private function getSubstituteContent() {
287  # If this is a MediaWiki:x message, then load the messages
288  # and return the message value for x.
289  if ( $this->getTitle()->getNamespace() == NS_MEDIAWIKI ) {
290  $text = $this->getTitle()->getDefaultMessageText();
291  if ( $text === false ) {
292  $text = '';
293  }
294 
295  $content = ContentHandler::makeContent( $text, $this->getTitle() );
296  } else {
297  $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
298  $content = new MessageContent( $message, null );
299  }
300 
301  return $content;
302  }
303 
314  $content = $this->getSubstituteContent();
315 
316  return $content->getParserOutput( $this->getTitle(), 0, $options );
317  }
318 
326  public function getOldID() {
327  if ( is_null( $this->mOldId ) ) {
328  $this->mOldId = $this->getOldIDFromRequest();
329  }
330 
331  return $this->mOldId;
332  }
333 
339  public function getOldIDFromRequest() {
340  $this->mRedirectUrl = false;
341 
342  $request = $this->getContext()->getRequest();
343  $oldid = $request->getIntOrNull( 'oldid' );
344 
345  if ( $oldid === null ) {
346  return 0;
347  }
348 
349  if ( $oldid !== 0 ) {
350  # Load the given revision and check whether the page is another one.
351  # In that case, update this instance to reflect the change.
352  if ( $oldid === $this->mPage->getLatest() ) {
353  $this->mRevision = $this->mPage->getRevision();
354  } else {
355  $this->mRevision = Revision::newFromId( $oldid );
356  if ( $this->mRevision !== null ) {
357  // Revision title doesn't match the page title given?
358  if ( $this->mPage->getId() != $this->mRevision->getPage() ) {
359  $function = get_class( $this->mPage ) . '::newFromID';
360  $this->mPage = $function( $this->mRevision->getPage() );
361  }
362  }
363  }
364  }
365 
366  if ( $request->getVal( 'direction' ) == 'next' ) {
367  $nextid = $this->getTitle()->getNextRevisionID( $oldid );
368  if ( $nextid ) {
369  $oldid = $nextid;
370  $this->mRevision = null;
371  } else {
372  $this->mRedirectUrl = $this->getTitle()->getFullURL( 'redirect=no' );
373  }
374  } elseif ( $request->getVal( 'direction' ) == 'prev' ) {
375  $previd = $this->getTitle()->getPreviousRevisionID( $oldid );
376  if ( $previd ) {
377  $oldid = $previd;
378  $this->mRevision = null;
379  }
380  }
381 
382  $this->mRevIdFetched = $this->mRevision ? $this->mRevision->getId() : 0;
383 
384  return $oldid;
385  }
386 
400  protected function fetchContentObject() {
401  if ( !$this->mContentLoaded ) {
402  $this->fetchRevisionRecord();
403  }
404 
405  return $this->mContentObject;
406  }
407 
417  protected function fetchRevisionRecord() {
418  if ( $this->fetchResult ) {
419  return $this->mRevision ? $this->mRevision->getRevisionRecord() : null;
420  }
421 
422  $this->mContentLoaded = true;
423  $this->mContentObject = null;
424 
425  $oldid = $this->getOldID();
426 
427  // $this->mRevision might already be fetched by getOldIDFromRequest()
428  if ( !$this->mRevision ) {
429  if ( !$oldid ) {
430  $this->mRevision = $this->mPage->getRevision();
431 
432  if ( !$this->mRevision ) {
433  wfDebug( __METHOD__ . " failed to find page data for title " .
434  $this->getTitle()->getPrefixedText() . "\n" );
435 
436  // Just for sanity, output for this case is done by showMissingArticle().
437  $this->fetchResult = Status::newFatal( 'noarticletext' );
438  $this->applyContentOverride( $this->makeFetchErrorContent() );
439  return null;
440  }
441  } else {
442  $this->mRevision = Revision::newFromId( $oldid );
443 
444  if ( !$this->mRevision ) {
445  wfDebug( __METHOD__ . " failed to load revision, rev_id $oldid\n" );
446 
447  $this->fetchResult = Status::newFatal( 'missing-revision', $oldid );
448  $this->applyContentOverride( $this->makeFetchErrorContent() );
449  return null;
450  }
451  }
452  }
453 
454  $this->mRevIdFetched = $this->mRevision->getId();
455  $this->fetchResult = Status::newGood( $this->mRevision );
456 
457  if (
458  !$this->mRevision->userCan( RevisionRecord::DELETED_TEXT, $this->getContext()->getUser() )
459  ) {
460  wfDebug( __METHOD__ . " failed to retrieve content of revision " .
461  $this->mRevision->getId() . "\n" );
462 
463  // Just for sanity, output for this case is done by showDeletedRevisionHeader().
464  $this->fetchResult = Status::newFatal( 'rev-deleted-text-permission' );
465  $this->applyContentOverride( $this->makeFetchErrorContent() );
466  return null;
467  }
468 
469  if ( Hooks::isRegistered( 'ArticleAfterFetchContentObject' ) ) {
470  $contentObject = $this->mRevision->getContent(
471  RevisionRecord::FOR_THIS_USER,
472  $this->getContext()->getUser()
473  );
474 
475  $hookContentObject = $contentObject;
476 
477  // Avoid PHP 7.1 warning of passing $this by reference
478  $articlePage = $this;
479 
480  Hooks::run(
481  'ArticleAfterFetchContentObject',
482  [ &$articlePage, &$hookContentObject ],
483  '1.32'
484  );
485 
486  if ( $hookContentObject !== $contentObject ) {
487  // A hook handler is trying to override the content
488  $this->applyContentOverride( $hookContentObject );
489  }
490  }
491 
492  // For B/C only
493  $this->mContentObject = $this->mRevision->getContent(
494  RevisionRecord::FOR_THIS_USER,
495  $this->getContext()->getUser()
496  );
497 
498  return $this->mRevision->getRevisionRecord();
499  }
500 
507  private function makeFetchErrorContent() {
508  if ( !$this->fetchResult || $this->fetchResult->isOK() ) {
509  return null;
510  }
511 
512  return new MessageContent( $this->fetchResult->getMessage() );
513  }
514 
527  private function applyContentOverride( Content $override ) {
528  // Construct a fake revision
529  $rev = new MutableRevisionRecord( $this->getTitle() );
530  $rev->setContent( SlotRecord::MAIN, $override );
531 
532  $this->mRevision = new Revision( $rev );
533 
534  // For B/C only
535  $this->mContentObject = $override;
536  }
537 
543  public function isCurrent() {
544  # If no oldid, this is the current version.
545  if ( $this->getOldID() == 0 ) {
546  return true;
547  }
548 
549  return $this->mPage->exists() && $this->mRevision && $this->mRevision->isCurrent();
550  }
551 
561  public function getRevisionFetched() {
562  $this->fetchRevisionRecord();
563 
564  if ( $this->fetchResult->isOK() ) {
565  return $this->mRevision;
566  }
567  }
568 
577  public function getRevIdFetched() {
578  if ( $this->fetchResult && $this->fetchResult->isOK() ) {
579  return $this->fetchResult->value->getId();
580  } else {
581  return $this->mPage->getLatest();
582  }
583  }
584 
589  public function view() {
591 
592  # Get variables from query string
593  # As side effect this will load the revision and update the title
594  # in a revision ID is passed in the request, so this should remain
595  # the first call of this method even if $oldid is used way below.
596  $oldid = $this->getOldID();
597 
598  $user = $this->getContext()->getUser();
599  # Another whitelist check in case getOldID() is altering the title
600  $permErrors = $this->getTitle()->getUserPermissionsErrors( 'read', $user );
601  if ( count( $permErrors ) ) {
602  wfDebug( __METHOD__ . ": denied on secondary read check\n" );
603  throw new PermissionsError( 'read', $permErrors );
604  }
605 
606  $outputPage = $this->getContext()->getOutput();
607  # getOldID() may as well want us to redirect somewhere else
608  if ( $this->mRedirectUrl ) {
609  $outputPage->redirect( $this->mRedirectUrl );
610  wfDebug( __METHOD__ . ": redirecting due to oldid\n" );
611 
612  return;
613  }
614 
615  # If we got diff in the query, we want to see a diff page instead of the article.
616  if ( $this->getContext()->getRequest()->getCheck( 'diff' ) ) {
617  wfDebug( __METHOD__ . ": showing diff page\n" );
618  $this->showDiffPage();
619 
620  return;
621  }
622 
623  # Set page title (may be overridden by DISPLAYTITLE)
624  $outputPage->setPageTitle( $this->getTitle()->getPrefixedText() );
625 
626  $outputPage->setArticleFlag( true );
627  # Allow frames by default
628  $outputPage->allowClickjacking();
629 
630  $parserCache = MediaWikiServices::getInstance()->getParserCache();
631 
632  $parserOptions = $this->getParserOptions();
633  $poOptions = [];
634  # Render printable version, use printable version cache
635  if ( $outputPage->isPrintable() ) {
636  $parserOptions->setIsPrintable( true );
637  $poOptions['enableSectionEditLinks'] = false;
638  } elseif ( $this->viewIsRenderAction
639  || !$this->isCurrent() || !$this->getTitle()->quickUserCan( 'edit', $user )
640  ) {
641  $poOptions['enableSectionEditLinks'] = false;
642  }
643 
644  # Try client and file cache
645  if ( !$wgDebugToolbar && $oldid === 0 && $this->mPage->checkTouched() ) {
646  # Try to stream the output from file cache
647  if ( $wgUseFileCache && $this->tryFileCache() ) {
648  wfDebug( __METHOD__ . ": done file cache\n" );
649  # tell wgOut that output is taken care of
650  $outputPage->disable();
651  $this->mPage->doViewUpdates( $user, $oldid );
652 
653  return;
654  }
655  }
656 
657  # Should the parser cache be used?
658  $useParserCache = $this->mPage->shouldCheckParserCache( $parserOptions, $oldid );
659  wfDebug( 'Article::view using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
660  if ( $user->getStubThreshold() ) {
661  MediaWikiServices::getInstance()->getStatsdDataFactory()->increment( 'pcache_miss_stub' );
662  }
663 
664  $this->showRedirectedFromHeader();
665  $this->showNamespaceHeader();
666 
667  # Iterate through the possible ways of constructing the output text.
668  # Keep going until $outputDone is set, or we run out of things to do.
669  $pass = 0;
670  $outputDone = false;
671  $this->mParserOutput = false;
672 
673  while ( !$outputDone && ++$pass ) {
674  switch ( $pass ) {
675  case 1:
676  // Avoid PHP 7.1 warning of passing $this by reference
677  $articlePage = $this;
678  Hooks::run( 'ArticleViewHeader', [ &$articlePage, &$outputDone, &$useParserCache ] );
679  break;
680  case 2:
681  # Early abort if the page doesn't exist
682  if ( !$this->mPage->exists() ) {
683  wfDebug( __METHOD__ . ": showing missing article\n" );
684  $this->showMissingArticle();
685  $this->mPage->doViewUpdates( $user );
686  return;
687  }
688 
689  # Try the parser cache
690  if ( $useParserCache ) {
691  $this->mParserOutput = $parserCache->get( $this->mPage, $parserOptions );
692 
693  if ( $this->mParserOutput !== false ) {
694  if ( $oldid ) {
695  wfDebug( __METHOD__ . ": showing parser cache contents for current rev permalink\n" );
696  $this->setOldSubtitle( $oldid );
697  } else {
698  wfDebug( __METHOD__ . ": showing parser cache contents\n" );
699  }
700  $outputPage->addParserOutput( $this->mParserOutput, $poOptions );
701  # Ensure that UI elements requiring revision ID have
702  # the correct version information.
703  $outputPage->setRevisionId( $this->mPage->getLatest() );
704  # Preload timestamp to avoid a DB hit
705  $cachedTimestamp = $this->mParserOutput->getTimestamp();
706  if ( $cachedTimestamp !== null ) {
707  $outputPage->setRevisionTimestamp( $cachedTimestamp );
708  $this->mPage->setTimestamp( $cachedTimestamp );
709  }
710  $outputDone = true;
711  }
712  }
713  break;
714  case 3:
715  # Are we looking at an old revision
716  $rev = $this->fetchRevisionRecord();
717  if ( $oldid && $this->fetchResult->isOK() ) {
718  $this->setOldSubtitle( $oldid );
719 
720  if ( !$this->showDeletedRevisionHeader() ) {
721  wfDebug( __METHOD__ . ": cannot view deleted revision\n" );
722  return;
723  }
724  }
725 
726  # Ensure that UI elements requiring revision ID have
727  # the correct version information.
728  $outputPage->setRevisionId( $this->getRevIdFetched() );
729  # Preload timestamp to avoid a DB hit
730  $outputPage->setRevisionTimestamp( $this->mPage->getTimestamp() );
731 
732  # Pages containing custom CSS or JavaScript get special treatment
733  if ( $this->getTitle()->isSiteConfigPage() || $this->getTitle()->isUserConfigPage() ) {
734  $dir = $this->getContext()->getLanguage()->getDir();
735  $lang = $this->getContext()->getLanguage()->getHtmlCode();
736 
737  $outputPage->wrapWikiMsg(
738  "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
739  'clearyourcache'
740  );
741  } elseif ( !Hooks::run( 'ArticleRevisionViewCustom', [
742  $rev,
743  $this->getTitle(),
744  $oldid,
745  $outputPage,
746  ] )
747  ) {
748  // NOTE: sync with hooks called in DifferenceEngine::renderNewRevision()
749  // Allow extensions do their own custom view for certain pages
750  $outputDone = true;
751  } elseif ( !Hooks::run( 'ArticleContentViewCustom',
752  [ $this->fetchContentObject(), $this->getTitle(), $outputPage ], '1.32' )
753  ) {
754  // NOTE: sync with hooks called in DifferenceEngine::renderNewRevision()
755  // Allow extensions do their own custom view for certain pages
756  $outputDone = true;
757  }
758  break;
759  case 4:
760  # Run the parse, protected by a pool counter
761  wfDebug( __METHOD__ . ": doing uncached parse\n" );
762 
763  $rev = $this->fetchRevisionRecord();
764  $error = null;
765 
766  if ( $rev ) {
767  $poolArticleView = new PoolWorkArticleView(
768  $this->getPage(),
769  $parserOptions,
770  $this->getRevIdFetched(),
771  $useParserCache,
772  $rev,
773  // permission checking was done earlier via showDeletedRevisionHeader()
774  RevisionRecord::RAW
775  );
776  $ok = $poolArticleView->execute();
777  $error = $poolArticleView->getError();
778  $this->mParserOutput = $poolArticleView->getParserOutput() ?: null;
779 
780  # Don't cache a dirty ParserOutput object
781  if ( $poolArticleView->getIsDirty() ) {
782  $outputPage->setCdnMaxage( 0 );
783  $outputPage->addHTML( "<!-- parser cache is expired, " .
784  "sending anyway due to pool overload-->\n" );
785  }
786  } else {
787  $ok = false;
788  }
789 
790  if ( !$ok ) {
791  if ( $error ) {
792  $outputPage->clearHTML(); // for release() errors
793  $outputPage->enableClientCache( false );
794  $outputPage->setRobotPolicy( 'noindex,nofollow' );
795 
796  $errortext = $error->getWikiText( false, 'view-pool-error' );
797  $outputPage->wrapWikiTextAsInterface( 'errorbox', $errortext );
798  }
799  # Connection or timeout error
800  return;
801  }
802 
803  if ( $this->mParserOutput ) {
804  $outputPage->addParserOutput( $this->mParserOutput, $poOptions );
805  }
806 
807  if ( $rev && $this->getRevisionRedirectTarget( $rev ) ) {
808  $outputPage->addSubtitle( "<span id=\"redirectsub\">" .
809  $this->getContext()->msg( 'redirectpagesub' )->parse() . "</span>" );
810  }
811 
812  $outputDone = true;
813  break;
814  # Should be unreachable, but just in case...
815  default:
816  break 2;
817  }
818  }
819 
820  // Get the ParserOutput actually *displayed* here.
821  // Note that $this->mParserOutput is the *current*/oldid version output.
822  // Note that the ArticleViewHeader hook is allowed to set $outputDone to a
823  // ParserOutput instance.
824  $pOutput = ( $outputDone instanceof ParserOutput )
825  ? $outputDone // object fetched by hook
826  : ( $this->mParserOutput ?: null ); // ParserOutput or null, avoid false
827 
828  # Adjust title for main page & pages with displaytitle
829  if ( $pOutput ) {
830  $this->adjustDisplayTitle( $pOutput );
831  }
832 
833  # For the main page, overwrite the <title> element with the con-
834  # tents of 'pagetitle-view-mainpage' instead of the default (if
835  # that's not empty).
836  # This message always exists because it is in the i18n files
837  if ( $this->getTitle()->isMainPage() ) {
838  $msg = wfMessage( 'pagetitle-view-mainpage' )->inContentLanguage();
839  if ( !$msg->isDisabled() ) {
840  $outputPage->setHTMLTitle( $msg->title( $this->getTitle() )->text() );
841  }
842  }
843 
844  # Use adaptive TTLs for CDN so delayed/failed purges are noticed less often.
845  # This could use getTouched(), but that could be scary for major template edits.
846  $outputPage->adaptCdnTTL( $this->mPage->getTimestamp(), IExpiringStore::TTL_DAY );
847 
848  # Check for any __NOINDEX__ tags on the page using $pOutput
849  $policy = $this->getRobotPolicy( 'view', $pOutput ?: null );
850  $outputPage->setIndexPolicy( $policy['index'] );
851  $outputPage->setFollowPolicy( $policy['follow'] ); // FIXME: test this
852 
853  $this->showViewFooter();
854  $this->mPage->doViewUpdates( $user, $oldid ); // FIXME: test this
855 
856  # Load the postEdit module if the user just saved this revision
857  # See also EditPage::setPostEditCookie
858  $request = $this->getContext()->getRequest();
860  $postEdit = $request->getCookie( $cookieKey );
861  if ( $postEdit ) {
862  # Clear the cookie. This also prevents caching of the response.
863  $request->response()->clearCookie( $cookieKey );
864  $outputPage->addJsConfigVars( 'wgPostEdit', $postEdit );
865  $outputPage->addModules( 'mediawiki.action.view.postEdit' ); // FIXME: test this
866  }
867  }
868 
873  private function getRevisionRedirectTarget( RevisionRecord $revision ) {
874  // TODO: find a *good* place for the code that determines the redirect target for
875  // a given revision!
876  // NOTE: Use main slot content. Compare code in DerivedPageDataUpdater::revisionIsRedirect.
877  $content = $revision->getContent( SlotRecord::MAIN );
878  return $content ? $content->getRedirectTarget() : null;
879  }
880 
885  public function adjustDisplayTitle( ParserOutput $pOutput ) {
886  $out = $this->getContext()->getOutput();
887 
888  # Adjust the title if it was set by displaytitle, -{T|}- or language conversion
889  $titleText = $pOutput->getTitleText();
890  if ( strval( $titleText ) !== '' ) {
891  $out->setPageTitle( $titleText );
892  $out->setDisplayTitle( $titleText );
893  }
894  }
895 
900  protected function showDiffPage() {
901  $request = $this->getContext()->getRequest();
902  $user = $this->getContext()->getUser();
903  $diff = $request->getVal( 'diff' );
904  $rcid = $request->getVal( 'rcid' );
905  $diffOnly = $request->getBool( 'diffonly', $user->getOption( 'diffonly' ) );
906  $purge = $request->getVal( 'action' ) == 'purge';
907  $unhide = $request->getInt( 'unhide' ) == 1;
908  $oldid = $this->getOldID();
909 
910  $rev = $this->getRevisionFetched();
911 
912  if ( !$rev ) {
913  $this->getContext()->getOutput()->setPageTitle( wfMessage( 'errorpagetitle' ) );
914  $msg = $this->getContext()->msg( 'difference-missing-revision' )
915  ->params( $oldid )
916  ->numParams( 1 )
917  ->parseAsBlock();
918  $this->getContext()->getOutput()->addHTML( $msg );
919  return;
920  }
921 
922  $contentHandler = $rev->getContentHandler();
923  $de = $contentHandler->createDifferenceEngine(
924  $this->getContext(),
925  $oldid,
926  $diff,
927  $rcid,
928  $purge,
929  $unhide
930  );
931 
932  // DifferenceEngine directly fetched the revision:
933  $this->mRevIdFetched = $de->getNewid();
934  $de->showDiffPage( $diffOnly );
935 
936  // Run view updates for the newer revision being diffed (and shown
937  // below the diff if not $diffOnly).
938  list( $old, $new ) = $de->mapDiffPrevNext( $oldid, $diff );
939  // New can be false, convert it to 0 - this conveniently means the latest revision
940  $this->mPage->doViewUpdates( $user, (int)$new );
941  }
942 
950  public function getRobotPolicy( $action, ParserOutput $pOutput = null ) {
952 
953  $ns = $this->getTitle()->getNamespace();
954 
955  # Don't index user and user talk pages for blocked users (T13443)
956  if ( ( $ns == NS_USER || $ns == NS_USER_TALK ) && !$this->getTitle()->isSubpage() ) {
957  $specificTarget = null;
958  $vagueTarget = null;
959  $titleText = $this->getTitle()->getText();
960  if ( IP::isValid( $titleText ) ) {
961  $vagueTarget = $titleText;
962  } else {
963  $specificTarget = $titleText;
964  }
965  if ( DatabaseBlock::newFromTarget( $specificTarget, $vagueTarget ) instanceof DatabaseBlock ) {
966  return [
967  'index' => 'noindex',
968  'follow' => 'nofollow'
969  ];
970  }
971  }
972 
973  if ( $this->mPage->getId() === 0 || $this->getOldID() ) {
974  # Non-articles (special pages etc), and old revisions
975  return [
976  'index' => 'noindex',
977  'follow' => 'nofollow'
978  ];
979  } elseif ( $this->getContext()->getOutput()->isPrintable() ) {
980  # Discourage indexing of printable versions, but encourage following
981  return [
982  'index' => 'noindex',
983  'follow' => 'follow'
984  ];
985  } elseif ( $this->getContext()->getRequest()->getInt( 'curid' ) ) {
986  # For ?curid=x urls, disallow indexing
987  return [
988  'index' => 'noindex',
989  'follow' => 'follow'
990  ];
991  }
992 
993  # Otherwise, construct the policy based on the various config variables.
994  $policy = self::formatRobotPolicy( $wgDefaultRobotPolicy );
995 
996  if ( isset( $wgNamespaceRobotPolicies[$ns] ) ) {
997  # Honour customised robot policies for this namespace
998  $policy = array_merge(
999  $policy,
1000  self::formatRobotPolicy( $wgNamespaceRobotPolicies[$ns] )
1001  );
1002  }
1003  if ( $this->getTitle()->canUseNoindex() && is_object( $pOutput ) && $pOutput->getIndexPolicy() ) {
1004  # __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates
1005  # a final sanity check that we have really got the parser output.
1006  $policy = array_merge(
1007  $policy,
1008  [ 'index' => $pOutput->getIndexPolicy() ]
1009  );
1010  }
1011 
1012  if ( isset( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] ) ) {
1013  # (T16900) site config can override user-defined __INDEX__ or __NOINDEX__
1014  $policy = array_merge(
1015  $policy,
1016  self::formatRobotPolicy( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] )
1017  );
1018  }
1019 
1020  return $policy;
1021  }
1022 
1030  public static function formatRobotPolicy( $policy ) {
1031  if ( is_array( $policy ) ) {
1032  return $policy;
1033  } elseif ( !$policy ) {
1034  return [];
1035  }
1036 
1037  $policy = explode( ',', $policy );
1038  $policy = array_map( 'trim', $policy );
1039 
1040  $arr = [];
1041  foreach ( $policy as $var ) {
1042  if ( in_array( $var, [ 'index', 'noindex' ] ) ) {
1043  $arr['index'] = $var;
1044  } elseif ( in_array( $var, [ 'follow', 'nofollow' ] ) ) {
1045  $arr['follow'] = $var;
1046  }
1047  }
1048 
1049  return $arr;
1050  }
1051 
1059  public function showRedirectedFromHeader() {
1060  global $wgRedirectSources;
1061 
1062  $context = $this->getContext();
1063  $outputPage = $context->getOutput();
1064  $request = $context->getRequest();
1065  $rdfrom = $request->getVal( 'rdfrom' );
1066 
1067  // Construct a URL for the current page view, but with the target title
1068  $query = $request->getValues();
1069  unset( $query['rdfrom'] );
1070  unset( $query['title'] );
1071  if ( $this->getTitle()->isRedirect() ) {
1072  // Prevent double redirects
1073  $query['redirect'] = 'no';
1074  }
1075  $redirectTargetUrl = $this->getTitle()->getLinkURL( $query );
1076 
1077  if ( isset( $this->mRedirectedFrom ) ) {
1078  // Avoid PHP 7.1 warning of passing $this by reference
1079  $articlePage = $this;
1080 
1081  // This is an internally redirected page view.
1082  // We'll need a backlink to the source page for navigation.
1083  if ( Hooks::run( 'ArticleViewRedirect', [ &$articlePage ] ) ) {
1084  $redir = Linker::linkKnown(
1085  $this->mRedirectedFrom,
1086  null,
1087  [],
1088  [ 'redirect' => 'no' ]
1089  );
1090 
1091  $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
1092  $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
1093  . "</span>" );
1094 
1095  // Add the script to update the displayed URL and
1096  // set the fragment if one was specified in the redirect
1097  $outputPage->addJsConfigVars( [
1098  'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1099  ] );
1100  $outputPage->addModules( 'mediawiki.action.view.redirect' );
1101 
1102  // Add a <link rel="canonical"> tag
1103  $outputPage->setCanonicalUrl( $this->getTitle()->getCanonicalURL() );
1104 
1105  // Tell the output object that the user arrived at this article through a redirect
1106  $outputPage->setRedirectedFrom( $this->mRedirectedFrom );
1107 
1108  return true;
1109  }
1110  } elseif ( $rdfrom ) {
1111  // This is an externally redirected view, from some other wiki.
1112  // If it was reported from a trusted site, supply a backlink.
1113  if ( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) {
1114  $redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
1115  $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
1116  $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
1117  . "</span>" );
1118 
1119  // Add the script to update the displayed URL
1120  $outputPage->addJsConfigVars( [
1121  'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1122  ] );
1123  $outputPage->addModules( 'mediawiki.action.view.redirect' );
1124 
1125  return true;
1126  }
1127  }
1128 
1129  return false;
1130  }
1131 
1136  public function showNamespaceHeader() {
1137  if ( $this->getTitle()->isTalkPage() && !wfMessage( 'talkpageheader' )->isDisabled() ) {
1138  $this->getContext()->getOutput()->wrapWikiMsg(
1139  "<div class=\"mw-talkpageheader\">\n$1\n</div>",
1140  [ 'talkpageheader' ]
1141  );
1142  }
1143  }
1144 
1148  public function showViewFooter() {
1149  # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
1150  if ( $this->getTitle()->getNamespace() == NS_USER_TALK
1151  && IP::isValid( $this->getTitle()->getText() )
1152  ) {
1153  $this->getContext()->getOutput()->addWikiMsg( 'anontalkpagetext' );
1154  }
1155 
1156  // Show a footer allowing the user to patrol the shown revision or page if possible
1157  $patrolFooterShown = $this->showPatrolFooter();
1158 
1159  Hooks::run( 'ArticleViewFooter', [ $this, $patrolFooterShown ] );
1160  }
1161 
1171  public function showPatrolFooter() {
1173 
1174  // Allow hooks to decide whether to not output this at all
1175  if ( !Hooks::run( 'ArticleShowPatrolFooter', [ $this ] ) ) {
1176  return false;
1177  }
1178 
1179  $outputPage = $this->getContext()->getOutput();
1180  $user = $this->getContext()->getUser();
1181  $title = $this->getTitle();
1182  $rc = false;
1183 
1184  if ( !$title->quickUserCan( 'patrol', $user )
1185  || !( $wgUseRCPatrol || $wgUseNPPatrol
1186  || ( $wgUseFilePatrol && $title->inNamespace( NS_FILE ) ) )
1187  ) {
1188  // Patrolling is disabled or the user isn't allowed to
1189  return false;
1190  }
1191 
1192  if ( $this->mRevision
1193  && !RecentChange::isInRCLifespan( $this->mRevision->getTimestamp(), 21600 )
1194  ) {
1195  // The current revision is already older than what could be in the RC table
1196  // 6h tolerance because the RC might not be cleaned out regularly
1197  return false;
1198  }
1199 
1200  // Check for cached results
1201  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1202  $key = $cache->makeKey( 'unpatrollable-page', $title->getArticleID() );
1203  if ( $cache->get( $key ) ) {
1204  return false;
1205  }
1206 
1207  $dbr = wfGetDB( DB_REPLICA );
1208  $oldestRevisionTimestamp = $dbr->selectField(
1209  'revision',
1210  'MIN( rev_timestamp )',
1211  [ 'rev_page' => $title->getArticleID() ],
1212  __METHOD__
1213  );
1214 
1215  // New page patrol: Get the timestamp of the oldest revison which
1216  // the revision table holds for the given page. Then we look
1217  // whether it's within the RC lifespan and if it is, we try
1218  // to get the recentchanges row belonging to that entry
1219  // (with rc_new = 1).
1220  $recentPageCreation = false;
1221  if ( $oldestRevisionTimestamp
1222  && RecentChange::isInRCLifespan( $oldestRevisionTimestamp, 21600 )
1223  ) {
1224  // 6h tolerance because the RC might not be cleaned out regularly
1225  $recentPageCreation = true;
1227  [
1228  'rc_new' => 1,
1229  'rc_timestamp' => $oldestRevisionTimestamp,
1230  'rc_namespace' => $title->getNamespace(),
1231  'rc_cur_id' => $title->getArticleID()
1232  ],
1233  __METHOD__
1234  );
1235  if ( $rc ) {
1236  // Use generic patrol message for new pages
1237  $markPatrolledMsg = wfMessage( 'markaspatrolledtext' );
1238  }
1239  }
1240 
1241  // File patrol: Get the timestamp of the latest upload for this page,
1242  // check whether it is within the RC lifespan and if it is, we try
1243  // to get the recentchanges row belonging to that entry
1244  // (with rc_type = RC_LOG, rc_log_type = upload).
1245  $recentFileUpload = false;
1246  if ( ( !$rc || $rc->getAttribute( 'rc_patrolled' ) ) && $wgUseFilePatrol
1247  && $title->getNamespace() === NS_FILE ) {
1248  // Retrieve timestamp of most recent upload
1249  $newestUploadTimestamp = $dbr->selectField(
1250  'image',
1251  'MAX( img_timestamp )',
1252  [ 'img_name' => $title->getDBkey() ],
1253  __METHOD__
1254  );
1255  if ( $newestUploadTimestamp
1256  && RecentChange::isInRCLifespan( $newestUploadTimestamp, 21600 )
1257  ) {
1258  // 6h tolerance because the RC might not be cleaned out regularly
1259  $recentFileUpload = true;
1261  [
1262  'rc_type' => RC_LOG,
1263  'rc_log_type' => 'upload',
1264  'rc_timestamp' => $newestUploadTimestamp,
1265  'rc_namespace' => NS_FILE,
1266  'rc_cur_id' => $title->getArticleID()
1267  ],
1268  __METHOD__
1269  );
1270  if ( $rc ) {
1271  // Use patrol message specific to files
1272  $markPatrolledMsg = wfMessage( 'markaspatrolledtext-file' );
1273  }
1274  }
1275  }
1276 
1277  if ( !$recentPageCreation && !$recentFileUpload ) {
1278  // Page creation and latest upload (for files) is too old to be in RC
1279 
1280  // We definitely can't patrol so cache the information
1281  // When a new file version is uploaded, the cache is cleared
1282  $cache->set( $key, '1' );
1283 
1284  return false;
1285  }
1286 
1287  if ( !$rc ) {
1288  // Don't cache: This can be hit if the page gets accessed very fast after
1289  // its creation / latest upload or in case we have high replica DB lag. In case
1290  // the revision is too old, we will already return above.
1291  return false;
1292  }
1293 
1294  if ( $rc->getAttribute( 'rc_patrolled' ) ) {
1295  // Patrolled RC entry around
1296 
1297  // Cache the information we gathered above in case we can't patrol
1298  // Don't cache in case we can patrol as this could change
1299  $cache->set( $key, '1' );
1300 
1301  return false;
1302  }
1303 
1304  if ( $rc->getPerformer()->equals( $user ) ) {
1305  // Don't show a patrol link for own creations/uploads. If the user could
1306  // patrol them, they already would be patrolled
1307  return false;
1308  }
1309 
1310  $outputPage->preventClickjacking();
1311  if ( $user->isAllowed( 'writeapi' ) ) {
1312  $outputPage->addModules( 'mediawiki.page.patrol.ajax' );
1313  }
1314 
1316  $title,
1317  $markPatrolledMsg->escaped(),
1318  [],
1319  [
1320  'action' => 'markpatrolled',
1321  'rcid' => $rc->getAttribute( 'rc_id' ),
1322  ]
1323  );
1324 
1325  $outputPage->addHTML(
1326  "<div class='patrollink' data-mw='interface'>" .
1327  wfMessage( 'markaspatrolledlink' )->rawParams( $link )->escaped() .
1328  '</div>'
1329  );
1330 
1331  return true;
1332  }
1333 
1340  public static function purgePatrolFooterCache( $articleID ) {
1341  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1342  $cache->delete( $cache->makeKey( 'unpatrollable-page', $articleID ) );
1343  }
1344 
1349  public function showMissingArticle() {
1350  global $wgSend404Code;
1351 
1352  $outputPage = $this->getContext()->getOutput();
1353  // Whether the page is a root user page of an existing user (but not a subpage)
1354  $validUserPage = false;
1355 
1356  $title = $this->getTitle();
1357 
1358  $services = MediaWikiServices::getInstance();
1359 
1360  # Show info in user (talk) namespace. Does the user exist? Is he blocked?
1361  if ( $title->getNamespace() == NS_USER
1362  || $title->getNamespace() == NS_USER_TALK
1363  ) {
1364  $rootPart = explode( '/', $title->getText() )[0];
1365  $user = User::newFromName( $rootPart, false /* allow IP users */ );
1366  $ip = User::isIP( $rootPart );
1367  $block = DatabaseBlock::newFromTarget( $user, $user );
1368 
1369  if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
1370  $outputPage->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
1371  [ 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) ] );
1372  } elseif (
1373  !is_null( $block ) &&
1374  $block->getType() != DatabaseBlock::TYPE_AUTO &&
1375  ( $block->isSitewide() || $user->isBlockedFrom( $title ) )
1376  ) {
1377  // Show log extract if the user is sitewide blocked or is partially
1378  // blocked and not allowed to edit their user page or user talk page
1380  $outputPage,
1381  'block',
1382  $services->getNamespaceInfo()->getCanonicalName( NS_USER ) . ':' .
1383  $block->getTarget(),
1384  '',
1385  [
1386  'lim' => 1,
1387  'showIfEmpty' => false,
1388  'msgKey' => [
1389  'blocked-notice-logextract',
1390  $user->getName() # Support GENDER in notice
1391  ]
1392  ]
1393  );
1394  $validUserPage = !$title->isSubpage();
1395  } else {
1396  $validUserPage = !$title->isSubpage();
1397  }
1398  }
1399 
1400  Hooks::run( 'ShowMissingArticle', [ $this ] );
1401 
1402  # Show delete and move logs if there were any such events.
1403  # The logging query can DOS the site when bots/crawlers cause 404 floods,
1404  # so be careful showing this. 404 pages must be cheap as they are hard to cache.
1405  $dbCache = ObjectCache::getInstance( 'db-replicated' );
1406  $key = $dbCache->makeKey( 'page-recent-delete', md5( $title->getPrefixedText() ) );
1407  $loggedIn = $this->getContext()->getUser()->isLoggedIn();
1408  $sessionExists = $this->getContext()->getRequest()->getSession()->isPersistent();
1409  if ( $loggedIn || $dbCache->get( $key ) || $sessionExists ) {
1410  $logTypes = [ 'delete', 'move', 'protect' ];
1411 
1412  $dbr = wfGetDB( DB_REPLICA );
1413 
1414  $conds = [ 'log_action != ' . $dbr->addQuotes( 'revision' ) ];
1415  // Give extensions a chance to hide their (unrelated) log entries
1416  Hooks::run( 'Article::MissingArticleConditions', [ &$conds, $logTypes ] );
1418  $outputPage,
1419  $logTypes,
1420  $title,
1421  '',
1422  [
1423  'lim' => 10,
1424  'conds' => $conds,
1425  'showIfEmpty' => false,
1426  'msgKey' => [ $loggedIn || $sessionExists
1427  ? 'moveddeleted-notice'
1428  : 'moveddeleted-notice-recent'
1429  ]
1430  ]
1431  );
1432  }
1433 
1434  if ( !$this->mPage->hasViewableContent() && $wgSend404Code && !$validUserPage ) {
1435  // If there's no backing content, send a 404 Not Found
1436  // for better machine handling of broken links.
1437  $this->getContext()->getRequest()->response()->statusHeader( 404 );
1438  }
1439 
1440  // Also apply the robot policy for nonexisting pages (even if a 404 was used for sanity)
1441  $policy = $this->getRobotPolicy( 'view' );
1442  $outputPage->setIndexPolicy( $policy['index'] );
1443  $outputPage->setFollowPolicy( $policy['follow'] );
1444 
1445  $hookResult = Hooks::run( 'BeforeDisplayNoArticleText', [ $this ] );
1446 
1447  if ( !$hookResult ) {
1448  return;
1449  }
1450 
1451  # Show error message
1452  $oldid = $this->getOldID();
1453  if ( !$oldid && $title->getNamespace() === NS_MEDIAWIKI && $title->hasSourceText() ) {
1454  // use fake Content object for system message
1455  $parserOptions = ParserOptions::newCanonical( 'canonical' );
1456  $outputPage->addParserOutput( $this->getEmptyPageParserOutput( $parserOptions ) );
1457  } else {
1458  if ( $oldid ) {
1459  $text = wfMessage( 'missing-revision', $oldid )->plain();
1460  } elseif ( $title->quickUserCan( 'create', $this->getContext()->getUser() )
1461  && $title->quickUserCan( 'edit', $this->getContext()->getUser() )
1462  ) {
1463  $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
1464  $text = wfMessage( $message )->plain();
1465  } else {
1466  $text = wfMessage( 'noarticletext-nopermission' )->plain();
1467  }
1468 
1469  $dir = $this->getContext()->getLanguage()->getDir();
1470  $lang = $this->getContext()->getLanguage()->getHtmlCode();
1471  $outputPage->addWikiTextAsInterface( Xml::openElement( 'div', [
1472  'class' => "noarticletext mw-content-$dir",
1473  'dir' => $dir,
1474  'lang' => $lang,
1475  ] ) . "\n$text\n</div>" );
1476  }
1477  }
1478 
1485  public function showDeletedRevisionHeader() {
1486  if ( !$this->mRevision->isDeleted( RevisionRecord::DELETED_TEXT ) ) {
1487  // Not deleted
1488  return true;
1489  }
1490 
1491  $outputPage = $this->getContext()->getOutput();
1492  $user = $this->getContext()->getUser();
1493  // If the user is not allowed to see it...
1494  if ( !$this->mRevision->userCan( RevisionRecord::DELETED_TEXT, $user ) ) {
1495  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1496  'rev-deleted-text-permission' );
1497 
1498  return false;
1499  // If the user needs to confirm that they want to see it...
1500  } elseif ( $this->getContext()->getRequest()->getInt( 'unhide' ) != 1 ) {
1501  # Give explanation and add a link to view the revision...
1502  $oldid = intval( $this->getOldID() );
1503  $link = $this->getTitle()->getFullURL( "oldid={$oldid}&unhide=1" );
1504  $msg = $this->mRevision->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ?
1505  'rev-suppressed-text-unhide' : 'rev-deleted-text-unhide';
1506  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1507  [ $msg, $link ] );
1508 
1509  return false;
1510  // We are allowed to see...
1511  } else {
1512  $msg = $this->mRevision->isDeleted( RevisionRecord::DELETED_RESTRICTED ) ?
1513  'rev-suppressed-text-view' : 'rev-deleted-text-view';
1514  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", $msg );
1515 
1516  return true;
1517  }
1518  }
1519 
1528  public function setOldSubtitle( $oldid = 0 ) {
1529  // Avoid PHP 7.1 warning of passing $this by reference
1530  $articlePage = $this;
1531 
1532  if ( !Hooks::run( 'DisplayOldSubtitle', [ &$articlePage, &$oldid ] ) ) {
1533  return;
1534  }
1535 
1536  $context = $this->getContext();
1537  $unhide = $context->getRequest()->getInt( 'unhide' ) == 1;
1538 
1539  # Cascade unhide param in links for easy deletion browsing
1540  $extraParams = [];
1541  if ( $unhide ) {
1542  $extraParams['unhide'] = 1;
1543  }
1544 
1545  if ( $this->mRevision && $this->mRevision->getId() === $oldid ) {
1546  $revision = $this->mRevision;
1547  } else {
1548  $revision = Revision::newFromId( $oldid );
1549  }
1550 
1551  $timestamp = $revision->getTimestamp();
1552 
1553  $current = ( $oldid == $this->mPage->getLatest() );
1554  $language = $context->getLanguage();
1555  $user = $context->getUser();
1556 
1557  $td = $language->userTimeAndDate( $timestamp, $user );
1558  $tddate = $language->userDate( $timestamp, $user );
1559  $tdtime = $language->userTime( $timestamp, $user );
1560 
1561  # Show user links if allowed to see them. If hidden, then show them only if requested...
1562  $userlinks = Linker::revUserTools( $revision, !$unhide );
1563 
1564  $infomsg = $current && !$context->msg( 'revision-info-current' )->isDisabled()
1565  ? 'revision-info-current'
1566  : 'revision-info';
1567 
1568  $outputPage = $context->getOutput();
1569  $revisionInfo = "<div id=\"mw-{$infomsg}\">" .
1570  $context->msg( $infomsg, $td )
1571  ->rawParams( $userlinks )
1572  ->params( $revision->getId(), $tddate, $tdtime, $revision->getUserText() )
1573  ->rawParams( Linker::revComment( $revision, true, true ) )
1574  ->parse() .
1575  "</div>";
1576 
1577  $lnk = $current
1578  ? $context->msg( 'currentrevisionlink' )->escaped()
1580  $this->getTitle(),
1581  $context->msg( 'currentrevisionlink' )->escaped(),
1582  [],
1583  $extraParams
1584  );
1585  $curdiff = $current
1586  ? $context->msg( 'diff' )->escaped()
1588  $this->getTitle(),
1589  $context->msg( 'diff' )->escaped(),
1590  [],
1591  [
1592  'diff' => 'cur',
1593  'oldid' => $oldid
1594  ] + $extraParams
1595  );
1596  $prev = $this->getTitle()->getPreviousRevisionID( $oldid );
1597  $prevlink = $prev
1599  $this->getTitle(),
1600  $context->msg( 'previousrevision' )->escaped(),
1601  [],
1602  [
1603  'direction' => 'prev',
1604  'oldid' => $oldid
1605  ] + $extraParams
1606  )
1607  : $context->msg( 'previousrevision' )->escaped();
1608  $prevdiff = $prev
1610  $this->getTitle(),
1611  $context->msg( 'diff' )->escaped(),
1612  [],
1613  [
1614  'diff' => 'prev',
1615  'oldid' => $oldid
1616  ] + $extraParams
1617  )
1618  : $context->msg( 'diff' )->escaped();
1619  $nextlink = $current
1620  ? $context->msg( 'nextrevision' )->escaped()
1622  $this->getTitle(),
1623  $context->msg( 'nextrevision' )->escaped(),
1624  [],
1625  [
1626  'direction' => 'next',
1627  'oldid' => $oldid
1628  ] + $extraParams
1629  );
1630  $nextdiff = $current
1631  ? $context->msg( 'diff' )->escaped()
1633  $this->getTitle(),
1634  $context->msg( 'diff' )->escaped(),
1635  [],
1636  [
1637  'diff' => 'next',
1638  'oldid' => $oldid
1639  ] + $extraParams
1640  );
1641 
1642  $cdel = Linker::getRevDeleteLink( $user, $revision, $this->getTitle() );
1643  if ( $cdel !== '' ) {
1644  $cdel .= ' ';
1645  }
1646 
1647  // the outer div is need for styling the revision info and nav in MobileFrontend
1648  $outputPage->addSubtitle( "<div class=\"mw-revision\">" . $revisionInfo .
1649  "<div id=\"mw-revision-nav\">" . $cdel .
1650  $context->msg( 'revision-nav' )->rawParams(
1651  $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff
1652  )->escaped() . "</div></div>" );
1653  }
1654 
1668  public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) {
1669  $lang = $this->getTitle()->getPageLanguage();
1670  $out = $this->getContext()->getOutput();
1671  if ( $appendSubtitle ) {
1672  $out->addSubtitle( wfMessage( 'redirectpagesub' ) );
1673  }
1674  $out->addModuleStyles( 'mediawiki.action.view.redirectPage' );
1675  return static::getRedirectHeaderHtml( $lang, $target, $forceKnown );
1676  }
1677 
1690  public static function getRedirectHeaderHtml( Language $lang, $target, $forceKnown = false ) {
1691  if ( !is_array( $target ) ) {
1692  $target = [ $target ];
1693  }
1694 
1695  $html = '<ul class="redirectText">';
1697  foreach ( $target as $title ) {
1698  $html .= '<li>' . Linker::link(
1699  $title,
1700  htmlspecialchars( $title->getFullText() ),
1701  [],
1702  // Make sure wiki page redirects are not followed
1703  $title->isRedirect() ? [ 'redirect' => 'no' ] : [],
1704  ( $forceKnown ? [ 'known', 'noclasses' ] : [] )
1705  ) . '</li>';
1706  }
1707  $html .= '</ul>';
1708 
1709  $redirectToText = wfMessage( 'redirectto' )->inLanguage( $lang )->escaped();
1710 
1711  return '<div class="redirectMsg">' .
1712  '<p>' . $redirectToText . '</p>' .
1713  $html .
1714  '</div>';
1715  }
1716 
1725  public function addHelpLink( $to, $overrideBaseUrl = false ) {
1726  $msg = wfMessage(
1727  'namespace-' . $this->getTitle()->getNamespace() . '-helppage'
1728  );
1729 
1730  $out = $this->getContext()->getOutput();
1731  if ( !$msg->isDisabled() ) {
1732  $helpUrl = Skin::makeUrl( $msg->plain() );
1733  $out->addHelpLink( $helpUrl, true );
1734  } else {
1735  $out->addHelpLink( $to, $overrideBaseUrl );
1736  }
1737  }
1738 
1742  public function render() {
1743  $this->getContext()->getRequest()->response()->header( 'X-Robots-Tag: noindex' );
1744  $this->getContext()->getOutput()->setArticleBodyOnly( true );
1745  // We later set 'enableSectionEditLinks=false' based on this; also used by ImagePage
1746  $this->viewIsRenderAction = true;
1747  $this->view();
1748  }
1749 
1753  public function protect() {
1754  $form = new ProtectionForm( $this );
1755  $form->execute();
1756  }
1757 
1761  public function unprotect() {
1762  $this->protect();
1763  }
1764 
1768  public function delete() {
1769  # This code desperately needs to be totally rewritten
1770 
1771  $title = $this->getTitle();
1772  $context = $this->getContext();
1773  $user = $context->getUser();
1774  $request = $context->getRequest();
1775 
1776  # Check permissions
1777  $permissionErrors = $title->getUserPermissionsErrors( 'delete', $user );
1778  if ( count( $permissionErrors ) ) {
1779  throw new PermissionsError( 'delete', $permissionErrors );
1780  }
1781 
1782  # Read-only check...
1783  if ( wfReadOnly() ) {
1784  throw new ReadOnlyError;
1785  }
1786 
1787  # Better double-check that it hasn't been deleted yet!
1788  $this->mPage->loadPageData(
1789  $request->wasPosted() ? WikiPage::READ_LATEST : WikiPage::READ_NORMAL
1790  );
1791  if ( !$this->mPage->exists() ) {
1792  $deleteLogPage = new LogPage( 'delete' );
1793  $outputPage = $context->getOutput();
1794  $outputPage->setPageTitle( $context->msg( 'cannotdelete-title', $title->getPrefixedText() ) );
1795  $outputPage->wrapWikiMsg( "<div class=\"error mw-error-cannotdelete\">\n$1\n</div>",
1796  [ 'cannotdelete', wfEscapeWikiText( $title->getPrefixedText() ) ]
1797  );
1798  $outputPage->addHTML(
1799  Xml::element( 'h2', null, $deleteLogPage->getName()->text() )
1800  );
1802  $outputPage,
1803  'delete',
1804  $title
1805  );
1806 
1807  return;
1808  }
1809 
1810  $deleteReasonList = $request->getText( 'wpDeleteReasonList', 'other' );
1811  $deleteReason = $request->getText( 'wpReason' );
1812 
1813  if ( $deleteReasonList == 'other' ) {
1814  $reason = $deleteReason;
1815  } elseif ( $deleteReason != '' ) {
1816  // Entry from drop down menu + additional comment
1817  $colonseparator = wfMessage( 'colon-separator' )->inContentLanguage()->text();
1818  $reason = $deleteReasonList . $colonseparator . $deleteReason;
1819  } else {
1820  $reason = $deleteReasonList;
1821  }
1822 
1823  if ( $request->wasPosted() && $user->matchEditToken( $request->getVal( 'wpEditToken' ),
1824  [ 'delete', $this->getTitle()->getPrefixedText() ] )
1825  ) {
1826  # Flag to hide all contents of the archived revisions
1827  $suppress = $request->getCheck( 'wpSuppress' ) && $user->isAllowed( 'suppressrevision' );
1828 
1829  $this->doDelete( $reason, $suppress );
1830 
1831  WatchAction::doWatchOrUnwatch( $request->getCheck( 'wpWatch' ), $title, $user );
1832 
1833  return;
1834  }
1835 
1836  // Generate deletion reason
1837  $hasHistory = false;
1838  if ( !$reason ) {
1839  try {
1840  $reason = $this->generateReason( $hasHistory );
1841  } catch ( Exception $e ) {
1842  # if a page is horribly broken, we still want to be able to
1843  # delete it. So be lenient about errors here.
1844  wfDebug( "Error while building auto delete summary: $e" );
1845  $reason = '';
1846  }
1847  }
1848 
1849  // If the page has a history, insert a warning
1850  if ( $hasHistory ) {
1851  $title = $this->getTitle();
1852 
1853  // The following can use the real revision count as this is only being shown for users
1854  // that can delete this page.
1855  // This, as a side-effect, also makes sure that the following query isn't being run for
1856  // pages with a larger history, unless the user has the 'bigdelete' right
1857  // (and is about to delete this page).
1858  $dbr = wfGetDB( DB_REPLICA );
1859  $revisions = $edits = (int)$dbr->selectField(
1860  'revision',
1861  'COUNT(rev_page)',
1862  [ 'rev_page' => $title->getArticleID() ],
1863  __METHOD__
1864  );
1865 
1866  // @todo i18n issue/patchwork message
1867  $context->getOutput()->addHTML(
1868  '<strong class="mw-delete-warning-revisions">' .
1869  $context->msg( 'historywarning' )->numParams( $revisions )->parse() .
1870  $context->msg( 'word-separator' )->escaped() . Linker::linkKnown( $title,
1871  $context->msg( 'history' )->escaped(),
1872  [],
1873  [ 'action' => 'history' ] ) .
1874  '</strong>'
1875  );
1876 
1877  if ( $title->isBigDeletion() ) {
1878  global $wgDeleteRevisionsLimit;
1879  $context->getOutput()->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
1880  [
1881  'delete-warning-toobig',
1882  $context->getLanguage()->formatNum( $wgDeleteRevisionsLimit )
1883  ]
1884  );
1885  }
1886  }
1887 
1888  $this->confirmDelete( $reason );
1889  }
1890 
1896  public function confirmDelete( $reason ) {
1897  wfDebug( "Article::confirmDelete\n" );
1898 
1899  $title = $this->getTitle();
1900  $ctx = $this->getContext();
1901  $outputPage = $ctx->getOutput();
1902  $outputPage->setPageTitle( wfMessage( 'delete-confirm', $title->getPrefixedText() ) );
1903  $outputPage->addBacklinkSubtitle( $title );
1904  $outputPage->setRobotPolicy( 'noindex,nofollow' );
1905  $outputPage->addModules( 'mediawiki.action.delete' );
1906 
1907  $backlinkCache = $title->getBacklinkCache();
1908  if ( $backlinkCache->hasLinks( 'pagelinks' ) || $backlinkCache->hasLinks( 'templatelinks' ) ) {
1909  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1910  'deleting-backlinks-warning' );
1911  }
1912 
1913  $subpageQueryLimit = 51;
1914  $subpages = $title->getSubpages( $subpageQueryLimit );
1915  $subpageCount = count( $subpages );
1916  if ( $subpageCount > 0 ) {
1917  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1918  [ 'deleting-subpages-warning', Message::numParam( $subpageCount ) ] );
1919  }
1920  $outputPage->addWikiMsg( 'confirmdeletetext' );
1921 
1922  Hooks::run( 'ArticleConfirmDelete', [ $this, $outputPage, &$reason ] );
1923 
1924  $user = $this->getContext()->getUser();
1925  $checkWatch = $user->getBoolOption( 'watchdeletion' ) || $user->isWatched( $title );
1926 
1927  $outputPage->enableOOUI();
1928 
1930  $ctx->msg( 'deletereason-dropdown' )->inContentLanguage()->text(),
1931  [ 'other' => $ctx->msg( 'deletereasonotherlist' )->inContentLanguage()->text() ]
1932  );
1933  $options = Xml::listDropDownOptionsOoui( $options );
1934 
1935  $fields[] = new OOUI\FieldLayout(
1936  new OOUI\DropdownInputWidget( [
1937  'name' => 'wpDeleteReasonList',
1938  'inputId' => 'wpDeleteReasonList',
1939  'tabIndex' => 1,
1940  'infusable' => true,
1941  'value' => '',
1942  'options' => $options
1943  ] ),
1944  [
1945  'label' => $ctx->msg( 'deletecomment' )->text(),
1946  'align' => 'top',
1947  ]
1948  );
1949 
1950  // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
1951  // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
1952  // Unicode codepoints.
1953  $fields[] = new OOUI\FieldLayout(
1954  new OOUI\TextInputWidget( [
1955  'name' => 'wpReason',
1956  'inputId' => 'wpReason',
1957  'tabIndex' => 2,
1959  'infusable' => true,
1960  'value' => $reason,
1961  'autofocus' => true,
1962  ] ),
1963  [
1964  'label' => $ctx->msg( 'deleteotherreason' )->text(),
1965  'align' => 'top',
1966  ]
1967  );
1968 
1969  if ( $user->isLoggedIn() ) {
1970  $fields[] = new OOUI\FieldLayout(
1971  new OOUI\CheckboxInputWidget( [
1972  'name' => 'wpWatch',
1973  'inputId' => 'wpWatch',
1974  'tabIndex' => 3,
1975  'selected' => $checkWatch,
1976  ] ),
1977  [
1978  'label' => $ctx->msg( 'watchthis' )->text(),
1979  'align' => 'inline',
1980  'infusable' => true,
1981  ]
1982  );
1983  }
1984 
1985  if ( $user->isAllowed( 'suppressrevision' ) ) {
1986  $fields[] = new OOUI\FieldLayout(
1987  new OOUI\CheckboxInputWidget( [
1988  'name' => 'wpSuppress',
1989  'inputId' => 'wpSuppress',
1990  'tabIndex' => 4,
1991  ] ),
1992  [
1993  'label' => $ctx->msg( 'revdelete-suppress' )->text(),
1994  'align' => 'inline',
1995  'infusable' => true,
1996  ]
1997  );
1998  }
1999 
2000  $fields[] = new OOUI\FieldLayout(
2001  new OOUI\ButtonInputWidget( [
2002  'name' => 'wpConfirmB',
2003  'inputId' => 'wpConfirmB',
2004  'tabIndex' => 5,
2005  'value' => $ctx->msg( 'deletepage' )->text(),
2006  'label' => $ctx->msg( 'deletepage' )->text(),
2007  'flags' => [ 'primary', 'destructive' ],
2008  'type' => 'submit',
2009  ] ),
2010  [
2011  'align' => 'top',
2012  ]
2013  );
2014 
2015  $fieldset = new OOUI\FieldsetLayout( [
2016  'label' => $ctx->msg( 'delete-legend' )->text(),
2017  'id' => 'mw-delete-table',
2018  'items' => $fields,
2019  ] );
2020 
2021  $form = new OOUI\FormLayout( [
2022  'method' => 'post',
2023  'action' => $title->getLocalURL( 'action=delete' ),
2024  'id' => 'deleteconfirm',
2025  ] );
2026  $form->appendContent(
2027  $fieldset,
2028  new OOUI\HtmlSnippet(
2029  Html::hidden( 'wpEditToken', $user->getEditToken( [ 'delete', $title->getPrefixedText() ] ) )
2030  )
2031  );
2032 
2033  $outputPage->addHTML(
2034  new OOUI\PanelLayout( [
2035  'classes' => [ 'deletepage-wrapper' ],
2036  'expanded' => false,
2037  'padded' => true,
2038  'framed' => true,
2039  'content' => $form,
2040  ] )
2041  );
2042 
2043  if ( $user->isAllowed( 'editinterface' ) ) {
2045  $ctx->msg( 'deletereason-dropdown' )->inContentLanguage()->getTitle(),
2046  wfMessage( 'delete-edit-reasonlist' )->escaped(),
2047  [],
2048  [ 'action' => 'edit' ]
2049  );
2050  $outputPage->addHTML( '<p class="mw-delete-editreasons">' . $link . '</p>' );
2051  }
2052 
2053  $deleteLogPage = new LogPage( 'delete' );
2054  $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
2055  LogEventsList::showLogExtract( $outputPage, 'delete', $title );
2056  }
2057 
2066  public function doDelete( $reason, $suppress = false, $immediate = false ) {
2067  $error = '';
2068  $context = $this->getContext();
2069  $outputPage = $context->getOutput();
2070  $user = $context->getUser();
2071  $status = $this->mPage->doDeleteArticleReal( $reason, $suppress, 0, true, $error, $user,
2072  [], 'delete', $immediate );
2073 
2074  if ( $status->isOK() ) {
2075  $deleted = $this->getTitle()->getPrefixedText();
2076 
2077  $outputPage->setPageTitle( wfMessage( 'actioncomplete' ) );
2078  $outputPage->setRobotPolicy( 'noindex,nofollow' );
2079 
2080  if ( $status->isGood() ) {
2081  $loglink = '[[Special:Log/delete|' . wfMessage( 'deletionlog' )->text() . ']]';
2082  $outputPage->addWikiMsg( 'deletedtext', wfEscapeWikiText( $deleted ), $loglink );
2083  Hooks::run( 'ArticleDeleteAfterSuccess', [ $this->getTitle(), $outputPage ] );
2084  } else {
2085  $outputPage->addWikiMsg( 'delete-scheduled', wfEscapeWikiText( $deleted ) );
2086  }
2087 
2088  $outputPage->returnToMain( false );
2089  } else {
2090  $outputPage->setPageTitle(
2091  wfMessage( 'cannotdelete-title',
2092  $this->getTitle()->getPrefixedText() )
2093  );
2094 
2095  if ( $error == '' ) {
2096  $outputPage->wrapWikiTextAsInterface(
2097  'error mw-error-cannotdelete',
2098  $status->getWikiText()
2099  );
2100  $deleteLogPage = new LogPage( 'delete' );
2101  $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
2102 
2104  $outputPage,
2105  'delete',
2106  $this->getTitle()
2107  );
2108  } else {
2109  $outputPage->addHTML( $error );
2110  }
2111  }
2112  }
2113 
2114  /* Caching functions */
2115 
2123  protected function tryFileCache() {
2124  static $called = false;
2125 
2126  if ( $called ) {
2127  wfDebug( "Article::tryFileCache(): called twice!?\n" );
2128  return false;
2129  }
2130 
2131  $called = true;
2132  if ( $this->isFileCacheable() ) {
2133  $cache = new HTMLFileCache( $this->getTitle(), 'view' );
2134  if ( $cache->isCacheGood( $this->mPage->getTouched() ) ) {
2135  wfDebug( "Article::tryFileCache(): about to load file\n" );
2136  $cache->loadFromFileCache( $this->getContext() );
2137  return true;
2138  } else {
2139  wfDebug( "Article::tryFileCache(): starting buffer\n" );
2140  ob_start( [ &$cache, 'saveToFileCache' ] );
2141  }
2142  } else {
2143  wfDebug( "Article::tryFileCache(): not cacheable\n" );
2144  }
2145 
2146  return false;
2147  }
2148 
2154  public function isFileCacheable( $mode = HTMLFileCache::MODE_NORMAL ) {
2155  $cacheable = false;
2156 
2157  if ( HTMLFileCache::useFileCache( $this->getContext(), $mode ) ) {
2158  $cacheable = $this->mPage->getId()
2159  && !$this->mRedirectedFrom && !$this->getTitle()->isRedirect();
2160  // Extension may have reason to disable file caching on some pages.
2161  if ( $cacheable ) {
2162  // Avoid PHP 7.1 warning of passing $this by reference
2163  $articlePage = $this;
2164  $cacheable = Hooks::run( 'IsFileCacheable', [ &$articlePage ] );
2165  }
2166  }
2167 
2168  return $cacheable;
2169  }
2170 
2184  public function getParserOutput( $oldid = null, User $user = null ) {
2185  // XXX: bypasses mParserOptions and thus setParserOptions()
2186 
2187  if ( $user === null ) {
2188  $parserOptions = $this->getParserOptions();
2189  } else {
2190  $parserOptions = $this->mPage->makeParserOptions( $user );
2191  }
2192 
2193  return $this->mPage->getParserOutput( $parserOptions, $oldid );
2194  }
2195 
2203  if ( $this->mParserOptions ) {
2204  throw new MWException( "can't change parser options after they have already been set" );
2205  }
2206 
2207  // clone, so if $options is modified later, it doesn't confuse the parser cache.
2208  $this->mParserOptions = clone $options;
2209  }
2210 
2215  public function getParserOptions() {
2216  if ( !$this->mParserOptions ) {
2217  $this->mParserOptions = $this->mPage->makeParserOptions( $this->getContext() );
2218  }
2219  // Clone to allow modifications of the return value without affecting cache
2220  return clone $this->mParserOptions;
2221  }
2222 
2229  public function setContext( $context ) {
2230  $this->mContext = $context;
2231  }
2232 
2239  public function getContext() {
2240  if ( $this->mContext instanceof IContextSource ) {
2241  return $this->mContext;
2242  } else {
2243  wfDebug( __METHOD__ . " called and \$mContext is null. " .
2244  "Return RequestContext::getMain(); for sanity\n" );
2245  return RequestContext::getMain();
2246  }
2247  }
2248 
2256  public function __get( $fname ) {
2257  if ( property_exists( $this->mPage, $fname ) ) {
2258  # wfWarn( "Access to raw $fname field " . __CLASS__ );
2259  return $this->mPage->$fname;
2260  }
2261  trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
2262  }
2263 
2271  public function __set( $fname, $fvalue ) {
2272  if ( property_exists( $this->mPage, $fname ) ) {
2273  # wfWarn( "Access to raw $fname field of " . __CLASS__ );
2274  $this->mPage->$fname = $fvalue;
2275  // Note: extensions may want to toss on new fields
2276  } elseif ( !in_array( $fname, [ 'mContext', 'mPage' ] ) ) {
2277  $this->mPage->$fname = $fvalue;
2278  } else {
2279  trigger_error( 'Inaccessible property via __set(): ' . $fname, E_USER_NOTICE );
2280  }
2281  }
2282 
2287  public function checkFlags( $flags ) {
2288  return $this->mPage->checkFlags( $flags );
2289  }
2290 
2295  public function checkTouched() {
2296  return $this->mPage->checkTouched();
2297  }
2298 
2303  public function clearPreparedEdit() {
2304  $this->mPage->clearPreparedEdit();
2305  }
2306 
2311  public function doDeleteArticleReal(
2312  $reason, $suppress = false, $u1 = null, $u2 = null, &$error = '', User $user = null,
2313  $tags = [], $immediate = false
2314  ) {
2315  return $this->mPage->doDeleteArticleReal(
2316  $reason, $suppress, $u1, $u2, $error, $user, $tags, 'delete', $immediate
2317  );
2318  }
2319 
2324  public function doDeleteUpdates(
2325  $id,
2326  Content $content = null,
2327  $revision = null,
2328  User $user = null
2329  ) {
2330  $this->mPage->doDeleteUpdates( $id, $content, $revision, $user );
2331  }
2332 
2338  public function doEditContent( Content $content, $summary, $flags = 0, $originalRevId = false,
2339  User $user = null, $serialFormat = null
2340  ) {
2341  wfDeprecated( __METHOD__, '1.29' );
2342  return $this->mPage->doEditContent( $content, $summary, $flags, $originalRevId,
2343  $user, $serialFormat
2344  );
2345  }
2346 
2351  public function doEditUpdates( Revision $revision, User $user, array $options = [] ) {
2352  return $this->mPage->doEditUpdates( $revision, $user, $options );
2353  }
2354 
2361  public function doPurge() {
2362  return $this->mPage->doPurge();
2363  }
2364 
2369  public function doViewUpdates( User $user, $oldid = 0 ) {
2370  $this->mPage->doViewUpdates( $user, $oldid );
2371  }
2372 
2377  public function exists() {
2378  return $this->mPage->exists();
2379  }
2380 
2385  public function followRedirect() {
2386  return $this->mPage->followRedirect();
2387  }
2388 
2393  public function getActionOverrides() {
2394  return $this->mPage->getActionOverrides();
2395  }
2396 
2401  public function getAutoDeleteReason( &$hasHistory ) {
2402  return $this->mPage->getAutoDeleteReason( $hasHistory );
2403  }
2404 
2409  public function getCategories() {
2410  return $this->mPage->getCategories();
2411  }
2412 
2417  public function getComment( $audience = RevisionRecord::FOR_PUBLIC, User $user = null ) {
2418  return $this->mPage->getComment( $audience, $user );
2419  }
2420 
2425  public function getContentHandler() {
2426  return $this->mPage->getContentHandler();
2427  }
2428 
2433  public function getContentModel() {
2434  return $this->mPage->getContentModel();
2435  }
2436 
2441  public function getContributors() {
2442  return $this->mPage->getContributors();
2443  }
2444 
2449  public function getCreator( $audience = RevisionRecord::FOR_PUBLIC, User $user = null ) {
2450  return $this->mPage->getCreator( $audience, $user );
2451  }
2452 
2457  public function getDeletionUpdates( Content $content = null ) {
2458  return $this->mPage->getDeletionUpdates( $content );
2459  }
2460 
2465  public function getHiddenCategories() {
2466  return $this->mPage->getHiddenCategories();
2467  }
2468 
2473  public function getId() {
2474  return $this->mPage->getId();
2475  }
2476 
2481  public function getLatest() {
2482  return $this->mPage->getLatest();
2483  }
2484 
2489  public function getLinksTimestamp() {
2490  return $this->mPage->getLinksTimestamp();
2491  }
2492 
2497  public function getMinorEdit() {
2498  return $this->mPage->getMinorEdit();
2499  }
2500 
2505  public function getOldestRevision() {
2506  return $this->mPage->getOldestRevision();
2507  }
2508 
2513  public function getRedirectTarget() {
2514  return $this->mPage->getRedirectTarget();
2515  }
2516 
2521  public function getRedirectURL( $rt ) {
2522  return $this->mPage->getRedirectURL( $rt );
2523  }
2524 
2529  public function getRevision() {
2530  return $this->mPage->getRevision();
2531  }
2532 
2537  public function getTimestamp() {
2538  return $this->mPage->getTimestamp();
2539  }
2540 
2545  public function getTouched() {
2546  return $this->mPage->getTouched();
2547  }
2548 
2553  public function getUndoContent( Revision $undo, Revision $undoafter = null ) {
2554  return $this->mPage->getUndoContent( $undo, $undoafter );
2555  }
2556 
2561  public function getUser( $audience = RevisionRecord::FOR_PUBLIC, User $user = null ) {
2562  return $this->mPage->getUser( $audience, $user );
2563  }
2564 
2569  public function getUserText( $audience = RevisionRecord::FOR_PUBLIC, User $user = null ) {
2570  return $this->mPage->getUserText( $audience, $user );
2571  }
2572 
2577  public function hasViewableContent() {
2578  return $this->mPage->hasViewableContent();
2579  }
2580 
2585  public function insertOn( $dbw, $pageId = null ) {
2586  return $this->mPage->insertOn( $dbw, $pageId );
2587  }
2588 
2593  public function insertProtectNullRevision( $revCommentMsg, array $limit,
2594  array $expiry, $cascade, $reason, $user = null
2595  ) {
2596  return $this->mPage->insertProtectNullRevision( $revCommentMsg, $limit,
2597  $expiry, $cascade, $reason, $user
2598  );
2599  }
2600 
2605  public function insertRedirect() {
2606  return $this->mPage->insertRedirect();
2607  }
2608 
2613  public function insertRedirectEntry( Title $rt, $oldLatest = null ) {
2614  return $this->mPage->insertRedirectEntry( $rt, $oldLatest );
2615  }
2616 
2621  public function isCountable( $editInfo = false ) {
2622  return $this->mPage->isCountable( $editInfo );
2623  }
2624 
2629  public function isRedirect() {
2630  return $this->mPage->isRedirect();
2631  }
2632 
2637  public function loadFromRow( $data, $from ) {
2638  return $this->mPage->loadFromRow( $data, $from );
2639  }
2640 
2645  public function loadPageData( $from = 'fromdb' ) {
2646  $this->mPage->loadPageData( $from );
2647  }
2648 
2653  public function lockAndGetLatest() {
2654  return $this->mPage->lockAndGetLatest();
2655  }
2656 
2661  public function makeParserOptions( $context ) {
2662  return $this->mPage->makeParserOptions( $context );
2663  }
2664 
2669  public function pageDataFromId( $dbr, $id, $options = [] ) {
2670  return $this->mPage->pageDataFromId( $dbr, $id, $options );
2671  }
2672 
2677  public function pageDataFromTitle( $dbr, $title, $options = [] ) {
2678  return $this->mPage->pageDataFromTitle( $dbr, $title, $options );
2679  }
2680 
2685  public function prepareContentForEdit(
2686  Content $content, $revision = null, User $user = null,
2687  $serialFormat = null, $useCache = true
2688  ) {
2689  return $this->mPage->prepareContentForEdit(
2690  $content, $revision, $user,
2691  $serialFormat, $useCache
2692  );
2693  }
2694 
2699  public function protectDescription( array $limit, array $expiry ) {
2700  return $this->mPage->protectDescription( $limit, $expiry );
2701  }
2702 
2707  public function protectDescriptionLog( array $limit, array $expiry ) {
2708  return $this->mPage->protectDescriptionLog( $limit, $expiry );
2709  }
2710 
2715  public function replaceSectionAtRev( $sectionId, Content $sectionContent,
2716  $sectionTitle = '', $baseRevId = null
2717  ) {
2718  return $this->mPage->replaceSectionAtRev( $sectionId, $sectionContent,
2719  $sectionTitle, $baseRevId
2720  );
2721  }
2722 
2727  public function replaceSectionContent(
2728  $sectionId, Content $sectionContent, $sectionTitle = '', $edittime = null
2729  ) {
2730  return $this->mPage->replaceSectionContent(
2731  $sectionId, $sectionContent, $sectionTitle, $edittime
2732  );
2733  }
2734 
2739  public function setTimestamp( $ts ) {
2740  $this->mPage->setTimestamp( $ts );
2741  }
2742 
2747  public function shouldCheckParserCache( ParserOptions $parserOptions, $oldId ) {
2748  return $this->mPage->shouldCheckParserCache( $parserOptions, $oldId );
2749  }
2750 
2755  public function supportsSections() {
2756  return $this->mPage->supportsSections();
2757  }
2758 
2763  public function triggerOpportunisticLinksUpdate( ParserOutput $parserOutput ) {
2764  return $this->mPage->triggerOpportunisticLinksUpdate( $parserOutput );
2765  }
2766 
2771  public function updateCategoryCounts( array $added, array $deleted, $id = 0 ) {
2772  return $this->mPage->updateCategoryCounts( $added, $deleted, $id );
2773  }
2774 
2779  public function updateIfNewerOn( $dbw, $revision ) {
2780  return $this->mPage->updateIfNewerOn( $dbw, $revision );
2781  }
2782 
2787  public function updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect = null ) {
2788  return $this->mPage->updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect );
2789  }
2790 
2795  public function updateRevisionOn( $dbw, $revision, $lastRevision = null,
2796  $lastRevIsRedirect = null
2797  ) {
2798  return $this->mPage->updateRevisionOn( $dbw, $revision, $lastRevision,
2799  $lastRevIsRedirect
2800  );
2801  }
2802 
2811  public function doUpdateRestrictions( array $limit, array $expiry, &$cascade,
2812  $reason, User $user
2813  ) {
2814  return $this->mPage->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $user );
2815  }
2816 
2824  public function updateRestrictions( $limit = [], $reason = '',
2825  &$cascade = 0, $expiry = []
2826  ) {
2827  return $this->mPage->doUpdateRestrictions(
2828  $limit,
2829  $expiry,
2830  $cascade,
2831  $reason,
2832  $this->getContext()->getUser()
2833  );
2834  }
2835 
2847  public function doDeleteArticle(
2848  $reason, $suppress = false, $u1 = null, $u2 = null, &$error = '', $immediate = false
2849  ) {
2850  return $this->mPage->doDeleteArticle( $reason, $suppress, $u1, $u2, $error,
2851  null, $immediate );
2852  }
2853 
2863  public function doRollback( $fromP, $summary, $token, $bot, &$resultDetails, User $user = null ) {
2864  if ( !$user ) {
2865  $user = $this->getContext()->getUser();
2866  }
2867 
2868  return $this->mPage->doRollback( $fromP, $summary, $token, $bot, $resultDetails, $user );
2869  }
2870 
2879  public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser = null ) {
2880  if ( !$guser ) {
2881  $guser = $this->getContext()->getUser();
2882  }
2883 
2884  return $this->mPage->commitRollback( $fromP, $summary, $bot, $resultDetails, $guser );
2885  }
2886 
2891  public function generateReason( &$hasHistory ) {
2892  $title = $this->mPage->getTitle();
2894  return $handler->getAutoDeleteReason( $title, $hasHistory );
2895  }
2896 
2897  // ******
2898 }
getComment( $audience=RevisionRecord::FOR_PUBLIC, User $user=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2417
getRedirectTarget()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2513
lockAndGetLatest()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2653
return true to allow those checks to and false if checking is done remove or add to the links of a group of changes in EnhancedChangesList Hook subscribers can return false to omit this line from recentchanges use this to change the tables headers change it to an object instance and return false override the list derivative used $groups Array of ChangesListFilterGroup objects(added in 1.34) 'FileDeleteComplete' null for the local wiki Added in
Definition: hooks.txt:1529
getUndoContent(Revision $undo, Revision $undoafter=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2553
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped noclasses just before the function returns a value If you return an< a > element with HTML attributes $attribs and contents $html will be returned If you return $ret will be returned and may include noclasses & $html
Definition: hooks.txt:1972
deferred txt A few of the database updates required by various functions here can be deferred until after the result page is displayed to the user For updating the view updating the linked to tables after a etc PHP does not yet have any way to tell the server to actually return and disconnect while still running these but it might have such a feature in the future We handle these by creating a deferred update object and putting those objects on a global list
Definition: deferred.txt:11
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking, formatting, etc.
getRedirectedFrom()
Get the page this view was redirected from.
Definition: Article.php:203
return true to allow those checks to and false if checking is done remove or add to the links of a group of changes in EnhancedChangesList Hook subscribers can return false to omit this line from recentchanges use this to change the tables headers change it to an object instance and return false override the list derivative used $groups Array of ChangesListFilterGroup objects(added in 1.34) 'FileDeleteComplete' null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify $query
Definition: hooks.txt:1529
getLatest()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2481
setParserOptions(ParserOptions $options)
Override the ParserOptions used to render the primary article wikitext.
Definition: Article.php:2202
doEditUpdates(Revision $revision, User $user, array $options=[])
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2351
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:473
checkFlags( $flags)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2287
isCountable( $editInfo=false)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2621
exists()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2377
supportsSections()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2755
Show an error when the wiki is locked/read-only and the user tries to do something that requires writ...
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:926
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
protect()
action=protect handler
Definition: Article.php:1753
Status null $fetchResult
represents the outcome of fetchRevisionRecord().
Definition: Article.php:100
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2147
$wgNamespaceRobotPolicies
Robot policies per namespaces.
Wrapper allowing us to handle a system message as a Content object.
$wgDebugToolbar
Display the new debugging toolbar.
getPage()
Get the WikiPage object of this instance.
Definition: Article.php:231
static useFileCache(IContextSource $context, $mode=self::MODE_NORMAL)
Check if pages can be cached for this request/user.
updateCategoryCounts(array $added, array $deleted, $id=0)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2771
string bool $mRedirectUrl
URL to redirect to or false if none.
Definition: Article.php:83
if(!isset( $args[0])) $lang
ParserOptions null $mParserOptions
ParserOptions object for $wgUser articles.
Definition: Article.php:52
Special handling for category description pages, showing pages, subcategories and file that belong to...
adjustDisplayTitle(ParserOutput $pOutput)
Adjust title for pages with displaytitle, -{T|}- or language conversion.
Definition: Article.php:885
shouldCheckParserCache(ParserOptions $parserOptions, $oldId)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2747
doUpdateRestrictions(array $limit, array $expiry, &$cascade, $reason, User $user)
Definition: Article.php:2811
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
getParserOutput( $oldid=null, User $user=null)
#-
Definition: Article.php:2184
makeFetchErrorContent()
Returns a Content object representing any error in $this->fetchContent, or null if there is no such e...
Definition: Article.php:507
Class for viewing MediaWiki article and history.
Definition: Article.php:38
Page view caching in the file system.
getRedirectURL( $rt)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2521
followRedirect()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2385
__get( $fname)
Use PHP&#39;s magic __get handler to handle accessing of raw WikiPage fields for backwards compatibility...
Definition: Article.php:2256
Class for viewing MediaWiki file description pages.
Definition: ImagePage.php:31
doDeleteArticle( $reason, $suppress=false, $u1=null, $u2=null, &$error='', $immediate=false)
Definition: Article.php:2847
static getInstance( $id)
Get a cached instance of the specified type of cache object.
Definition: ObjectCache.php:92
triggerOpportunisticLinksUpdate(ParserOutput $parserOutput)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2763
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency MediaWikiServices
Definition: injection.txt:23
getOldIDFromRequest()
Sets $this->mRedirectUrl to a correct URL if the query parameters are incorrect.
Definition: Article.php:339
getDeletionUpdates(Content $content=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2457
setContext( $context)
Sets the context this Article is executed in.
Definition: Article.php:2229
A DatabaseBlock (unlike a SystemBlock) is stored in the database, may give rise to autoblocks and may...
const COMMENT_CHARACTER_LIMIT
Maximum length of a comment in UTF-8 characters.
fetchRevisionRecord()
Fetches the revision to work on.
Definition: Article.php:417
clearPreparedEdit()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2303
getRevisionRedirectTarget(RevisionRecord $revision)
Definition: Article.php:873
static numParam( $num)
Definition: Message.php:1051
replaceSectionContent( $sectionId, Content $sectionContent, $sectionTitle='', $edittime=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2727
__set( $fname, $fvalue)
Use PHP&#39;s magic __set handler to handle setting of raw WikiPage fields for backwards compatibility...
Definition: Article.php:2271
$wgUseNPPatrol
Use new page patrolling to check new pages on Special:Newpages.
This list may contain false positives That usually means there is additional text with links below the first Each row contains links to the first and second as well as the first line of the second redirect text
getContributors()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2441
updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2787
showMissingArticle()
Show the error text for a missing article.
Definition: Article.php:1349
loadFromRow( $data, $from)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2637
updateRevisionOn( $dbw, $revision, $lastRevision=null, $lastRevIsRedirect=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2795
protectDescription(array $limit, array $expiry)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2699
Mutable RevisionRecord implementation, for building new revision entries programmatically.
Class to simplify the use of log pages.
Definition: LogPage.php:33
setOldSubtitle( $oldid=0)
Generate the navigation links when browsing through an article revisions It shows the information as:...
Definition: Article.php:1528
return true to allow those checks to and false if checking is done remove or add to the links of a group of changes in EnhancedChangesList Hook subscribers can return false to omit this line from recentchanges use this to change the tables headers change it to an object instance and return false override the list derivative used $groups Array of ChangesListFilterGroup objects(added in 1.34) 'FileDeleteComplete' null for the local wiki Added should default to null in handler for backwards compatibility add a value to it if you want to add a cookie that have to vary cache options can modify as strings Extensions should add to this list prev or next refreshes the diff cache $unhide
Definition: hooks.txt:1529
static newCanonical( $context=null, $userLang=null)
Creates a "canonical" ParserOptions object.
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:3039
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:51
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable modifiable after all normalizations have been except for the $wgMaxImageArea check set to true or false to override the $wgMaxImageArea check result gives extension the possibility to transform it themselves $handler
Definition: hooks.txt:767
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output $out
Definition: hooks.txt:767
getContext()
Gets the context this Article is executed in.
Definition: Article.php:2239
Revision null $mRevision
Revision to be shown.
Definition: Article.php:109
pageDataFromTitle( $dbr, $title, $options=[])
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2677
bool $viewIsRenderAction
Whether render() was called.
Definition: Article.php:123
static newFatal( $message)
Factory function for fatal errors.
Definition: StatusValue.php:68
Status::newGood()` to allow deletion, and then `return false` from the hook function. Ensure you consume the 'ChangeTagAfterDelete' hook to carry out custom deletion actions. $tag:name of the tag $user:user initiating the action & $status:Status object. See above. 'ChangeTagsListActive':Allows you to nominate which of the tags your extension uses are in active use. & $tags:list of all active tags. Append to this array. 'ChangeTagsAfterUpdateTags':Called after tags have been updated with the ChangeTags::updateTags function. Params:$addedTags:tags effectively added in the update $removedTags:tags effectively removed in the update $prevTags:tags that were present prior to the update $rc_id:recentchanges table id $rev_id:revision table id $log_id:logging table id $params:tag params $rc:RecentChange being tagged when the tagging accompanies the action, or null $user:User who performed the tagging when the tagging is subsequent to the action, or null 'ChangeTagsAllowedAdd':Called when checking if a user can add tags to a change. & $allowedTags:List of all the tags the user is allowed to add. Any tags the user wants to add( $addTags) that are not in this array will cause it to fail. You may add or remove tags to this array as required. $addTags:List of tags user intends to add. $user:User who is adding the tags. 'ChangeUserGroups':Called before user groups are changed. $performer:The User who will perform the change $user:The User whose groups will be changed & $add:The groups that will be added & $remove:The groups that will be removed 'Collation::factory':Called if $wgCategoryCollation is an unknown collation. $collationName:Name of the collation in question & $collationObject:Null. Replace with a subclass of the Collation class that implements the collation given in $collationName. 'ConfirmEmailComplete':Called after a user 's email has been confirmed successfully. $user:user(object) whose email is being confirmed 'ContentAlterParserOutput':Modify parser output for a given content object. Called by Content::getParserOutput after parsing has finished. Can be used for changes that depend on the result of the parsing but have to be done before LinksUpdate is called(such as adding tracking categories based on the rendered HTML). $content:The Content to render $title:Title of the page, as context $parserOutput:ParserOutput to manipulate 'ContentGetParserOutput':Customize parser output for a given content object, called by AbstractContent::getParserOutput. May be used to override the normal model-specific rendering of page content. $content:The Content to render $title:Title of the page, as context $revId:The revision ID, as context $options:ParserOptions for rendering. To avoid confusing the parser cache, the output can only depend on parameters provided to this hook function, not on global state. $generateHtml:boolean, indicating whether full HTML should be generated. If false, generation of HTML may be skipped, but other information should still be present in the ParserOutput object. & $output:ParserOutput, to manipulate or replace 'ContentHandlerDefaultModelFor':Called when the default content model is determined for a given title. May be used to assign a different model for that title. $title:the Title in question & $model:the model name. Use with CONTENT_MODEL_XXX constants. 'ContentHandlerForModelID':Called when a ContentHandler is requested for a given content model name, but no entry for that model exists in $wgContentHandlers. Note:if your extension implements additional models via this hook, please use GetContentModels hook to make them known to core. $modeName:the requested content model name & $handler:set this to a ContentHandler object, if desired. 'ContentModelCanBeUsedOn':Called to determine whether that content model can be used on a given page. This is especially useful to prevent some content models to be used in some special location. $contentModel:ID of the content model in question $title:the Title in question. & $ok:Output parameter, whether it is OK to use $contentModel on $title. Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok. 'ContribsPager::getQueryInfo':Before the contributions query is about to run & $pager:Pager object for contributions & $queryInfo:The query for the contribs Pager 'ContribsPager::reallyDoQuery':Called before really executing the query for My Contributions & $data:an array of results of all contribs queries $pager:The ContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'ContributionsLineEnding':Called before a contributions HTML line is finished $page:SpecialPage object for contributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'ContributionsToolLinks':Change tool links above Special:Contributions $id:User identifier $title:User page title & $tools:Array of tool links $specialPage:SpecialPage instance for context and services. Can be either SpecialContributions or DeletedContributionsPage. Extensions should type hint against a generic SpecialPage though. 'ConvertContent':Called by AbstractContent::convert when a conversion to another content model is requested. Handler functions that modify $result should generally return false to disable further attempts at conversion. $content:The Content object to be converted. $toModel:The ID of the content model to convert to. $lossy:boolean indicating whether lossy conversion is allowed. & $result:Output parameter, in case the handler function wants to provide a converted Content object. Note that $result->getContentModel() must return $toModel. 'ContentSecurityPolicyDefaultSource':Modify the allowed CSP load sources. This affects all directives except for the script directive. If you want to add a script source, see ContentSecurityPolicyScriptSource hook. & $defaultSrc:Array of Content-Security-Policy allowed sources $policyConfig:Current configuration for the Content-Security-Policy header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyDirectives':Modify the content security policy directives. Use this only if ContentSecurityPolicyDefaultSource and ContentSecurityPolicyScriptSource do not meet your needs. & $directives:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyScriptSource':Modify the allowed CSP script sources. Note that you also have to use ContentSecurityPolicyDefaultSource if you want non-script sources to be loaded from whatever you add. & $scriptSrc:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'CustomEditor':When invoking the page editor Return true to allow the normal editor to be used, or false if implementing a custom editor, e.g. for a special namespace, etc. $article:Article being edited $user:User performing the edit 'DeletedContribsPager::reallyDoQuery':Called before really executing the query for Special:DeletedContributions Similar to ContribsPager::reallyDoQuery & $data:an array of results of all contribs queries $pager:The DeletedContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'DeletedContributionsLineEnding':Called before a DeletedContributions HTML line is finished. Similar to ContributionsLineEnding $page:SpecialPage object for DeletedContributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'DeleteUnknownPreferences':Called by the cleanupPreferences.php maintenance script to build a WHERE clause with which to delete preferences that are not known about. This hook is used by extensions that have dynamically-named preferences that should not be deleted in the usual cleanup process. For example, the Gadgets extension creates preferences prefixed with 'gadget-', and so anything with that prefix is excluded from the deletion. &where:An array that will be passed as the $cond parameter to IDatabase::select() to determine what will be deleted from the user_properties table. $db:The IDatabase object, useful for accessing $db->buildLike() etc. 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function. $differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable 's value is null. This hook can be used to inject content into said class member variable. $differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the "mark as patrolled" link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink". $differenceEngine:DifferenceEngine object & $markAsPatrolledLink:The "mark as patrolled" link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions. & $rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision 's author, whether the revision was marked as a minor edit or not, etc. $differenceEngine:DifferenceEngine object & $newHeader:The string containing the various #mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
Definition: hooks.txt:1244
isRedirect()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2629
__construct(Title $title, $oldId=null)
Constructor and clear the article.
Definition: Article.php:130
protectDescriptionLog(array $limit, array $expiry)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2707
Interface for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
Definition: Page.php:24
wfReadOnly()
Check whether the wiki is in read-only mode.
$wgDefaultRobotPolicy
Default robot policy.
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt
static getMain()
Get the RequestContext object associated with the main request.
prepareContentForEdit(Content $content, $revision=null, User $user=null, $serialFormat=null, $useCache=true)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2685
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:108
$wgUseFileCache
This will cache static pages for non-logged-in users to reduce database traffic on public sites...
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title e g db for database replication lag or jobqueue for job queue size converted to pseudo seconds It is possible to add more fields and they will be returned to the user in the API response after the basic globals have been set but before ordinary actions take place or wrap services the preferred way to define a new service is the $wgServiceWiringFiles array $services
Definition: hooks.txt:2205
pageDataFromId( $dbr, $id, $options=[])
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2669
showViewFooter()
Show the footer section of an ordinary page view.
Definition: Article.php:1148
static doWatchOrUnwatch( $watch, Title $title, User $user)
Watch or unwatch a page.
Definition: WatchAction.php:89
$wgUseFilePatrol
Use file patrolling to check new files on Special:Newfiles.
getRevisionFetched()
Get the fetched Revision object depending on request parameters or null on failure.
Definition: Article.php:561
doEditContent(Content $content, $summary, $flags=0, $originalRevId=false, User $user=null, $serialFormat=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2338
const NS_MEDIA
Definition: Defines.php:48
showRedirectedFromHeader()
If this request is a redirect view, send "redirected from" subtitle to the output.
Definition: Article.php:1059
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
makeParserOptions( $context)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2661
getRevision()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2529
generateReason(&$hasHistory)
Definition: Article.php:2891
getContent( $role, $audience=self::FOR_PUBLIC, User $user=null)
Returns the Content of the given slot of this revision.
static listDropDownOptions( $list, $params=[])
Build options for a drop-down box from a textual list.
Definition: Xml.php:539
static getRedirectHeaderHtml(Language $lang, $target, $forceKnown=false)
Return the HTML for the top of a redirect page.
Definition: Article.php:1690
static purgePatrolFooterCache( $articleID)
Purge the cache used to check if it is worth showing the patrol footer For example, it is done during re-uploads when file patrol is used.
Definition: Article.php:1340
insertRedirect()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2605
static isValid( $ip)
Validate an IP address.
Definition: IP.php:111
int null $mOldId
The oldid of the article that was requested to be shown, 0 for the current revision.
Definition: Article.php:77
hasViewableContent()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2577
IContextSource null $mContext
The context this Article is executed in.
Definition: Article.php:43
getTitle()
Get the title object of the article.
Definition: Article.php:221
$cache
Definition: mcc.php:33
getActionOverrides()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2393
doViewUpdates(User $user, $oldid=0)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2369
getTitle()
Get the title object of the article.
Definition: WikiPage.php:294
const NS_CATEGORY
Definition: Defines.php:74
doPurge()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2361
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:1972
doRollback( $fromP, $summary, $token, $bot, &$resultDetails, User $user=null)
Definition: Article.php:2863
updateRestrictions( $limit=[], $reason='', &$cascade=0, $expiry=[])
Definition: Article.php:2824
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition: Linker.php:845
render()
Handle action=render.
Definition: Article.php:1742
insertProtectNullRevision( $revCommentMsg, array $limit, array $expiry, $cascade, $reason, $user=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2593
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Definition: Article.php:1725
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition: hooks.txt:767
getCategories()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2409
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:912
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
confirmDelete( $reason)
Output deletion confirmation dialog.
Definition: Article.php:1896
const NS_FILE
Definition: Defines.php:66
$wgRedirectSources
If local interwikis are set up which allow redirects, set this regexp to restrict URLs which will be ...
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition: hooks.txt:1748
$wgArticleRobotPolicies
Robot policies per article.
static newFromConds( $conds, $fname=__METHOD__, $dbType=DB_REPLICA)
Find the first recent change matching some specific conditions.
WikiPage null $mPage
The WikiPage object of this instance.
Definition: Article.php:46
static getForTitle(Title $title)
Returns the appropriate ContentHandler singleton for the given title.
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
replaceSectionAtRev( $sectionId, Content $sectionContent, $sectionTitle='', $baseRevId=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2715
getContentHandler()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2425
const NS_MEDIAWIKI
Definition: Defines.php:68
doDelete( $reason, $suppress=false, $immediate=false)
Perform a deletion and output success or failure messages.
Definition: Article.php:2066
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings, LocalSettings).
Definition: Setup.php:131
insertOn( $dbw, $pageId=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2585
static makeUrl( $name, $urlaction='')
Definition: Skin.php:1193
getUser( $audience=RevisionRecord::FOR_PUBLIC, User $user=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2561
newPage(Title $title)
Definition: Article.php:139
Title null $mRedirectedFrom
Title from which we were redirected here, if any.
Definition: Article.php:80
setTimestamp( $ts)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2739
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:592
getOldestRevision()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2505
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...
clear()
Clear the object.
Definition: Article.php:238
bool $mContentLoaded
Is the target revision loaded? Set by fetchRevisionRecord().
Definition: Article.php:70
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
static isRegistered( $name)
Returns true if a hook has a function registered to it.
Definition: Hooks.php:80
getAutoDeleteReason(&$hasHistory)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2401
checkTouched()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2295
static getRevDeleteLink(User $user, Revision $rev, LinkTarget $title)
Get a revision-deletion link, or disabled link, or nothing, depending on user permissions & the setti...
Definition: Linker.php:2105
getId()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2473
getTimestamp()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2537
getUserText( $audience=RevisionRecord::FOR_PUBLIC, User $user=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2569
Content null $mContentObject
Content of the main slot of $this->mRevision.
Definition: Article.php:62
ParserOutput null false $mParserOutput
The ParserOutput generated for viewing the page, initialized by view().
Definition: Article.php:116
doDeleteArticleReal( $reason, $suppress=false, $u1=null, $u2=null, &$error='', User $user=null, $tags=[], $immediate=false)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2311
getMinorEdit()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2497
view()
This is the default action of the index.php entry point: just view the page of the given title...
Definition: Article.php:589
static linkKnown( $target, $html=null, $customAttribs=[], $query=[], $options=[ 'known'])
Identical to link(), except $options defaults to &#39;known&#39;.
Definition: Linker.php:141
getEmptyPageParserOutput(ParserOptions $options)
Returns ParserOutput to use when a page does not exist.
Definition: Article.php:313
getContentObject()
Returns a Content object representing the pages effective display content, not necessarily the revisi...
Definition: Article.php:270
static hidden( $name, $value, array $attribs=[])
Convenience function to produce an input element with type=hidden.
Definition: Html.php:796
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
isFileCacheable( $mode=HTMLFileCache::MODE_NORMAL)
Check if the page can be cached.
Definition: Article.php:2154
getOldID()
Definition: Article.php:326
showNamespaceHeader()
Show a header specific to the namespace currently being viewed, like [[MediaWiki:Talkpagetext]].
Definition: Article.php:1136
Show an error when a user tries to do something they do not have the necessary permissions for...
$wgDeleteRevisionsLimit
Optional to restrict deletion of pages with higher revision counts to users with the &#39;bigdelete&#39; perm...
static element( $element, $attribs=null, $contents='', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:41
tryFileCache()
checkLastModified returns true if it has taken care of all output to the client that is necessary for...
Definition: Article.php:2123
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on and they can depend only on the ResourceLoaderContext $context
Definition: hooks.txt:2621
insertRedirectEntry(Title $rt, $oldLatest=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2613
doDeleteUpdates( $id, Content $content=null, $revision=null, User $user=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2324
viewRedirect( $target, $appendSubtitle=true, $forceKnown=false)
Return the HTML for the top of a redirect page.
Definition: Article.php:1668
const POST_EDIT_COOKIE_KEY_PREFIX
Prefix of key for cookie used to pass post-edit state.
Definition: EditPage.php:203
static revComment(Revision $rev, $local=false, $isPublic=false, $useParentheses=true)
Wrap and format the given revision&#39;s comment block, if the current user is allowed to view it...
Definition: Linker.php:1572
int $mRevIdFetched
Revision ID of revision that was loaded.
Definition: Article.php:90
getSubstituteContent()
Returns Content object to use when the page does not exist.
Definition: Article.php:286
Page revision base class.
loadPageData( $from='fromdb')
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2645
static newFromID( $id)
Constructor from a page id.
Definition: Article.php:148
fetchContentObject()
Get text content object Does NOT follow redirects.
Definition: Article.php:400
const DB_REPLICA
Definition: defines.php:25
static newFromTitle( $title, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition: Article.php:160
isCurrent()
Returns true if the currently-referenced revision is the current edit to this page (and it exists)...
Definition: Article.php:543
unprotect()
action=unprotect handler (alias)
Definition: Article.php:1761
Handles the page protection UI and backend.
getTouched()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2545
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:535
static newFromWikiPage(WikiPage $page, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition: Article.php:192
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:118
static link( $target, $html=null, $customAttribs=[], $query=[], $options=[])
This function returns an HTML link to the given target.
Definition: Linker.php:85
getCreator( $audience=RevisionRecord::FOR_PUBLIC, User $user=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2449
$wgSend404Code
Some web hosts attempt to rewrite all responses with a 404 (not found) status code, mangling or hiding MediaWiki&#39;s output.
static makeContent( $text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
$content
Definition: pageupdater.txt:72
getRevIdFetched()
Use this to fetch the rev ID used on page views.
Definition: Article.php:577
const NS_USER_TALK
Definition: Defines.php:63
showDiffPage()
Show a diff page according to current request variables.
Definition: Article.php:900
applyContentOverride(Content $override)
Applies a content override by constructing a fake Revision object and assigning it to mRevision...
Definition: Article.php:527
updateIfNewerOn( $dbw, $revision)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2779
do that in ParserLimitReportFormat instead use this to modify the parameters of the image all existing parser cache entries will be invalid To avoid you ll need to handle that somehow(e.g. with the RejectParserCacheValue hook) because MediaWiki won 't do it for you. & $defaults also a ContextSource after deleting those rows but within the same transaction you ll probably need to make sure the header is varied on $request
Definition: hooks.txt:2621
showDeletedRevisionHeader()
If the revision requested for view is deleted, check permissions.
Definition: Article.php:1485
setRedirectedFrom(Title $from)
Tell the page view functions that this view was redirected from another page on the wiki...
Definition: Article.php:212
return true to allow those checks to and false if checking is done & $user
Definition: hooks.txt:1454
getContentModel()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2433
getLinksTimestamp()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2489
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser=null)
Definition: Article.php:2879
static listDropDownOptionsOoui( $options)
Convert options for a drop-down box into a format accepted by OOUI\DropdownInputWidget etc...
Definition: Xml.php:581
getParserOptions()
Get parser options suitable for rendering the primary article wikitext.
Definition: Article.php:2215
const RC_LOG
Definition: Defines.php:124
getRobotPolicy( $action, ParserOutput $pOutput=null)
Get the robot policy to be used for the current view.
Definition: Article.php:950
showPatrolFooter()
If patrol is possible, output a patrol UI box.
Definition: Article.php:1171
static showLogExtract(&$out, $types=[], $page='', $user='', $param=[])
Show log extract.
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:1119
static formatRobotPolicy( $policy)
Converts a String robot policy into an associative array, to allow merging of several policies using ...
Definition: Article.php:1030
getHiddenCategories()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2465