MediaWiki  master
Article.php
Go to the documentation of this file.
1 <?php
26 
37 class Article implements Page {
42  protected $mContext;
43 
45  protected $mPage;
46 
52 
62 
69  public $mContentLoaded = false;
70 
76  public $mOldId;
77 
80 
82  public $mRedirectUrl = false;
83 
89  public $mRevIdFetched = 0;
90 
99  private $fetchResult = null;
100 
108  public $mRevision = null;
109 
116 
122  protected $viewIsRenderAction = false;
123 
129  public function __construct( Title $title, $oldId = null ) {
130  $this->mOldId = $oldId;
131  $this->mPage = $this->newPage( $title );
132  }
133 
138  protected function newPage( Title $title ) {
139  return new WikiPage( $title );
140  }
141 
147  public static function newFromID( $id ) {
148  $t = Title::newFromID( $id );
149  return $t == null ? null : new static( $t );
150  }
151 
159  public static function newFromTitle( $title, IContextSource $context ) {
160  if ( NS_MEDIA == $title->getNamespace() ) {
161  // XXX: This should not be here, but where should it go?
162  $title = Title::makeTitle( NS_FILE, $title->getDBkey() );
163  }
164 
165  $page = null;
166  Hooks::run( 'ArticleFromTitle', [ &$title, &$page, $context ] );
167  if ( !$page ) {
168  switch ( $title->getNamespace() ) {
169  case NS_FILE:
170  $page = new ImagePage( $title );
171  break;
172  case NS_CATEGORY:
173  $page = new CategoryPage( $title );
174  break;
175  default:
176  $page = new Article( $title );
177  }
178  }
179  $page->setContext( $context );
180 
181  return $page;
182  }
183 
191  public static function newFromWikiPage( WikiPage $page, IContextSource $context ) {
192  $article = self::newFromTitle( $page->getTitle(), $context );
193  $article->mPage = $page; // override to keep process cached vars
194  return $article;
195  }
196 
202  public function getRedirectedFrom() {
203  return $this->mRedirectedFrom;
204  }
205 
211  public function setRedirectedFrom( Title $from ) {
212  $this->mRedirectedFrom = $from;
213  }
214 
220  public function getTitle() {
221  return $this->mPage->getTitle();
222  }
223 
230  public function getPage() {
231  return $this->mPage;
232  }
233 
237  public function clear() {
238  $this->mContentLoaded = false;
239 
240  $this->mRedirectedFrom = null; # Title object if set
241  $this->mRevIdFetched = 0;
242  $this->mRedirectUrl = false;
243  $this->mRevision = null;
244  $this->mContentObject = null;
245  $this->fetchResult = null;
246 
247  // TODO hard-deprecate direct access to public fields
248 
249  $this->mPage->clear();
250  }
251 
269  protected function getContentObject() {
270  if ( $this->mPage->getId() === 0 ) {
271  $content = $this->getSubstituteContent();
272  } else {
273  $this->fetchContentObject();
275  }
276 
277  return $content;
278  }
279 
285  private function getSubstituteContent() {
286  # If this is a MediaWiki:x message, then load the messages
287  # and return the message value for x.
288  if ( $this->getTitle()->getNamespace() == NS_MEDIAWIKI ) {
289  $text = $this->getTitle()->getDefaultMessageText();
290  if ( $text === false ) {
291  $text = '';
292  }
293 
294  $content = ContentHandler::makeContent( $text, $this->getTitle() );
295  } else {
296  $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
297  $content = new MessageContent( $message, null );
298  }
299 
300  return $content;
301  }
302 
313  $content = $this->getSubstituteContent();
314 
315  return $content->getParserOutput( $this->getTitle(), 0, $options );
316  }
317 
325  public function getOldID() {
326  if ( is_null( $this->mOldId ) ) {
327  $this->mOldId = $this->getOldIDFromRequest();
328  }
329 
330  return $this->mOldId;
331  }
332 
338  public function getOldIDFromRequest() {
339  $this->mRedirectUrl = false;
340 
341  $request = $this->getContext()->getRequest();
342  $oldid = $request->getIntOrNull( 'oldid' );
343 
344  if ( $oldid === null ) {
345  return 0;
346  }
347 
348  if ( $oldid !== 0 ) {
349  # Load the given revision and check whether the page is another one.
350  # In that case, update this instance to reflect the change.
351  if ( $oldid === $this->mPage->getLatest() ) {
352  $this->mRevision = $this->mPage->getRevision();
353  } else {
354  $this->mRevision = Revision::newFromId( $oldid );
355  if ( $this->mRevision !== null ) {
356  // Revision title doesn't match the page title given?
357  if ( $this->mPage->getId() != $this->mRevision->getPage() ) {
358  $function = get_class( $this->mPage ) . '::newFromID';
359  $this->mPage = $function( $this->mRevision->getPage() );
360  }
361  }
362  }
363  }
364 
365  if ( $request->getVal( 'direction' ) == 'next' ) {
366  $nextid = $this->getTitle()->getNextRevisionID( $oldid );
367  if ( $nextid ) {
368  $oldid = $nextid;
369  $this->mRevision = null;
370  } else {
371  $this->mRedirectUrl = $this->getTitle()->getFullURL( 'redirect=no' );
372  }
373  } elseif ( $request->getVal( 'direction' ) == 'prev' ) {
374  $previd = $this->getTitle()->getPreviousRevisionID( $oldid );
375  if ( $previd ) {
376  $oldid = $previd;
377  $this->mRevision = null;
378  }
379  }
380 
381  $this->mRevIdFetched = $this->mRevision ? $this->mRevision->getId() : 0;
382 
383  return $oldid;
384  }
385 
399  protected function fetchContentObject() {
400  if ( !$this->mContentLoaded ) {
401  $this->fetchRevisionRecord();
402  }
403 
404  return $this->mContentObject;
405  }
406 
416  protected function fetchRevisionRecord() {
417  if ( $this->fetchResult ) {
418  return $this->mRevision ? $this->mRevision->getRevisionRecord() : null;
419  }
420 
421  $this->mContentLoaded = true;
422  $this->mContentObject = null;
423 
424  $oldid = $this->getOldID();
425 
426  // $this->mRevision might already be fetched by getOldIDFromRequest()
427  if ( !$this->mRevision ) {
428  if ( !$oldid ) {
429  $this->mRevision = $this->mPage->getRevision();
430 
431  if ( !$this->mRevision ) {
432  wfDebug( __METHOD__ . " failed to find page data for title " .
433  $this->getTitle()->getPrefixedText() . "\n" );
434 
435  // Just for sanity, output for this case is done by showMissingArticle().
436  $this->fetchResult = Status::newFatal( 'noarticletext' );
437  $this->applyContentOverride( $this->makeFetchErrorContent() );
438  return null;
439  }
440  } else {
441  $this->mRevision = Revision::newFromId( $oldid );
442 
443  if ( !$this->mRevision ) {
444  wfDebug( __METHOD__ . " failed to load revision, rev_id $oldid\n" );
445 
446  $this->fetchResult = Status::newFatal( 'missing-revision', $oldid );
447  $this->applyContentOverride( $this->makeFetchErrorContent() );
448  return null;
449  }
450  }
451  }
452 
453  $this->mRevIdFetched = $this->mRevision->getId();
454  $this->fetchResult = Status::newGood( $this->mRevision );
455 
456  if ( !$this->mRevision->userCan( Revision::DELETED_TEXT, $this->getContext()->getUser() ) ) {
457  wfDebug( __METHOD__ . " failed to retrieve content of revision " .
458  $this->mRevision->getId() . "\n" );
459 
460  // Just for sanity, output for this case is done by showDeletedRevisionHeader().
461  $this->fetchResult = Status::newFatal( 'rev-deleted-text-permission' );
462  $this->applyContentOverride( $this->makeFetchErrorContent() );
463  return null;
464  }
465 
466  if ( Hooks::isRegistered( 'ArticleAfterFetchContentObject' ) ) {
467  $contentObject = $this->mRevision->getContent(
469  $this->getContext()->getUser()
470  );
471 
472  $hookContentObject = $contentObject;
473 
474  // Avoid PHP 7.1 warning of passing $this by reference
475  $articlePage = $this;
476 
477  Hooks::run(
478  'ArticleAfterFetchContentObject',
479  [ &$articlePage, &$hookContentObject ],
480  '1.32'
481  );
482 
483  if ( $hookContentObject !== $contentObject ) {
484  // A hook handler is trying to override the content
485  $this->applyContentOverride( $hookContentObject );
486  }
487  }
488 
489  // For B/C only
490  $this->mContentObject = $this->mRevision->getContent(
492  $this->getContext()->getUser()
493  );
494 
495  return $this->mRevision->getRevisionRecord();
496  }
497 
504  private function makeFetchErrorContent() {
505  if ( !$this->fetchResult || $this->fetchResult->isOK() ) {
506  return null;
507  }
508 
509  return new MessageContent( $this->fetchResult->getMessage() );
510  }
511 
524  private function applyContentOverride( Content $override ) {
525  // Construct a fake revision
526  $rev = new MutableRevisionRecord( $this->getTitle() );
527  $rev->setContent( SlotRecord::MAIN, $override );
528 
529  $this->mRevision = new Revision( $rev );
530 
531  // For B/C only
532  $this->mContentObject = $override;
533  }
534 
540  public function isCurrent() {
541  # If no oldid, this is the current version.
542  if ( $this->getOldID() == 0 ) {
543  return true;
544  }
545 
546  return $this->mPage->exists() && $this->mRevision && $this->mRevision->isCurrent();
547  }
548 
558  public function getRevisionFetched() {
559  $this->fetchRevisionRecord();
560 
561  if ( $this->fetchResult->isOK() ) {
562  return $this->mRevision;
563  }
564  }
565 
574  public function getRevIdFetched() {
575  if ( $this->fetchResult && $this->fetchResult->isOK() ) {
576  return $this->fetchResult->value->getId();
577  } else {
578  return $this->mPage->getLatest();
579  }
580  }
581 
586  public function view() {
588 
589  # Get variables from query string
590  # As side effect this will load the revision and update the title
591  # in a revision ID is passed in the request, so this should remain
592  # the first call of this method even if $oldid is used way below.
593  $oldid = $this->getOldID();
594 
595  $user = $this->getContext()->getUser();
596  # Another whitelist check in case getOldID() is altering the title
597  $permErrors = $this->getTitle()->getUserPermissionsErrors( 'read', $user );
598  if ( count( $permErrors ) ) {
599  wfDebug( __METHOD__ . ": denied on secondary read check\n" );
600  throw new PermissionsError( 'read', $permErrors );
601  }
602 
603  $outputPage = $this->getContext()->getOutput();
604  # getOldID() may as well want us to redirect somewhere else
605  if ( $this->mRedirectUrl ) {
606  $outputPage->redirect( $this->mRedirectUrl );
607  wfDebug( __METHOD__ . ": redirecting due to oldid\n" );
608 
609  return;
610  }
611 
612  # If we got diff in the query, we want to see a diff page instead of the article.
613  if ( $this->getContext()->getRequest()->getCheck( 'diff' ) ) {
614  wfDebug( __METHOD__ . ": showing diff page\n" );
615  $this->showDiffPage();
616 
617  return;
618  }
619 
620  # Set page title (may be overridden by DISPLAYTITLE)
621  $outputPage->setPageTitle( $this->getTitle()->getPrefixedText() );
622 
623  $outputPage->setArticleFlag( true );
624  # Allow frames by default
625  $outputPage->allowClickjacking();
626 
627  $parserCache = MediaWikiServices::getInstance()->getParserCache();
628 
629  $parserOptions = $this->getParserOptions();
630  $poOptions = [];
631  # Render printable version, use printable version cache
632  if ( $outputPage->isPrintable() ) {
633  $parserOptions->setIsPrintable( true );
634  $poOptions['enableSectionEditLinks'] = false;
635  } elseif ( $this->viewIsRenderAction
636  || !$this->isCurrent() || !$this->getTitle()->quickUserCan( 'edit', $user )
637  ) {
638  $poOptions['enableSectionEditLinks'] = false;
639  }
640 
641  # Try client and file cache
642  if ( !$wgDebugToolbar && $oldid === 0 && $this->mPage->checkTouched() ) {
643  # Try to stream the output from file cache
644  if ( $wgUseFileCache && $this->tryFileCache() ) {
645  wfDebug( __METHOD__ . ": done file cache\n" );
646  # tell wgOut that output is taken care of
647  $outputPage->disable();
648  $this->mPage->doViewUpdates( $user, $oldid );
649 
650  return;
651  }
652  }
653 
654  # Should the parser cache be used?
655  $useParserCache = $this->mPage->shouldCheckParserCache( $parserOptions, $oldid );
656  wfDebug( 'Article::view using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
657  if ( $user->getStubThreshold() ) {
658  MediaWikiServices::getInstance()->getStatsdDataFactory()->increment( 'pcache_miss_stub' );
659  }
660 
661  $this->showRedirectedFromHeader();
662  $this->showNamespaceHeader();
663 
664  # Iterate through the possible ways of constructing the output text.
665  # Keep going until $outputDone is set, or we run out of things to do.
666  $pass = 0;
667  $outputDone = false;
668  $this->mParserOutput = false;
669 
670  while ( !$outputDone && ++$pass ) {
671  switch ( $pass ) {
672  case 1:
673  // Avoid PHP 7.1 warning of passing $this by reference
674  $articlePage = $this;
675  Hooks::run( 'ArticleViewHeader', [ &$articlePage, &$outputDone, &$useParserCache ] );
676  break;
677  case 2:
678  # Early abort if the page doesn't exist
679  if ( !$this->mPage->exists() ) {
680  wfDebug( __METHOD__ . ": showing missing article\n" );
681  $this->showMissingArticle();
682  $this->mPage->doViewUpdates( $user );
683  return;
684  }
685 
686  # Try the parser cache
687  if ( $useParserCache ) {
688  $this->mParserOutput = $parserCache->get( $this->mPage, $parserOptions );
689 
690  if ( $this->mParserOutput !== false ) {
691  if ( $oldid ) {
692  wfDebug( __METHOD__ . ": showing parser cache contents for current rev permalink\n" );
693  $this->setOldSubtitle( $oldid );
694  } else {
695  wfDebug( __METHOD__ . ": showing parser cache contents\n" );
696  }
697  $outputPage->addParserOutput( $this->mParserOutput, $poOptions );
698  # Ensure that UI elements requiring revision ID have
699  # the correct version information.
700  $outputPage->setRevisionId( $this->mPage->getLatest() );
701  # Preload timestamp to avoid a DB hit
702  $cachedTimestamp = $this->mParserOutput->getTimestamp();
703  if ( $cachedTimestamp !== null ) {
704  $outputPage->setRevisionTimestamp( $cachedTimestamp );
705  $this->mPage->setTimestamp( $cachedTimestamp );
706  }
707  $outputDone = true;
708  }
709  }
710  break;
711  case 3:
712  # Are we looking at an old revision
713  $rev = $this->fetchRevisionRecord();
714  if ( $oldid && $this->fetchResult->isOK() ) {
715  $this->setOldSubtitle( $oldid );
716 
717  if ( !$this->showDeletedRevisionHeader() ) {
718  wfDebug( __METHOD__ . ": cannot view deleted revision\n" );
719  return;
720  }
721  }
722 
723  # Ensure that UI elements requiring revision ID have
724  # the correct version information.
725  $outputPage->setRevisionId( $this->getRevIdFetched() );
726  # Preload timestamp to avoid a DB hit
727  $outputPage->setRevisionTimestamp( $this->mPage->getTimestamp() );
728 
729  # Pages containing custom CSS or JavaScript get special treatment
730  if ( $this->getTitle()->isSiteConfigPage() || $this->getTitle()->isUserConfigPage() ) {
731  $dir = $this->getContext()->getLanguage()->getDir();
732  $lang = $this->getContext()->getLanguage()->getHtmlCode();
733 
734  $outputPage->wrapWikiMsg(
735  "<div id='mw-clearyourcache' lang='$lang' dir='$dir' class='mw-content-$dir'>\n$1\n</div>",
736  'clearyourcache'
737  );
738  } elseif ( !Hooks::run( 'ArticleRevisionViewCustom', [
739  $rev,
740  $this->getTitle(),
741  $oldid,
742  $outputPage,
743  ] )
744  ) {
745  // NOTE: sync with hooks called in DifferenceEngine::renderNewRevision()
746  // Allow extensions do their own custom view for certain pages
747  $outputDone = true;
748  } elseif ( !Hooks::run( 'ArticleContentViewCustom',
749  [ $this->fetchContentObject(), $this->getTitle(), $outputPage ], '1.32' )
750  ) {
751  // NOTE: sync with hooks called in DifferenceEngine::renderNewRevision()
752  // Allow extensions do their own custom view for certain pages
753  $outputDone = true;
754  }
755  break;
756  case 4:
757  # Run the parse, protected by a pool counter
758  wfDebug( __METHOD__ . ": doing uncached parse\n" );
759 
760  $rev = $this->fetchRevisionRecord();
761  $error = null;
762 
763  if ( $rev ) {
764  $poolArticleView = new PoolWorkArticleView(
765  $this->getPage(),
766  $parserOptions,
767  $this->getRevIdFetched(),
768  $useParserCache,
769  $rev,
770  // permission checking was done earlier via showDeletedRevisionHeader()
771  RevisionRecord::RAW
772  );
773  $ok = $poolArticleView->execute();
774  $error = $poolArticleView->getError();
775  $this->mParserOutput = $poolArticleView->getParserOutput() ?: null;
776 
777  # Don't cache a dirty ParserOutput object
778  if ( $poolArticleView->getIsDirty() ) {
779  $outputPage->setCdnMaxage( 0 );
780  $outputPage->addHTML( "<!-- parser cache is expired, " .
781  "sending anyway due to pool overload-->\n" );
782  }
783  } else {
784  $ok = false;
785  }
786 
787  if ( !$ok ) {
788  if ( $error ) {
789  $outputPage->clearHTML(); // for release() errors
790  $outputPage->enableClientCache( false );
791  $outputPage->setRobotPolicy( 'noindex,nofollow' );
792 
793  $errortext = $error->getWikiText( false, 'view-pool-error' );
794  $outputPage->wrapWikiTextAsInterface( 'errorbox', $errortext );
795  }
796  # Connection or timeout error
797  return;
798  }
799 
800  if ( $this->mParserOutput ) {
801  $outputPage->addParserOutput( $this->mParserOutput, $poOptions );
802  }
803 
804  if ( $rev && $this->getRevisionRedirectTarget( $rev ) ) {
805  $outputPage->addSubtitle( "<span id=\"redirectsub\">" .
806  $this->getContext()->msg( 'redirectpagesub' )->parse() . "</span>" );
807  }
808 
809  $outputDone = true;
810  break;
811  # Should be unreachable, but just in case...
812  default:
813  break 2;
814  }
815  }
816 
817  // Get the ParserOutput actually *displayed* here.
818  // Note that $this->mParserOutput is the *current*/oldid version output.
819  // Note that the ArticleViewHeader hook is allowed to set $outputDone to a
820  // ParserOutput instance.
821  $pOutput = ( $outputDone instanceof ParserOutput )
822  ? $outputDone // object fetched by hook
823  : ( $this->mParserOutput ?: null ); // ParserOutput or null, avoid false
824 
825  # Adjust title for main page & pages with displaytitle
826  if ( $pOutput ) {
827  $this->adjustDisplayTitle( $pOutput );
828  }
829 
830  # For the main page, overwrite the <title> element with the con-
831  # tents of 'pagetitle-view-mainpage' instead of the default (if
832  # that's not empty).
833  # This message always exists because it is in the i18n files
834  if ( $this->getTitle()->isMainPage() ) {
835  $msg = wfMessage( 'pagetitle-view-mainpage' )->inContentLanguage();
836  if ( !$msg->isDisabled() ) {
837  $outputPage->setHTMLTitle( $msg->title( $this->getTitle() )->text() );
838  }
839  }
840 
841  # Use adaptive TTLs for CDN so delayed/failed purges are noticed less often.
842  # This could use getTouched(), but that could be scary for major template edits.
843  $outputPage->adaptCdnTTL( $this->mPage->getTimestamp(), IExpiringStore::TTL_DAY );
844 
845  # Check for any __NOINDEX__ tags on the page using $pOutput
846  $policy = $this->getRobotPolicy( 'view', $pOutput ?: null );
847  $outputPage->setIndexPolicy( $policy['index'] );
848  $outputPage->setFollowPolicy( $policy['follow'] ); // FIXME: test this
849 
850  $this->showViewFooter();
851  $this->mPage->doViewUpdates( $user, $oldid ); // FIXME: test this
852 
853  # Load the postEdit module if the user just saved this revision
854  # See also EditPage::setPostEditCookie
855  $request = $this->getContext()->getRequest();
857  $postEdit = $request->getCookie( $cookieKey );
858  if ( $postEdit ) {
859  # Clear the cookie. This also prevents caching of the response.
860  $request->response()->clearCookie( $cookieKey );
861  $outputPage->addJsConfigVars( 'wgPostEdit', $postEdit );
862  $outputPage->addModules( 'mediawiki.action.view.postEdit' ); // FIXME: test this
863  }
864  }
865 
870  private function getRevisionRedirectTarget( RevisionRecord $revision ) {
871  // TODO: find a *good* place for the code that determines the redirect target for
872  // a given revision!
873  // NOTE: Use main slot content. Compare code in DerivedPageDataUpdater::revisionIsRedirect.
874  $content = $revision->getContent( SlotRecord::MAIN );
875  return $content ? $content->getRedirectTarget() : null;
876  }
877 
882  public function adjustDisplayTitle( ParserOutput $pOutput ) {
883  $out = $this->getContext()->getOutput();
884 
885  # Adjust the title if it was set by displaytitle, -{T|}- or language conversion
886  $titleText = $pOutput->getTitleText();
887  if ( strval( $titleText ) !== '' ) {
888  $out->setPageTitle( $titleText );
889  $out->setDisplayTitle( $titleText );
890  }
891  }
892 
897  protected function showDiffPage() {
898  $request = $this->getContext()->getRequest();
899  $user = $this->getContext()->getUser();
900  $diff = $request->getVal( 'diff' );
901  $rcid = $request->getVal( 'rcid' );
902  $diffOnly = $request->getBool( 'diffonly', $user->getOption( 'diffonly' ) );
903  $purge = $request->getVal( 'action' ) == 'purge';
904  $unhide = $request->getInt( 'unhide' ) == 1;
905  $oldid = $this->getOldID();
906 
907  $rev = $this->getRevisionFetched();
908 
909  if ( !$rev ) {
910  $this->getContext()->getOutput()->setPageTitle( wfMessage( 'errorpagetitle' ) );
911  $msg = $this->getContext()->msg( 'difference-missing-revision' )
912  ->params( $oldid )
913  ->numParams( 1 )
914  ->parseAsBlock();
915  $this->getContext()->getOutput()->addHTML( $msg );
916  return;
917  }
918 
919  $contentHandler = $rev->getContentHandler();
920  $de = $contentHandler->createDifferenceEngine(
921  $this->getContext(),
922  $oldid,
923  $diff,
924  $rcid,
925  $purge,
926  $unhide
927  );
928 
929  // DifferenceEngine directly fetched the revision:
930  $this->mRevIdFetched = $de->getNewid();
931  $de->showDiffPage( $diffOnly );
932 
933  // Run view updates for the newer revision being diffed (and shown
934  // below the diff if not $diffOnly).
935  list( $old, $new ) = $de->mapDiffPrevNext( $oldid, $diff );
936  // New can be false, convert it to 0 - this conveniently means the latest revision
937  $this->mPage->doViewUpdates( $user, (int)$new );
938  }
939 
947  public function getRobotPolicy( $action, ParserOutput $pOutput = null ) {
949 
950  $ns = $this->getTitle()->getNamespace();
951 
952  # Don't index user and user talk pages for blocked users (T13443)
953  if ( ( $ns == NS_USER || $ns == NS_USER_TALK ) && !$this->getTitle()->isSubpage() ) {
954  $specificTarget = null;
955  $vagueTarget = null;
956  $titleText = $this->getTitle()->getText();
957  if ( IP::isValid( $titleText ) ) {
958  $vagueTarget = $titleText;
959  } else {
960  $specificTarget = $titleText;
961  }
962  if ( Block::newFromTarget( $specificTarget, $vagueTarget ) instanceof Block ) {
963  return [
964  'index' => 'noindex',
965  'follow' => 'nofollow'
966  ];
967  }
968  }
969 
970  if ( $this->mPage->getId() === 0 || $this->getOldID() ) {
971  # Non-articles (special pages etc), and old revisions
972  return [
973  'index' => 'noindex',
974  'follow' => 'nofollow'
975  ];
976  } elseif ( $this->getContext()->getOutput()->isPrintable() ) {
977  # Discourage indexing of printable versions, but encourage following
978  return [
979  'index' => 'noindex',
980  'follow' => 'follow'
981  ];
982  } elseif ( $this->getContext()->getRequest()->getInt( 'curid' ) ) {
983  # For ?curid=x urls, disallow indexing
984  return [
985  'index' => 'noindex',
986  'follow' => 'follow'
987  ];
988  }
989 
990  # Otherwise, construct the policy based on the various config variables.
991  $policy = self::formatRobotPolicy( $wgDefaultRobotPolicy );
992 
993  if ( isset( $wgNamespaceRobotPolicies[$ns] ) ) {
994  # Honour customised robot policies for this namespace
995  $policy = array_merge(
996  $policy,
997  self::formatRobotPolicy( $wgNamespaceRobotPolicies[$ns] )
998  );
999  }
1000  if ( $this->getTitle()->canUseNoindex() && is_object( $pOutput ) && $pOutput->getIndexPolicy() ) {
1001  # __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates
1002  # a final sanity check that we have really got the parser output.
1003  $policy = array_merge(
1004  $policy,
1005  [ 'index' => $pOutput->getIndexPolicy() ]
1006  );
1007  }
1008 
1009  if ( isset( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] ) ) {
1010  # (T16900) site config can override user-defined __INDEX__ or __NOINDEX__
1011  $policy = array_merge(
1012  $policy,
1013  self::formatRobotPolicy( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] )
1014  );
1015  }
1016 
1017  return $policy;
1018  }
1019 
1027  public static function formatRobotPolicy( $policy ) {
1028  if ( is_array( $policy ) ) {
1029  return $policy;
1030  } elseif ( !$policy ) {
1031  return [];
1032  }
1033 
1034  $policy = explode( ',', $policy );
1035  $policy = array_map( 'trim', $policy );
1036 
1037  $arr = [];
1038  foreach ( $policy as $var ) {
1039  if ( in_array( $var, [ 'index', 'noindex' ] ) ) {
1040  $arr['index'] = $var;
1041  } elseif ( in_array( $var, [ 'follow', 'nofollow' ] ) ) {
1042  $arr['follow'] = $var;
1043  }
1044  }
1045 
1046  return $arr;
1047  }
1048 
1056  public function showRedirectedFromHeader() {
1057  global $wgRedirectSources;
1058 
1059  $context = $this->getContext();
1060  $outputPage = $context->getOutput();
1061  $request = $context->getRequest();
1062  $rdfrom = $request->getVal( 'rdfrom' );
1063 
1064  // Construct a URL for the current page view, but with the target title
1065  $query = $request->getValues();
1066  unset( $query['rdfrom'] );
1067  unset( $query['title'] );
1068  if ( $this->getTitle()->isRedirect() ) {
1069  // Prevent double redirects
1070  $query['redirect'] = 'no';
1071  }
1072  $redirectTargetUrl = $this->getTitle()->getLinkURL( $query );
1073 
1074  if ( isset( $this->mRedirectedFrom ) ) {
1075  // Avoid PHP 7.1 warning of passing $this by reference
1076  $articlePage = $this;
1077 
1078  // This is an internally redirected page view.
1079  // We'll need a backlink to the source page for navigation.
1080  if ( Hooks::run( 'ArticleViewRedirect', [ &$articlePage ] ) ) {
1081  $redir = Linker::linkKnown(
1082  $this->mRedirectedFrom,
1083  null,
1084  [],
1085  [ 'redirect' => 'no' ]
1086  );
1087 
1088  $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
1089  $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
1090  . "</span>" );
1091 
1092  // Add the script to update the displayed URL and
1093  // set the fragment if one was specified in the redirect
1094  $outputPage->addJsConfigVars( [
1095  'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1096  ] );
1097  $outputPage->addModules( 'mediawiki.action.view.redirect' );
1098 
1099  // Add a <link rel="canonical"> tag
1100  $outputPage->setCanonicalUrl( $this->getTitle()->getCanonicalURL() );
1101 
1102  // Tell the output object that the user arrived at this article through a redirect
1103  $outputPage->setRedirectedFrom( $this->mRedirectedFrom );
1104 
1105  return true;
1106  }
1107  } elseif ( $rdfrom ) {
1108  // This is an externally redirected view, from some other wiki.
1109  // If it was reported from a trusted site, supply a backlink.
1110  if ( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) {
1111  $redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
1112  $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
1113  $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
1114  . "</span>" );
1115 
1116  // Add the script to update the displayed URL
1117  $outputPage->addJsConfigVars( [
1118  'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
1119  ] );
1120  $outputPage->addModules( 'mediawiki.action.view.redirect' );
1121 
1122  return true;
1123  }
1124  }
1125 
1126  return false;
1127  }
1128 
1133  public function showNamespaceHeader() {
1134  if ( $this->getTitle()->isTalkPage() && !wfMessage( 'talkpageheader' )->isDisabled() ) {
1135  $this->getContext()->getOutput()->wrapWikiMsg(
1136  "<div class=\"mw-talkpageheader\">\n$1\n</div>",
1137  [ 'talkpageheader' ]
1138  );
1139  }
1140  }
1141 
1145  public function showViewFooter() {
1146  # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
1147  if ( $this->getTitle()->getNamespace() == NS_USER_TALK
1148  && IP::isValid( $this->getTitle()->getText() )
1149  ) {
1150  $this->getContext()->getOutput()->addWikiMsg( 'anontalkpagetext' );
1151  }
1152 
1153  // Show a footer allowing the user to patrol the shown revision or page if possible
1154  $patrolFooterShown = $this->showPatrolFooter();
1155 
1156  Hooks::run( 'ArticleViewFooter', [ $this, $patrolFooterShown ] );
1157  }
1158 
1168  public function showPatrolFooter() {
1170 
1171  // Allow hooks to decide whether to not output this at all
1172  if ( !Hooks::run( 'ArticleShowPatrolFooter', [ $this ] ) ) {
1173  return false;
1174  }
1175 
1176  $outputPage = $this->getContext()->getOutput();
1177  $user = $this->getContext()->getUser();
1178  $title = $this->getTitle();
1179  $rc = false;
1180 
1181  if ( !$title->quickUserCan( 'patrol', $user )
1182  || !( $wgUseRCPatrol || $wgUseNPPatrol
1183  || ( $wgUseFilePatrol && $title->inNamespace( NS_FILE ) ) )
1184  ) {
1185  // Patrolling is disabled or the user isn't allowed to
1186  return false;
1187  }
1188 
1189  if ( $this->mRevision
1190  && !RecentChange::isInRCLifespan( $this->mRevision->getTimestamp(), 21600 )
1191  ) {
1192  // The current revision is already older than what could be in the RC table
1193  // 6h tolerance because the RC might not be cleaned out regularly
1194  return false;
1195  }
1196 
1197  // Check for cached results
1198  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1199  $key = $cache->makeKey( 'unpatrollable-page', $title->getArticleID() );
1200  if ( $cache->get( $key ) ) {
1201  return false;
1202  }
1203 
1204  $dbr = wfGetDB( DB_REPLICA );
1205  $oldestRevisionTimestamp = $dbr->selectField(
1206  'revision',
1207  'MIN( rev_timestamp )',
1208  [ 'rev_page' => $title->getArticleID() ],
1209  __METHOD__
1210  );
1211 
1212  // New page patrol: Get the timestamp of the oldest revison which
1213  // the revision table holds for the given page. Then we look
1214  // whether it's within the RC lifespan and if it is, we try
1215  // to get the recentchanges row belonging to that entry
1216  // (with rc_new = 1).
1217  $recentPageCreation = false;
1218  if ( $oldestRevisionTimestamp
1219  && RecentChange::isInRCLifespan( $oldestRevisionTimestamp, 21600 )
1220  ) {
1221  // 6h tolerance because the RC might not be cleaned out regularly
1222  $recentPageCreation = true;
1224  [
1225  'rc_new' => 1,
1226  'rc_timestamp' => $oldestRevisionTimestamp,
1227  'rc_namespace' => $title->getNamespace(),
1228  'rc_cur_id' => $title->getArticleID()
1229  ],
1230  __METHOD__
1231  );
1232  if ( $rc ) {
1233  // Use generic patrol message for new pages
1234  $markPatrolledMsg = wfMessage( 'markaspatrolledtext' );
1235  }
1236  }
1237 
1238  // File patrol: Get the timestamp of the latest upload for this page,
1239  // check whether it is within the RC lifespan and if it is, we try
1240  // to get the recentchanges row belonging to that entry
1241  // (with rc_type = RC_LOG, rc_log_type = upload).
1242  $recentFileUpload = false;
1243  if ( ( !$rc || $rc->getAttribute( 'rc_patrolled' ) ) && $wgUseFilePatrol
1244  && $title->getNamespace() === NS_FILE ) {
1245  // Retrieve timestamp of most recent upload
1246  $newestUploadTimestamp = $dbr->selectField(
1247  'image',
1248  'MAX( img_timestamp )',
1249  [ 'img_name' => $title->getDBkey() ],
1250  __METHOD__
1251  );
1252  if ( $newestUploadTimestamp
1253  && RecentChange::isInRCLifespan( $newestUploadTimestamp, 21600 )
1254  ) {
1255  // 6h tolerance because the RC might not be cleaned out regularly
1256  $recentFileUpload = true;
1258  [
1259  'rc_type' => RC_LOG,
1260  'rc_log_type' => 'upload',
1261  'rc_timestamp' => $newestUploadTimestamp,
1262  'rc_namespace' => NS_FILE,
1263  'rc_cur_id' => $title->getArticleID()
1264  ],
1265  __METHOD__
1266  );
1267  if ( $rc ) {
1268  // Use patrol message specific to files
1269  $markPatrolledMsg = wfMessage( 'markaspatrolledtext-file' );
1270  }
1271  }
1272  }
1273 
1274  if ( !$recentPageCreation && !$recentFileUpload ) {
1275  // Page creation and latest upload (for files) is too old to be in RC
1276 
1277  // We definitely can't patrol so cache the information
1278  // When a new file version is uploaded, the cache is cleared
1279  $cache->set( $key, '1' );
1280 
1281  return false;
1282  }
1283 
1284  if ( !$rc ) {
1285  // Don't cache: This can be hit if the page gets accessed very fast after
1286  // its creation / latest upload or in case we have high replica DB lag. In case
1287  // the revision is too old, we will already return above.
1288  return false;
1289  }
1290 
1291  if ( $rc->getAttribute( 'rc_patrolled' ) ) {
1292  // Patrolled RC entry around
1293 
1294  // Cache the information we gathered above in case we can't patrol
1295  // Don't cache in case we can patrol as this could change
1296  $cache->set( $key, '1' );
1297 
1298  return false;
1299  }
1300 
1301  if ( $rc->getPerformer()->equals( $user ) ) {
1302  // Don't show a patrol link for own creations/uploads. If the user could
1303  // patrol them, they already would be patrolled
1304  return false;
1305  }
1306 
1307  $outputPage->preventClickjacking();
1308  if ( $user->isAllowed( 'writeapi' ) ) {
1309  $outputPage->addModules( 'mediawiki.page.patrol.ajax' );
1310  }
1311 
1313  $title,
1314  $markPatrolledMsg->escaped(),
1315  [],
1316  [
1317  'action' => 'markpatrolled',
1318  'rcid' => $rc->getAttribute( 'rc_id' ),
1319  ]
1320  );
1321 
1322  $outputPage->addHTML(
1323  "<div class='patrollink' data-mw='interface'>" .
1324  wfMessage( 'markaspatrolledlink' )->rawParams( $link )->escaped() .
1325  '</div>'
1326  );
1327 
1328  return true;
1329  }
1330 
1337  public static function purgePatrolFooterCache( $articleID ) {
1338  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1339  $cache->delete( $cache->makeKey( 'unpatrollable-page', $articleID ) );
1340  }
1341 
1346  public function showMissingArticle() {
1347  global $wgSend404Code;
1348 
1349  $outputPage = $this->getContext()->getOutput();
1350  // Whether the page is a root user page of an existing user (but not a subpage)
1351  $validUserPage = false;
1352 
1353  $title = $this->getTitle();
1354 
1355  # Show info in user (talk) namespace. Does the user exist? Is he blocked?
1356  if ( $title->getNamespace() == NS_USER
1357  || $title->getNamespace() == NS_USER_TALK
1358  ) {
1359  $rootPart = explode( '/', $title->getText() )[0];
1360  $user = User::newFromName( $rootPart, false /* allow IP users */ );
1361  $ip = User::isIP( $rootPart );
1362  $block = Block::newFromTarget( $user, $user );
1363 
1364  if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
1365  $outputPage->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
1366  [ 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) ] );
1367  } elseif (
1368  !is_null( $block ) &&
1369  $block->getType() != Block::TYPE_AUTO &&
1370  ( $block->isSitewide() || $user->isBlockedFrom( $title ) )
1371  ) {
1372  // Show log extract if the user is sitewide blocked or is partially
1373  // blocked and not allowed to edit their user page or user talk page
1375  $outputPage,
1376  'block',
1377  MWNamespace::getCanonicalName( NS_USER ) . ':' . $block->getTarget(),
1378  '',
1379  [
1380  'lim' => 1,
1381  'showIfEmpty' => false,
1382  'msgKey' => [
1383  'blocked-notice-logextract',
1384  $user->getName() # Support GENDER in notice
1385  ]
1386  ]
1387  );
1388  $validUserPage = !$title->isSubpage();
1389  } else {
1390  $validUserPage = !$title->isSubpage();
1391  }
1392  }
1393 
1394  Hooks::run( 'ShowMissingArticle', [ $this ] );
1395 
1396  # Show delete and move logs if there were any such events.
1397  # The logging query can DOS the site when bots/crawlers cause 404 floods,
1398  # so be careful showing this. 404 pages must be cheap as they are hard to cache.
1399  $cache = MediaWikiServices::getInstance()->getMainObjectStash();
1400  $key = $cache->makeKey( 'page-recent-delete', md5( $title->getPrefixedText() ) );
1401  $loggedIn = $this->getContext()->getUser()->isLoggedIn();
1402  $sessionExists = $this->getContext()->getRequest()->getSession()->isPersistent();
1403  if ( $loggedIn || $cache->get( $key ) || $sessionExists ) {
1404  $logTypes = [ 'delete', 'move', 'protect' ];
1405 
1406  $dbr = wfGetDB( DB_REPLICA );
1407 
1408  $conds = [ 'log_action != ' . $dbr->addQuotes( 'revision' ) ];
1409  // Give extensions a chance to hide their (unrelated) log entries
1410  Hooks::run( 'Article::MissingArticleConditions', [ &$conds, $logTypes ] );
1412  $outputPage,
1413  $logTypes,
1414  $title,
1415  '',
1416  [
1417  'lim' => 10,
1418  'conds' => $conds,
1419  'showIfEmpty' => false,
1420  'msgKey' => [ $loggedIn || $sessionExists
1421  ? 'moveddeleted-notice'
1422  : 'moveddeleted-notice-recent'
1423  ]
1424  ]
1425  );
1426  }
1427 
1428  if ( !$this->mPage->hasViewableContent() && $wgSend404Code && !$validUserPage ) {
1429  // If there's no backing content, send a 404 Not Found
1430  // for better machine handling of broken links.
1431  $this->getContext()->getRequest()->response()->statusHeader( 404 );
1432  }
1433 
1434  // Also apply the robot policy for nonexisting pages (even if a 404 was used for sanity)
1435  $policy = $this->getRobotPolicy( 'view' );
1436  $outputPage->setIndexPolicy( $policy['index'] );
1437  $outputPage->setFollowPolicy( $policy['follow'] );
1438 
1439  $hookResult = Hooks::run( 'BeforeDisplayNoArticleText', [ $this ] );
1440 
1441  if ( !$hookResult ) {
1442  return;
1443  }
1444 
1445  # Show error message
1446  $oldid = $this->getOldID();
1447  if ( !$oldid && $title->getNamespace() === NS_MEDIAWIKI && $title->hasSourceText() ) {
1448  // use fake Content object for system message
1449  $parserOptions = ParserOptions::newCanonical( 'canonical' );
1450  $outputPage->addParserOutput( $this->getEmptyPageParserOutput( $parserOptions ) );
1451  } else {
1452  if ( $oldid ) {
1453  $text = wfMessage( 'missing-revision', $oldid )->plain();
1454  } elseif ( $title->quickUserCan( 'create', $this->getContext()->getUser() )
1455  && $title->quickUserCan( 'edit', $this->getContext()->getUser() )
1456  ) {
1457  $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
1458  $text = wfMessage( $message )->plain();
1459  } else {
1460  $text = wfMessage( 'noarticletext-nopermission' )->plain();
1461  }
1462 
1463  $dir = $this->getContext()->getLanguage()->getDir();
1464  $lang = $this->getContext()->getLanguage()->getHtmlCode();
1465  $outputPage->addWikiTextAsInterface( Xml::openElement( 'div', [
1466  'class' => "noarticletext mw-content-$dir",
1467  'dir' => $dir,
1468  'lang' => $lang,
1469  ] ) . "\n$text\n</div>" );
1470  }
1471  }
1472 
1479  public function showDeletedRevisionHeader() {
1480  if ( !$this->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
1481  // Not deleted
1482  return true;
1483  }
1484 
1485  $outputPage = $this->getContext()->getOutput();
1486  $user = $this->getContext()->getUser();
1487  // If the user is not allowed to see it...
1488  if ( !$this->mRevision->userCan( Revision::DELETED_TEXT, $user ) ) {
1489  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1490  'rev-deleted-text-permission' );
1491 
1492  return false;
1493  // If the user needs to confirm that they want to see it...
1494  } elseif ( $this->getContext()->getRequest()->getInt( 'unhide' ) != 1 ) {
1495  # Give explanation and add a link to view the revision...
1496  $oldid = intval( $this->getOldID() );
1497  $link = $this->getTitle()->getFullURL( "oldid={$oldid}&unhide=1" );
1498  $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
1499  'rev-suppressed-text-unhide' : 'rev-deleted-text-unhide';
1500  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1501  [ $msg, $link ] );
1502 
1503  return false;
1504  // We are allowed to see...
1505  } else {
1506  $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
1507  'rev-suppressed-text-view' : 'rev-deleted-text-view';
1508  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", $msg );
1509 
1510  return true;
1511  }
1512  }
1513 
1522  public function setOldSubtitle( $oldid = 0 ) {
1523  // Avoid PHP 7.1 warning of passing $this by reference
1524  $articlePage = $this;
1525 
1526  if ( !Hooks::run( 'DisplayOldSubtitle', [ &$articlePage, &$oldid ] ) ) {
1527  return;
1528  }
1529 
1530  $context = $this->getContext();
1531  $unhide = $context->getRequest()->getInt( 'unhide' ) == 1;
1532 
1533  # Cascade unhide param in links for easy deletion browsing
1534  $extraParams = [];
1535  if ( $unhide ) {
1536  $extraParams['unhide'] = 1;
1537  }
1538 
1539  if ( $this->mRevision && $this->mRevision->getId() === $oldid ) {
1540  $revision = $this->mRevision;
1541  } else {
1542  $revision = Revision::newFromId( $oldid );
1543  }
1544 
1545  $timestamp = $revision->getTimestamp();
1546 
1547  $current = ( $oldid == $this->mPage->getLatest() );
1548  $language = $context->getLanguage();
1549  $user = $context->getUser();
1550 
1551  $td = $language->userTimeAndDate( $timestamp, $user );
1552  $tddate = $language->userDate( $timestamp, $user );
1553  $tdtime = $language->userTime( $timestamp, $user );
1554 
1555  # Show user links if allowed to see them. If hidden, then show them only if requested...
1556  $userlinks = Linker::revUserTools( $revision, !$unhide );
1557 
1558  $infomsg = $current && !$context->msg( 'revision-info-current' )->isDisabled()
1559  ? 'revision-info-current'
1560  : 'revision-info';
1561 
1562  $outputPage = $context->getOutput();
1563  $revisionInfo = "<div id=\"mw-{$infomsg}\">" .
1564  $context->msg( $infomsg, $td )
1565  ->rawParams( $userlinks )
1566  ->params( $revision->getId(), $tddate, $tdtime, $revision->getUserText() )
1567  ->rawParams( Linker::revComment( $revision, true, true ) )
1568  ->parse() .
1569  "</div>";
1570 
1571  $lnk = $current
1572  ? $context->msg( 'currentrevisionlink' )->escaped()
1574  $this->getTitle(),
1575  $context->msg( 'currentrevisionlink' )->escaped(),
1576  [],
1577  $extraParams
1578  );
1579  $curdiff = $current
1580  ? $context->msg( 'diff' )->escaped()
1582  $this->getTitle(),
1583  $context->msg( 'diff' )->escaped(),
1584  [],
1585  [
1586  'diff' => 'cur',
1587  'oldid' => $oldid
1588  ] + $extraParams
1589  );
1590  $prev = $this->getTitle()->getPreviousRevisionID( $oldid );
1591  $prevlink = $prev
1593  $this->getTitle(),
1594  $context->msg( 'previousrevision' )->escaped(),
1595  [],
1596  [
1597  'direction' => 'prev',
1598  'oldid' => $oldid
1599  ] + $extraParams
1600  )
1601  : $context->msg( 'previousrevision' )->escaped();
1602  $prevdiff = $prev
1604  $this->getTitle(),
1605  $context->msg( 'diff' )->escaped(),
1606  [],
1607  [
1608  'diff' => 'prev',
1609  'oldid' => $oldid
1610  ] + $extraParams
1611  )
1612  : $context->msg( 'diff' )->escaped();
1613  $nextlink = $current
1614  ? $context->msg( 'nextrevision' )->escaped()
1616  $this->getTitle(),
1617  $context->msg( 'nextrevision' )->escaped(),
1618  [],
1619  [
1620  'direction' => 'next',
1621  'oldid' => $oldid
1622  ] + $extraParams
1623  );
1624  $nextdiff = $current
1625  ? $context->msg( 'diff' )->escaped()
1627  $this->getTitle(),
1628  $context->msg( 'diff' )->escaped(),
1629  [],
1630  [
1631  'diff' => 'next',
1632  'oldid' => $oldid
1633  ] + $extraParams
1634  );
1635 
1636  $cdel = Linker::getRevDeleteLink( $user, $revision, $this->getTitle() );
1637  if ( $cdel !== '' ) {
1638  $cdel .= ' ';
1639  }
1640 
1641  // the outer div is need for styling the revision info and nav in MobileFrontend
1642  $outputPage->addSubtitle( "<div class=\"mw-revision\">" . $revisionInfo .
1643  "<div id=\"mw-revision-nav\">" . $cdel .
1644  $context->msg( 'revision-nav' )->rawParams(
1645  $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff
1646  )->escaped() . "</div></div>" );
1647  }
1648 
1662  public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) {
1663  $lang = $this->getTitle()->getPageLanguage();
1664  $out = $this->getContext()->getOutput();
1665  if ( $appendSubtitle ) {
1666  $out->addSubtitle( wfMessage( 'redirectpagesub' ) );
1667  }
1668  $out->addModuleStyles( 'mediawiki.action.view.redirectPage' );
1669  return static::getRedirectHeaderHtml( $lang, $target, $forceKnown );
1670  }
1671 
1684  public static function getRedirectHeaderHtml( Language $lang, $target, $forceKnown = false ) {
1685  if ( !is_array( $target ) ) {
1686  $target = [ $target ];
1687  }
1688 
1689  $html = '<ul class="redirectText">';
1691  foreach ( $target as $title ) {
1692  $html .= '<li>' . Linker::link(
1693  $title,
1694  htmlspecialchars( $title->getFullText() ),
1695  [],
1696  // Make sure wiki page redirects are not followed
1697  $title->isRedirect() ? [ 'redirect' => 'no' ] : [],
1698  ( $forceKnown ? [ 'known', 'noclasses' ] : [] )
1699  ) . '</li>';
1700  }
1701  $html .= '</ul>';
1702 
1703  $redirectToText = wfMessage( 'redirectto' )->inLanguage( $lang )->escaped();
1704 
1705  return '<div class="redirectMsg">' .
1706  '<p>' . $redirectToText . '</p>' .
1707  $html .
1708  '</div>';
1709  }
1710 
1719  public function addHelpLink( $to, $overrideBaseUrl = false ) {
1720  $msg = wfMessage(
1721  'namespace-' . $this->getTitle()->getNamespace() . '-helppage'
1722  );
1723 
1724  $out = $this->getContext()->getOutput();
1725  if ( !$msg->isDisabled() ) {
1726  $helpUrl = Skin::makeUrl( $msg->plain() );
1727  $out->addHelpLink( $helpUrl, true );
1728  } else {
1729  $out->addHelpLink( $to, $overrideBaseUrl );
1730  }
1731  }
1732 
1736  public function render() {
1737  $this->getContext()->getRequest()->response()->header( 'X-Robots-Tag: noindex' );
1738  $this->getContext()->getOutput()->setArticleBodyOnly( true );
1739  // We later set 'enableSectionEditLinks=false' based on this; also used by ImagePage
1740  $this->viewIsRenderAction = true;
1741  $this->view();
1742  }
1743 
1747  public function protect() {
1748  $form = new ProtectionForm( $this );
1749  $form->execute();
1750  }
1751 
1755  public function unprotect() {
1756  $this->protect();
1757  }
1758 
1762  public function delete() {
1763  # This code desperately needs to be totally rewritten
1764 
1765  $title = $this->getTitle();
1766  $context = $this->getContext();
1767  $user = $context->getUser();
1768  $request = $context->getRequest();
1769 
1770  # Check permissions
1771  $permissionErrors = $title->getUserPermissionsErrors( 'delete', $user );
1772  if ( count( $permissionErrors ) ) {
1773  throw new PermissionsError( 'delete', $permissionErrors );
1774  }
1775 
1776  # Read-only check...
1777  if ( wfReadOnly() ) {
1778  throw new ReadOnlyError;
1779  }
1780 
1781  # Better double-check that it hasn't been deleted yet!
1782  $this->mPage->loadPageData(
1783  $request->wasPosted() ? WikiPage::READ_LATEST : WikiPage::READ_NORMAL
1784  );
1785  if ( !$this->mPage->exists() ) {
1786  $deleteLogPage = new LogPage( 'delete' );
1787  $outputPage = $context->getOutput();
1788  $outputPage->setPageTitle( $context->msg( 'cannotdelete-title', $title->getPrefixedText() ) );
1789  $outputPage->wrapWikiMsg( "<div class=\"error mw-error-cannotdelete\">\n$1\n</div>",
1790  [ 'cannotdelete', wfEscapeWikiText( $title->getPrefixedText() ) ]
1791  );
1792  $outputPage->addHTML(
1793  Xml::element( 'h2', null, $deleteLogPage->getName()->text() )
1794  );
1796  $outputPage,
1797  'delete',
1798  $title
1799  );
1800 
1801  return;
1802  }
1803 
1804  $deleteReasonList = $request->getText( 'wpDeleteReasonList', 'other' );
1805  $deleteReason = $request->getText( 'wpReason' );
1806 
1807  if ( $deleteReasonList == 'other' ) {
1808  $reason = $deleteReason;
1809  } elseif ( $deleteReason != '' ) {
1810  // Entry from drop down menu + additional comment
1811  $colonseparator = wfMessage( 'colon-separator' )->inContentLanguage()->text();
1812  $reason = $deleteReasonList . $colonseparator . $deleteReason;
1813  } else {
1814  $reason = $deleteReasonList;
1815  }
1816 
1817  if ( $request->wasPosted() && $user->matchEditToken( $request->getVal( 'wpEditToken' ),
1818  [ 'delete', $this->getTitle()->getPrefixedText() ] )
1819  ) {
1820  # Flag to hide all contents of the archived revisions
1821  $suppress = $request->getCheck( 'wpSuppress' ) && $user->isAllowed( 'suppressrevision' );
1822 
1823  $this->doDelete( $reason, $suppress );
1824 
1825  WatchAction::doWatchOrUnwatch( $request->getCheck( 'wpWatch' ), $title, $user );
1826 
1827  return;
1828  }
1829 
1830  // Generate deletion reason
1831  $hasHistory = false;
1832  if ( !$reason ) {
1833  try {
1834  $reason = $this->generateReason( $hasHistory );
1835  } catch ( Exception $e ) {
1836  # if a page is horribly broken, we still want to be able to
1837  # delete it. So be lenient about errors here.
1838  wfDebug( "Error while building auto delete summary: $e" );
1839  $reason = '';
1840  }
1841  }
1842 
1843  // If the page has a history, insert a warning
1844  if ( $hasHistory ) {
1845  $title = $this->getTitle();
1846 
1847  // The following can use the real revision count as this is only being shown for users
1848  // that can delete this page.
1849  // This, as a side-effect, also makes sure that the following query isn't being run for
1850  // pages with a larger history, unless the user has the 'bigdelete' right
1851  // (and is about to delete this page).
1852  $dbr = wfGetDB( DB_REPLICA );
1853  $revisions = $edits = (int)$dbr->selectField(
1854  'revision',
1855  'COUNT(rev_page)',
1856  [ 'rev_page' => $title->getArticleID() ],
1857  __METHOD__
1858  );
1859 
1860  // @todo i18n issue/patchwork message
1861  $context->getOutput()->addHTML(
1862  '<strong class="mw-delete-warning-revisions">' .
1863  $context->msg( 'historywarning' )->numParams( $revisions )->parse() .
1864  $context->msg( 'word-separator' )->escaped() . Linker::linkKnown( $title,
1865  $context->msg( 'history' )->escaped(),
1866  [],
1867  [ 'action' => 'history' ] ) .
1868  '</strong>'
1869  );
1870 
1871  if ( $title->isBigDeletion() ) {
1872  global $wgDeleteRevisionsLimit;
1873  $context->getOutput()->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
1874  [
1875  'delete-warning-toobig',
1876  $context->getLanguage()->formatNum( $wgDeleteRevisionsLimit )
1877  ]
1878  );
1879  }
1880  }
1881 
1882  $this->confirmDelete( $reason );
1883  }
1884 
1890  public function confirmDelete( $reason ) {
1891  wfDebug( "Article::confirmDelete\n" );
1892 
1893  $title = $this->getTitle();
1894  $ctx = $this->getContext();
1895  $outputPage = $ctx->getOutput();
1896  $outputPage->setPageTitle( wfMessage( 'delete-confirm', $title->getPrefixedText() ) );
1897  $outputPage->addBacklinkSubtitle( $title );
1898  $outputPage->setRobotPolicy( 'noindex,nofollow' );
1899  $outputPage->addModules( 'mediawiki.action.delete' );
1900 
1901  $backlinkCache = $title->getBacklinkCache();
1902  if ( $backlinkCache->hasLinks( 'pagelinks' ) || $backlinkCache->hasLinks( 'templatelinks' ) ) {
1903  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1904  'deleting-backlinks-warning' );
1905  }
1906 
1907  $subpageQueryLimit = 51;
1908  $subpages = $title->getSubpages( $subpageQueryLimit );
1909  $subpageCount = count( $subpages );
1910  if ( $subpageCount > 0 ) {
1911  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1912  [ 'deleting-subpages-warning', Message::numParam( $subpageCount ) ] );
1913  }
1914  $outputPage->addWikiMsg( 'confirmdeletetext' );
1915 
1916  Hooks::run( 'ArticleConfirmDelete', [ $this, $outputPage, &$reason ] );
1917 
1918  $user = $this->getContext()->getUser();
1919  $checkWatch = $user->getBoolOption( 'watchdeletion' ) || $user->isWatched( $title );
1920 
1921  $outputPage->enableOOUI();
1922 
1924  $ctx->msg( 'deletereason-dropdown' )->inContentLanguage()->text(),
1925  [ 'other' => $ctx->msg( 'deletereasonotherlist' )->inContentLanguage()->text() ]
1926  );
1927  $options = Xml::listDropDownOptionsOoui( $options );
1928 
1929  $fields[] = new OOUI\FieldLayout(
1930  new OOUI\DropdownInputWidget( [
1931  'name' => 'wpDeleteReasonList',
1932  'inputId' => 'wpDeleteReasonList',
1933  'tabIndex' => 1,
1934  'infusable' => true,
1935  'value' => '',
1936  'options' => $options
1937  ] ),
1938  [
1939  'label' => $ctx->msg( 'deletecomment' )->text(),
1940  'align' => 'top',
1941  ]
1942  );
1943 
1944  // HTML maxlength uses "UTF-16 code units", which means that characters outside BMP
1945  // (e.g. emojis) count for two each. This limit is overridden in JS to instead count
1946  // Unicode codepoints.
1947  $fields[] = new OOUI\FieldLayout(
1948  new OOUI\TextInputWidget( [
1949  'name' => 'wpReason',
1950  'inputId' => 'wpReason',
1951  'tabIndex' => 2,
1953  'infusable' => true,
1954  'value' => $reason,
1955  'autofocus' => true,
1956  ] ),
1957  [
1958  'label' => $ctx->msg( 'deleteotherreason' )->text(),
1959  'align' => 'top',
1960  ]
1961  );
1962 
1963  if ( $user->isLoggedIn() ) {
1964  $fields[] = new OOUI\FieldLayout(
1965  new OOUI\CheckboxInputWidget( [
1966  'name' => 'wpWatch',
1967  'inputId' => 'wpWatch',
1968  'tabIndex' => 3,
1969  'selected' => $checkWatch,
1970  ] ),
1971  [
1972  'label' => $ctx->msg( 'watchthis' )->text(),
1973  'align' => 'inline',
1974  'infusable' => true,
1975  ]
1976  );
1977  }
1978 
1979  if ( $user->isAllowed( 'suppressrevision' ) ) {
1980  $fields[] = new OOUI\FieldLayout(
1981  new OOUI\CheckboxInputWidget( [
1982  'name' => 'wpSuppress',
1983  'inputId' => 'wpSuppress',
1984  'tabIndex' => 4,
1985  ] ),
1986  [
1987  'label' => $ctx->msg( 'revdelete-suppress' )->text(),
1988  'align' => 'inline',
1989  'infusable' => true,
1990  ]
1991  );
1992  }
1993 
1994  $fields[] = new OOUI\FieldLayout(
1995  new OOUI\ButtonInputWidget( [
1996  'name' => 'wpConfirmB',
1997  'inputId' => 'wpConfirmB',
1998  'tabIndex' => 5,
1999  'value' => $ctx->msg( 'deletepage' )->text(),
2000  'label' => $ctx->msg( 'deletepage' )->text(),
2001  'flags' => [ 'primary', 'destructive' ],
2002  'type' => 'submit',
2003  ] ),
2004  [
2005  'align' => 'top',
2006  ]
2007  );
2008 
2009  $fieldset = new OOUI\FieldsetLayout( [
2010  'label' => $ctx->msg( 'delete-legend' )->text(),
2011  'id' => 'mw-delete-table',
2012  'items' => $fields,
2013  ] );
2014 
2015  $form = new OOUI\FormLayout( [
2016  'method' => 'post',
2017  'action' => $title->getLocalURL( 'action=delete' ),
2018  'id' => 'deleteconfirm',
2019  ] );
2020  $form->appendContent(
2021  $fieldset,
2022  new OOUI\HtmlSnippet(
2023  Html::hidden( 'wpEditToken', $user->getEditToken( [ 'delete', $title->getPrefixedText() ] ) )
2024  )
2025  );
2026 
2027  $outputPage->addHTML(
2028  new OOUI\PanelLayout( [
2029  'classes' => [ 'deletepage-wrapper' ],
2030  'expanded' => false,
2031  'padded' => true,
2032  'framed' => true,
2033  'content' => $form,
2034  ] )
2035  );
2036 
2037  if ( $user->isAllowed( 'editinterface' ) ) {
2039  $ctx->msg( 'deletereason-dropdown' )->inContentLanguage()->getTitle(),
2040  wfMessage( 'delete-edit-reasonlist' )->escaped(),
2041  [],
2042  [ 'action' => 'edit' ]
2043  );
2044  $outputPage->addHTML( '<p class="mw-delete-editreasons">' . $link . '</p>' );
2045  }
2046 
2047  $deleteLogPage = new LogPage( 'delete' );
2048  $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
2049  LogEventsList::showLogExtract( $outputPage, 'delete', $title );
2050  }
2051 
2060  public function doDelete( $reason, $suppress = false, $immediate = false ) {
2061  $error = '';
2062  $context = $this->getContext();
2063  $outputPage = $context->getOutput();
2064  $user = $context->getUser();
2065  $status = $this->mPage->doDeleteArticleReal( $reason, $suppress, 0, true, $error, $user,
2066  [], 'delete', $immediate );
2067 
2068  if ( $status->isOK() ) {
2069  $deleted = $this->getTitle()->getPrefixedText();
2070 
2071  $outputPage->setPageTitle( wfMessage( 'actioncomplete' ) );
2072  $outputPage->setRobotPolicy( 'noindex,nofollow' );
2073 
2074  if ( $status->isGood() ) {
2075  $loglink = '[[Special:Log/delete|' . wfMessage( 'deletionlog' )->text() . ']]';
2076  $outputPage->addWikiMsg( 'deletedtext', wfEscapeWikiText( $deleted ), $loglink );
2077  Hooks::run( 'ArticleDeleteAfterSuccess', [ $this->getTitle(), $outputPage ] );
2078  } else {
2079  $outputPage->addWikiMsg( 'delete-scheduled', wfEscapeWikiText( $deleted ) );
2080  }
2081 
2082  $outputPage->returnToMain( false );
2083  } else {
2084  $outputPage->setPageTitle(
2085  wfMessage( 'cannotdelete-title',
2086  $this->getTitle()->getPrefixedText() )
2087  );
2088 
2089  if ( $error == '' ) {
2090  $outputPage->wrapWikiTextAsInterface(
2091  'error mw-error-cannotdelete',
2092  $status->getWikiText()
2093  );
2094  $deleteLogPage = new LogPage( 'delete' );
2095  $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
2096 
2098  $outputPage,
2099  'delete',
2100  $this->getTitle()
2101  );
2102  } else {
2103  $outputPage->addHTML( $error );
2104  }
2105  }
2106  }
2107 
2108  /* Caching functions */
2109 
2117  protected function tryFileCache() {
2118  static $called = false;
2119 
2120  if ( $called ) {
2121  wfDebug( "Article::tryFileCache(): called twice!?\n" );
2122  return false;
2123  }
2124 
2125  $called = true;
2126  if ( $this->isFileCacheable() ) {
2127  $cache = new HTMLFileCache( $this->getTitle(), 'view' );
2128  if ( $cache->isCacheGood( $this->mPage->getTouched() ) ) {
2129  wfDebug( "Article::tryFileCache(): about to load file\n" );
2130  $cache->loadFromFileCache( $this->getContext() );
2131  return true;
2132  } else {
2133  wfDebug( "Article::tryFileCache(): starting buffer\n" );
2134  ob_start( [ &$cache, 'saveToFileCache' ] );
2135  }
2136  } else {
2137  wfDebug( "Article::tryFileCache(): not cacheable\n" );
2138  }
2139 
2140  return false;
2141  }
2142 
2148  public function isFileCacheable( $mode = HTMLFileCache::MODE_NORMAL ) {
2149  $cacheable = false;
2150 
2151  if ( HTMLFileCache::useFileCache( $this->getContext(), $mode ) ) {
2152  $cacheable = $this->mPage->getId()
2153  && !$this->mRedirectedFrom && !$this->getTitle()->isRedirect();
2154  // Extension may have reason to disable file caching on some pages.
2155  if ( $cacheable ) {
2156  // Avoid PHP 7.1 warning of passing $this by reference
2157  $articlePage = $this;
2158  $cacheable = Hooks::run( 'IsFileCacheable', [ &$articlePage ] );
2159  }
2160  }
2161 
2162  return $cacheable;
2163  }
2164 
2178  public function getParserOutput( $oldid = null, User $user = null ) {
2179  // XXX: bypasses mParserOptions and thus setParserOptions()
2180 
2181  if ( $user === null ) {
2182  $parserOptions = $this->getParserOptions();
2183  } else {
2184  $parserOptions = $this->mPage->makeParserOptions( $user );
2185  }
2186 
2187  return $this->mPage->getParserOutput( $parserOptions, $oldid );
2188  }
2189 
2197  if ( $this->mParserOptions ) {
2198  throw new MWException( "can't change parser options after they have already been set" );
2199  }
2200 
2201  // clone, so if $options is modified later, it doesn't confuse the parser cache.
2202  $this->mParserOptions = clone $options;
2203  }
2204 
2209  public function getParserOptions() {
2210  if ( !$this->mParserOptions ) {
2211  $this->mParserOptions = $this->mPage->makeParserOptions( $this->getContext() );
2212  }
2213  // Clone to allow modifications of the return value without affecting cache
2214  return clone $this->mParserOptions;
2215  }
2216 
2223  public function setContext( $context ) {
2224  $this->mContext = $context;
2225  }
2226 
2233  public function getContext() {
2234  if ( $this->mContext instanceof IContextSource ) {
2235  return $this->mContext;
2236  } else {
2237  wfDebug( __METHOD__ . " called and \$mContext is null. " .
2238  "Return RequestContext::getMain(); for sanity\n" );
2239  return RequestContext::getMain();
2240  }
2241  }
2242 
2250  public function __get( $fname ) {
2251  if ( property_exists( $this->mPage, $fname ) ) {
2252  # wfWarn( "Access to raw $fname field " . __CLASS__ );
2253  return $this->mPage->$fname;
2254  }
2255  trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
2256  }
2257 
2265  public function __set( $fname, $fvalue ) {
2266  if ( property_exists( $this->mPage, $fname ) ) {
2267  # wfWarn( "Access to raw $fname field of " . __CLASS__ );
2268  $this->mPage->$fname = $fvalue;
2269  // Note: extensions may want to toss on new fields
2270  } elseif ( !in_array( $fname, [ 'mContext', 'mPage' ] ) ) {
2271  $this->mPage->$fname = $fvalue;
2272  } else {
2273  trigger_error( 'Inaccessible property via __set(): ' . $fname, E_USER_NOTICE );
2274  }
2275  }
2276 
2281  public function checkFlags( $flags ) {
2282  return $this->mPage->checkFlags( $flags );
2283  }
2284 
2289  public function checkTouched() {
2290  return $this->mPage->checkTouched();
2291  }
2292 
2297  public function clearPreparedEdit() {
2298  $this->mPage->clearPreparedEdit();
2299  }
2300 
2305  public function doDeleteArticleReal(
2306  $reason, $suppress = false, $u1 = null, $u2 = null, &$error = '', User $user = null,
2307  $tags = [], $immediate = false
2308  ) {
2309  return $this->mPage->doDeleteArticleReal(
2310  $reason, $suppress, $u1, $u2, $error, $user, $tags, 'delete', $immediate
2311  );
2312  }
2313 
2318  public function doDeleteUpdates(
2319  $id,
2320  Content $content = null,
2321  $revision = null,
2322  User $user = null
2323  ) {
2324  $this->mPage->doDeleteUpdates( $id, $content, $revision, $user );
2325  }
2326 
2332  public function doEditContent( Content $content, $summary, $flags = 0, $originalRevId = false,
2333  User $user = null, $serialFormat = null
2334  ) {
2335  wfDeprecated( __METHOD__, '1.29' );
2336  return $this->mPage->doEditContent( $content, $summary, $flags, $originalRevId,
2337  $user, $serialFormat
2338  );
2339  }
2340 
2345  public function doEditUpdates( Revision $revision, User $user, array $options = [] ) {
2346  return $this->mPage->doEditUpdates( $revision, $user, $options );
2347  }
2348 
2355  public function doPurge() {
2356  return $this->mPage->doPurge();
2357  }
2358 
2363  public function doViewUpdates( User $user, $oldid = 0 ) {
2364  $this->mPage->doViewUpdates( $user, $oldid );
2365  }
2366 
2371  public function exists() {
2372  return $this->mPage->exists();
2373  }
2374 
2379  public function followRedirect() {
2380  return $this->mPage->followRedirect();
2381  }
2382 
2387  public function getActionOverrides() {
2388  return $this->mPage->getActionOverrides();
2389  }
2390 
2395  public function getAutoDeleteReason( &$hasHistory ) {
2396  return $this->mPage->getAutoDeleteReason( $hasHistory );
2397  }
2398 
2403  public function getCategories() {
2404  return $this->mPage->getCategories();
2405  }
2406 
2411  public function getComment( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2412  return $this->mPage->getComment( $audience, $user );
2413  }
2414 
2419  public function getContentHandler() {
2420  return $this->mPage->getContentHandler();
2421  }
2422 
2427  public function getContentModel() {
2428  return $this->mPage->getContentModel();
2429  }
2430 
2435  public function getContributors() {
2436  return $this->mPage->getContributors();
2437  }
2438 
2443  public function getCreator( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2444  return $this->mPage->getCreator( $audience, $user );
2445  }
2446 
2451  public function getDeletionUpdates( Content $content = null ) {
2452  return $this->mPage->getDeletionUpdates( $content );
2453  }
2454 
2459  public function getHiddenCategories() {
2460  return $this->mPage->getHiddenCategories();
2461  }
2462 
2467  public function getId() {
2468  return $this->mPage->getId();
2469  }
2470 
2475  public function getLatest() {
2476  return $this->mPage->getLatest();
2477  }
2478 
2483  public function getLinksTimestamp() {
2484  return $this->mPage->getLinksTimestamp();
2485  }
2486 
2491  public function getMinorEdit() {
2492  return $this->mPage->getMinorEdit();
2493  }
2494 
2499  public function getOldestRevision() {
2500  return $this->mPage->getOldestRevision();
2501  }
2502 
2507  public function getRedirectTarget() {
2508  return $this->mPage->getRedirectTarget();
2509  }
2510 
2515  public function getRedirectURL( $rt ) {
2516  return $this->mPage->getRedirectURL( $rt );
2517  }
2518 
2523  public function getRevision() {
2524  return $this->mPage->getRevision();
2525  }
2526 
2531  public function getTimestamp() {
2532  return $this->mPage->getTimestamp();
2533  }
2534 
2539  public function getTouched() {
2540  return $this->mPage->getTouched();
2541  }
2542 
2547  public function getUndoContent( Revision $undo, Revision $undoafter = null ) {
2548  return $this->mPage->getUndoContent( $undo, $undoafter );
2549  }
2550 
2555  public function getUser( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2556  return $this->mPage->getUser( $audience, $user );
2557  }
2558 
2563  public function getUserText( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2564  return $this->mPage->getUserText( $audience, $user );
2565  }
2566 
2571  public function hasViewableContent() {
2572  return $this->mPage->hasViewableContent();
2573  }
2574 
2579  public function insertOn( $dbw, $pageId = null ) {
2580  return $this->mPage->insertOn( $dbw, $pageId );
2581  }
2582 
2587  public function insertProtectNullRevision( $revCommentMsg, array $limit,
2588  array $expiry, $cascade, $reason, $user = null
2589  ) {
2590  return $this->mPage->insertProtectNullRevision( $revCommentMsg, $limit,
2591  $expiry, $cascade, $reason, $user
2592  );
2593  }
2594 
2599  public function insertRedirect() {
2600  return $this->mPage->insertRedirect();
2601  }
2602 
2607  public function insertRedirectEntry( Title $rt, $oldLatest = null ) {
2608  return $this->mPage->insertRedirectEntry( $rt, $oldLatest );
2609  }
2610 
2615  public function isCountable( $editInfo = false ) {
2616  return $this->mPage->isCountable( $editInfo );
2617  }
2618 
2623  public function isRedirect() {
2624  return $this->mPage->isRedirect();
2625  }
2626 
2631  public function loadFromRow( $data, $from ) {
2632  return $this->mPage->loadFromRow( $data, $from );
2633  }
2634 
2639  public function loadPageData( $from = 'fromdb' ) {
2640  $this->mPage->loadPageData( $from );
2641  }
2642 
2647  public function lockAndGetLatest() {
2648  return $this->mPage->lockAndGetLatest();
2649  }
2650 
2655  public function makeParserOptions( $context ) {
2656  return $this->mPage->makeParserOptions( $context );
2657  }
2658 
2663  public function pageDataFromId( $dbr, $id, $options = [] ) {
2664  return $this->mPage->pageDataFromId( $dbr, $id, $options );
2665  }
2666 
2671  public function pageDataFromTitle( $dbr, $title, $options = [] ) {
2672  return $this->mPage->pageDataFromTitle( $dbr, $title, $options );
2673  }
2674 
2679  public function prepareContentForEdit(
2680  Content $content, $revision = null, User $user = null,
2681  $serialFormat = null, $useCache = true
2682  ) {
2683  return $this->mPage->prepareContentForEdit(
2684  $content, $revision, $user,
2685  $serialFormat, $useCache
2686  );
2687  }
2688 
2693  public function protectDescription( array $limit, array $expiry ) {
2694  return $this->mPage->protectDescription( $limit, $expiry );
2695  }
2696 
2701  public function protectDescriptionLog( array $limit, array $expiry ) {
2702  return $this->mPage->protectDescriptionLog( $limit, $expiry );
2703  }
2704 
2709  public function replaceSectionAtRev( $sectionId, Content $sectionContent,
2710  $sectionTitle = '', $baseRevId = null
2711  ) {
2712  return $this->mPage->replaceSectionAtRev( $sectionId, $sectionContent,
2713  $sectionTitle, $baseRevId
2714  );
2715  }
2716 
2721  public function replaceSectionContent(
2722  $sectionId, Content $sectionContent, $sectionTitle = '', $edittime = null
2723  ) {
2724  return $this->mPage->replaceSectionContent(
2725  $sectionId, $sectionContent, $sectionTitle, $edittime
2726  );
2727  }
2728 
2733  public function setTimestamp( $ts ) {
2734  $this->mPage->setTimestamp( $ts );
2735  }
2736 
2741  public function shouldCheckParserCache( ParserOptions $parserOptions, $oldId ) {
2742  return $this->mPage->shouldCheckParserCache( $parserOptions, $oldId );
2743  }
2744 
2749  public function supportsSections() {
2750  return $this->mPage->supportsSections();
2751  }
2752 
2757  public function triggerOpportunisticLinksUpdate( ParserOutput $parserOutput ) {
2758  return $this->mPage->triggerOpportunisticLinksUpdate( $parserOutput );
2759  }
2760 
2765  public function updateCategoryCounts( array $added, array $deleted, $id = 0 ) {
2766  return $this->mPage->updateCategoryCounts( $added, $deleted, $id );
2767  }
2768 
2773  public function updateIfNewerOn( $dbw, $revision ) {
2774  return $this->mPage->updateIfNewerOn( $dbw, $revision );
2775  }
2776 
2781  public function updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect = null ) {
2782  return $this->mPage->updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect );
2783  }
2784 
2789  public function updateRevisionOn( $dbw, $revision, $lastRevision = null,
2790  $lastRevIsRedirect = null
2791  ) {
2792  return $this->mPage->updateRevisionOn( $dbw, $revision, $lastRevision,
2793  $lastRevIsRedirect
2794  );
2795  }
2796 
2805  public function doUpdateRestrictions( array $limit, array $expiry, &$cascade,
2806  $reason, User $user
2807  ) {
2808  return $this->mPage->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $user );
2809  }
2810 
2818  public function updateRestrictions( $limit = [], $reason = '',
2819  &$cascade = 0, $expiry = []
2820  ) {
2821  return $this->mPage->doUpdateRestrictions(
2822  $limit,
2823  $expiry,
2824  $cascade,
2825  $reason,
2826  $this->getContext()->getUser()
2827  );
2828  }
2829 
2841  public function doDeleteArticle(
2842  $reason, $suppress = false, $u1 = null, $u2 = null, &$error = '', $immediate = false
2843  ) {
2844  return $this->mPage->doDeleteArticle( $reason, $suppress, $u1, $u2, $error,
2845  null, $immediate );
2846  }
2847 
2857  public function doRollback( $fromP, $summary, $token, $bot, &$resultDetails, User $user = null ) {
2858  if ( !$user ) {
2859  $user = $this->getContext()->getUser();
2860  }
2861 
2862  return $this->mPage->doRollback( $fromP, $summary, $token, $bot, $resultDetails, $user );
2863  }
2864 
2873  public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser = null ) {
2874  if ( !$guser ) {
2875  $guser = $this->getContext()->getUser();
2876  }
2877 
2878  return $this->mPage->commitRollback( $fromP, $summary, $bot, $resultDetails, $guser );
2879  }
2880 
2885  public function generateReason( &$hasHistory ) {
2886  $title = $this->mPage->getTitle();
2888  return $handler->getAutoDeleteReason( $title, $hasHistory );
2889  }
2890 
2891  // ******
2892 }
getRedirectTarget()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2507
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
const FOR_THIS_USER
Definition: Revision.php:55
lockAndGetLatest()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2647
getUndoContent(Revision $undo, Revision $undoafter=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2547
getUserText( $audience=Revision::FOR_PUBLIC, User $user=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2563
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:1982
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:202
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:1585
$data
Utility to generate mapping file used in mw.Title (phpCharToUpper.json)
getLatest()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2475
setParserOptions(ParserOptions $options)
Override the ParserOptions used to render the primary article wikitext.
Definition: Article.php:2196
doEditUpdates(Revision $revision, User $user, array $options=[])
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2345
static newFromID( $id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:470
checkFlags( $flags)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2281
isCountable( $editInfo=false)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2615
exists()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2371
supportsSections()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2749
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:969
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
protect()
action=protect handler
Definition: Article.php:1747
Status null $fetchResult
represents the outcome of fetchRevisionRecord().
Definition: Article.php:99
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException' returning false will NOT prevent logging $e
Definition: hooks.txt:2159
$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:230
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:2765
string bool $mRedirectUrl
URL to redirect to or false if none.
Definition: Article.php:82
if(!isset( $args[0])) $lang
static newFromTarget( $specificTarget, $vagueTarget=null, $fromMaster=false)
Given a target and the target&#39;s type, get an existing Block object if possible.
Definition: Block.php:1397
ParserOptions null $mParserOptions
ParserOptions object for $wgUser articles.
Definition: Article.php:51
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:882
shouldCheckParserCache(ParserOptions $parserOptions, $oldId)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2741
doUpdateRestrictions(array $limit, array $expiry, &$cascade, $reason, User $user)
Definition: Article.php:2805
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
getParserOutput( $oldid=null, User $user=null)
#-
Definition: Article.php:2178
makeFetchErrorContent()
Returns a Content object representing any error in $this->fetchContent, or null if there is no such e...
Definition: Article.php:504
Class for viewing MediaWiki article and history.
Definition: Article.php:37
null for the local wiki Added in
Definition: hooks.txt:1585
Page view caching in the file system.
getRedirectURL( $rt)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2515
followRedirect()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2379
__get( $fname)
Use PHP&#39;s magic __get handler to handle accessing of raw WikiPage fields for backwards compatibility...
Definition: Article.php:2250
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:2841
triggerOpportunisticLinksUpdate(ParserOutput $parserOutput)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2757
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:338
getDeletionUpdates(Content $content=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2451
setContext( $context)
Sets the context this Article is executed in.
Definition: Article.php:2223
const COMMENT_CHARACTER_LIMIT
Maximum length of a comment in UTF-8 characters.
fetchRevisionRecord()
Fetches the revision to work on.
Definition: Article.php:416
clearPreparedEdit()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2297
getRevisionRedirectTarget(RevisionRecord $revision)
Definition: Article.php:870
static numParam( $num)
Definition: Message.php:1049
replaceSectionContent( $sectionId, Content $sectionContent, $sectionTitle='', $edittime=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2721
__set( $fname, $fvalue)
Use PHP&#39;s magic __set handler to handle setting of raw WikiPage fields for backwards compatibility...
Definition: Article.php:2265
$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:2435
updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2781
showMissingArticle()
Show the error text for a missing article.
Definition: Article.php:1346
loadFromRow( $data, $from)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2631
updateRevisionOn( $dbw, $revision, $lastRevision=null, $lastRevIsRedirect=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2789
protectDescription(array $limit, array $expiry)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2693
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:1522
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:3050
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:48
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:780
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:780
getContext()
Gets the context this Article is executed in.
Definition: Article.php:2233
Revision null $mRevision
Revision to be shown.
Definition: Article.php:108
pageDataFromTitle( $dbr, $title, $options=[])
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2671
bool $viewIsRenderAction
Whether render() was called.
Definition: Article.php:122
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:1263
isRedirect()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2623
__construct(Title $title, $oldId=null)
Constructor and clear the article.
Definition: Article.php:129
protectDescriptionLog(array $limit, array $expiry)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2701
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:2679
static openElement( $element, $attribs=null)
This opens an XML element.
Definition: Xml.php:108
const FOR_PUBLIC
Definition: Revision.php:54
$wgUseFileCache
This will cache static pages for non-logged-in users to reduce database traffic on public sites...
pageDataFromId( $dbr, $id, $options=[])
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2663
showViewFooter()
Show the footer section of an ordinary page view.
Definition: Article.php:1145
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:558
doEditContent(Content $content, $summary, $flags=0, $originalRevId=false, User $user=null, $serialFormat=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2332
const NS_MEDIA
Definition: Defines.php:52
showRedirectedFromHeader()
If this request is a redirect view, send "redirected from" subtitle to the output.
Definition: Article.php:1056
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:2655
getRevision()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2523
generateReason(&$hasHistory)
Definition: Article.php:2885
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:1684
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:1337
insertRedirect()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2599
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:76
hasViewableContent()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2571
IContextSource null $mContext
The context this Article is executed in.
Definition: Article.php:42
getTitle()
Get the title object of the article.
Definition: Article.php:220
$cache
Definition: mcc.php:33
getActionOverrides()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2387
doViewUpdates(User $user, $oldid=0)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2363
getTitle()
Get the title object of the article.
Definition: WikiPage.php:294
const NS_CATEGORY
Definition: Defines.php:78
doPurge()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2355
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:1982
doRollback( $fromP, $summary, $token, $bot, &$resultDetails, User $user=null)
Definition: Article.php:2857
updateRestrictions( $limit=[], $reason='', &$cascade=0, $expiry=[])
Definition: Article.php:2818
static makeExternalLink( $url, $text, $escape=true, $linktype='', $attribs=[], $title=null)
Make an external link.
Definition: Linker.php:846
render()
Handle action=render.
Definition: Article.php:1736
insertProtectNullRevision( $revCommentMsg, array $limit, array $expiry, $cascade, $reason, $user=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2587
addHelpLink( $to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Definition: Article.php:1719
const DELETED_RESTRICTED
Definition: Revision.php:49
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:780
getCategories()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2403
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:925
$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:1890
const NS_FILE
Definition: Defines.php:70
$wgRedirectSources
If local interwikis are set up which allow redirects, set this regexp to restrict URLs which will be ...
const TYPE_AUTO
Definition: Block.php:99
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:1766
$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:45
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:2709
getContentHandler()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2419
const NS_MEDIAWIKI
Definition: Defines.php:72
doDelete( $reason, $suppress=false, $immediate=false)
Perform a deletion and output success or failure messages.
Definition: Article.php:2060
if(defined( 'MW_SETUP_CALLBACK')) $fname
Customization point after all loading (constants, functions, classes, DefaultSettings, LocalSettings).
Definition: Setup.php:123
insertOn( $dbw, $pageId=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2579
getUser( $audience=Revision::FOR_PUBLIC, User $user=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2555
static makeUrl( $name, $urlaction='')
Definition: Skin.php:1179
const DELETED_TEXT
Definition: Revision.php:46
newPage(Title $title)
Definition: Article.php:138
Title null $mRedirectedFrom
Title from which we were redirected here, if any.
Definition: Article.php:79
setTimestamp( $ts)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2733
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:589
getOldestRevision()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2499
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...
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:1585
clear()
Clear the object.
Definition: Article.php:237
bool $mContentLoaded
Is the target revision loaded? Set by fetchRevisionRecord().
Definition: Article.php:69
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:2395
checkTouched()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2289
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:2061
static getCanonicalName( $index)
Returns the canonical (English) name for a given index.
getId()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2467
getTimestamp()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2531
Content null $mContentObject
Content of the main slot of $this->mRevision.
Definition: Article.php:61
ParserOutput null false $mParserOutput
The ParserOutput generated for viewing the page, initialized by view().
Definition: Article.php:115
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:2305
getCreator( $audience=Revision::FOR_PUBLIC, User $user=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2443
getMinorEdit()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2491
view()
This is the default action of the index.php entry point: just view the page of the given title...
Definition: Article.php:586
static linkKnown( $target, $html=null, $customAttribs=[], $query=[], $options=[ 'known'])
Identical to link(), except $options defaults to &#39;known&#39;.
Definition: Linker.php:146
getEmptyPageParserOutput(ParserOptions $options)
Returns ParserOutput to use when a page does not exist.
Definition: Article.php:312
getContentObject()
Returns a Content object representing the pages effective display content, not necessarily the revisi...
Definition: Article.php:269
static hidden( $name, $value, array $attribs=[])
Convenience function to produce an input element with type=hidden.
Definition: Html.php:797
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:2148
getOldID()
Definition: Article.php:325
showNamespaceHeader()
Show a header specific to the namespace currently being viewed, like [[MediaWiki:Talkpagetext]].
Definition: Article.php:1133
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:2117
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:2633
getComment( $audience=Revision::FOR_PUBLIC, User $user=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2411
insertRedirectEntry(Title $rt, $oldLatest=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2607
doDeleteUpdates( $id, Content $content=null, $revision=null, User $user=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2318
viewRedirect( $target, $appendSubtitle=true, $forceKnown=false)
Return the HTML for the top of a redirect page.
Definition: Article.php:1662
const POST_EDIT_COOKIE_KEY_PREFIX
Prefix of key for cookie used to pass post-edit state.
Definition: EditPage.php:201
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:1522
int $mRevIdFetched
Revision ID of revision that was loaded.
Definition: Article.php:89
getSubstituteContent()
Returns Content object to use when the page does not exist.
Definition: Article.php:285
Page revision base class.
loadPageData( $from='fromdb')
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2639
static newFromID( $id)
Constructor from a page id.
Definition: Article.php:147
fetchContentObject()
Get text content object Does NOT follow redirects.
Definition: Article.php:399
const DB_REPLICA
Definition: defines.php:25
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 the name of the old file & $article
Definition: hooks.txt:1473
static newFromTitle( $title, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition: Article.php:159
isCurrent()
Returns true if the currently-referenced revision is the current edit to this page (and it exists)...
Definition: Article.php:540
unprotect()
action=unprotect handler (alias)
Definition: Article.php:1755
Handles the page protection UI and backend.
getTouched()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2539
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:587
static newFromWikiPage(WikiPage $page, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition: Article.php:191
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:84
$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:574
const NS_USER_TALK
Definition: Defines.php:67
showDiffPage()
Show a diff page according to current request variables.
Definition: Article.php:897
applyContentOverride(Content $override)
Applies a content override by constructing a fake Revision object and assigning it to mRevision...
Definition: Article.php:524
updateIfNewerOn( $dbw, $revision)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2773
Definition: Block.php:31
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:2633
showDeletedRevisionHeader()
If the revision requested for view is deleted, check permissions.
Definition: Article.php:1479
setRedirectedFrom(Title $from)
Tell the page view functions that this view was redirected from another page on the wiki...
Definition: Article.php:211
return true to allow those checks to and false if checking is done & $user
Definition: hooks.txt:1473
getContentModel()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2427
getLinksTimestamp()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2483
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:2873
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:2209
const RC_LOG
Definition: Defines.php:144
getRobotPolicy( $action, ParserOutput $pOutput=null)
Get the robot policy to be used for the current view.
Definition: Article.php:947
showPatrolFooter()
If patrol is possible, output a patrol UI box.
Definition: Article.php:1168
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:1090
static formatRobotPolicy( $policy)
Converts a String robot policy into an associative array, to allow merging of several policies using ...
Definition: Article.php:1027
getHiddenCategories()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2459