MediaWiki  master
Article.php
Go to the documentation of this file.
1 <?php
23 
35 class Article implements Page {
37  protected $mContext;
38 
40  protected $mPage;
41 
44 
49  public $mContent;
50 
56 
58  public $mContentLoaded = false;
59 
61  public $mOldId;
62 
64  public $mRedirectedFrom = null;
65 
67  public $mRedirectUrl = false;
68 
70  public $mRevIdFetched = 0;
71 
73  public $mRevision = null;
74 
77 
83  public function __construct( Title $title, $oldId = null ) {
84  $this->mOldId = $oldId;
85  $this->mPage = $this->newPage( $title );
86  }
87 
92  protected function newPage( Title $title ) {
93  return new WikiPage( $title );
94  }
95 
101  public static function newFromID( $id ) {
102  $t = Title::newFromID( $id );
103  return $t == null ? null : new static( $t );
104  }
105 
113  public static function newFromTitle( $title, IContextSource $context ) {
114  if ( NS_MEDIA == $title->getNamespace() ) {
115  // FIXME: where should this go?
116  $title = Title::makeTitle( NS_FILE, $title->getDBkey() );
117  }
118 
119  $page = null;
120  Hooks::run( 'ArticleFromTitle', [ &$title, &$page, $context ] );
121  if ( !$page ) {
122  switch ( $title->getNamespace() ) {
123  case NS_FILE:
124  $page = new ImagePage( $title );
125  break;
126  case NS_CATEGORY:
127  $page = new CategoryPage( $title );
128  break;
129  default:
130  $page = new Article( $title );
131  }
132  }
133  $page->setContext( $context );
134 
135  return $page;
136  }
137 
145  public static function newFromWikiPage( WikiPage $page, IContextSource $context ) {
146  $article = self::newFromTitle( $page->getTitle(), $context );
147  $article->mPage = $page; // override to keep process cached vars
148  return $article;
149  }
150 
156  public function getRedirectedFrom() {
157  return $this->mRedirectedFrom;
158  }
159 
165  public function setRedirectedFrom( Title $from ) {
166  $this->mRedirectedFrom = $from;
167  }
168 
174  public function getTitle() {
175  return $this->mPage->getTitle();
176  }
177 
184  public function getPage() {
185  return $this->mPage;
186  }
187 
191  public function clear() {
192  $this->mContentLoaded = false;
193 
194  $this->mRedirectedFrom = null; # Title object if set
195  $this->mRevIdFetched = 0;
196  $this->mRedirectUrl = false;
197 
198  $this->mPage->clear();
199  }
200 
216  protected function getContentObject() {
217  if ( $this->mPage->getId() === 0 ) {
218  # If this is a MediaWiki:x message, then load the messages
219  # and return the message value for x.
220  if ( $this->getTitle()->getNamespace() == NS_MEDIAWIKI ) {
221  $text = $this->getTitle()->getDefaultMessageText();
222  if ( $text === false ) {
223  $text = '';
224  }
225 
226  $content = ContentHandler::makeContent( $text, $this->getTitle() );
227  } else {
228  $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
229  $content = new MessageContent( $message, null, 'parsemag' );
230  }
231  } else {
232  $this->fetchContentObject();
234  }
235 
236  return $content;
237  }
238 
242  public function getOldID() {
243  if ( is_null( $this->mOldId ) ) {
244  $this->mOldId = $this->getOldIDFromRequest();
245  }
246 
247  return $this->mOldId;
248  }
249 
255  public function getOldIDFromRequest() {
256  $this->mRedirectUrl = false;
257 
258  $request = $this->getContext()->getRequest();
259  $oldid = $request->getIntOrNull( 'oldid' );
260 
261  if ( $oldid === null ) {
262  return 0;
263  }
264 
265  if ( $oldid !== 0 ) {
266  # Load the given revision and check whether the page is another one.
267  # In that case, update this instance to reflect the change.
268  if ( $oldid === $this->mPage->getLatest() ) {
269  $this->mRevision = $this->mPage->getRevision();
270  } else {
271  $this->mRevision = Revision::newFromId( $oldid );
272  if ( $this->mRevision !== null ) {
273  // Revision title doesn't match the page title given?
274  if ( $this->mPage->getId() != $this->mRevision->getPage() ) {
275  $function = [ get_class( $this->mPage ), 'newFromID' ];
276  $this->mPage = call_user_func( $function, $this->mRevision->getPage() );
277  }
278  }
279  }
280  }
281 
282  if ( $request->getVal( 'direction' ) == 'next' ) {
283  $nextid = $this->getTitle()->getNextRevisionID( $oldid );
284  if ( $nextid ) {
285  $oldid = $nextid;
286  $this->mRevision = null;
287  } else {
288  $this->mRedirectUrl = $this->getTitle()->getFullURL( 'redirect=no' );
289  }
290  } elseif ( $request->getVal( 'direction' ) == 'prev' ) {
291  $previd = $this->getTitle()->getPreviousRevisionID( $oldid );
292  if ( $previd ) {
293  $oldid = $previd;
294  $this->mRevision = null;
295  }
296  }
297 
298  return $oldid;
299  }
300 
313  protected function fetchContentObject() {
314  if ( $this->mContentLoaded ) {
315  return $this->mContentObject;
316  }
317 
318  $this->mContentLoaded = true;
319  $this->mContent = null;
320 
321  $oldid = $this->getOldID();
322 
323  # Pre-fill content with error message so that if something
324  # fails we'll have something telling us what we intended.
325  // XXX: this isn't page content but a UI message. horrible.
326  $this->mContentObject = new MessageContent( 'missing-revision', [ $oldid ] );
327 
328  if ( $oldid ) {
329  # $this->mRevision might already be fetched by getOldIDFromRequest()
330  if ( !$this->mRevision ) {
331  $this->mRevision = Revision::newFromId( $oldid );
332  if ( !$this->mRevision ) {
333  wfDebug( __METHOD__ . " failed to retrieve specified revision, id $oldid\n" );
334  return false;
335  }
336  }
337  } else {
338  $oldid = $this->mPage->getLatest();
339  if ( !$oldid ) {
340  wfDebug( __METHOD__ . " failed to find page data for title " .
341  $this->getTitle()->getPrefixedText() . "\n" );
342  return false;
343  }
344 
345  # Update error message with correct oldid
346  $this->mContentObject = new MessageContent( 'missing-revision', [ $oldid ] );
347 
348  $this->mRevision = $this->mPage->getRevision();
349 
350  if ( !$this->mRevision ) {
351  wfDebug( __METHOD__ . " failed to retrieve current page, rev_id $oldid\n" );
352  return false;
353  }
354  }
355 
356  // @todo FIXME: Horrible, horrible! This content-loading interface just plain sucks.
357  // We should instead work with the Revision object when we need it...
358  // Loads if user is allowed
359  $content = $this->mRevision->getContent(
361  $this->getContext()->getUser()
362  );
363 
364  if ( !$content ) {
365  wfDebug( __METHOD__ . " failed to retrieve content of revision " .
366  $this->mRevision->getId() . "\n" );
367  return false;
368  }
369 
370  $this->mContentObject = $content;
371  $this->mRevIdFetched = $this->mRevision->getId();
372 
373  // Avoid PHP 7.1 warning of passing $this by reference
374  $articlePage = $this;
375 
376  Hooks::run(
377  'ArticleAfterFetchContentObject',
378  [ &$articlePage, &$this->mContentObject ]
379  );
380 
381  return $this->mContentObject;
382  }
383 
389  public function isCurrent() {
390  # If no oldid, this is the current version.
391  if ( $this->getOldID() == 0 ) {
392  return true;
393  }
394 
395  return $this->mPage->exists() && $this->mRevision && $this->mRevision->isCurrent();
396  }
397 
405  public function getRevisionFetched() {
406  $this->fetchContentObject();
407 
408  return $this->mRevision;
409  }
410 
416  public function getRevIdFetched() {
417  if ( $this->mRevIdFetched ) {
418  return $this->mRevIdFetched;
419  } else {
420  return $this->mPage->getLatest();
421  }
422  }
423 
428  public function view() {
429  global $wgUseFileCache, $wgDebugToolbar;
430 
431  # Get variables from query string
432  # As side effect this will load the revision and update the title
433  # in a revision ID is passed in the request, so this should remain
434  # the first call of this method even if $oldid is used way below.
435  $oldid = $this->getOldID();
436 
437  $user = $this->getContext()->getUser();
438  # Another whitelist check in case getOldID() is altering the title
439  $permErrors = $this->getTitle()->getUserPermissionsErrors( 'read', $user );
440  if ( count( $permErrors ) ) {
441  wfDebug( __METHOD__ . ": denied on secondary read check\n" );
442  throw new PermissionsError( 'read', $permErrors );
443  }
444 
445  $outputPage = $this->getContext()->getOutput();
446  # getOldID() may as well want us to redirect somewhere else
447  if ( $this->mRedirectUrl ) {
448  $outputPage->redirect( $this->mRedirectUrl );
449  wfDebug( __METHOD__ . ": redirecting due to oldid\n" );
450 
451  return;
452  }
453 
454  # If we got diff in the query, we want to see a diff page instead of the article.
455  if ( $this->getContext()->getRequest()->getCheck( 'diff' ) ) {
456  wfDebug( __METHOD__ . ": showing diff page\n" );
457  $this->showDiffPage();
458 
459  return;
460  }
461 
462  # Set page title (may be overridden by DISPLAYTITLE)
463  $outputPage->setPageTitle( $this->getTitle()->getPrefixedText() );
464 
465  $outputPage->setArticleFlag( true );
466  # Allow frames by default
467  $outputPage->allowClickjacking();
468 
469  $parserCache = ParserCache::singleton();
470 
471  $parserOptions = $this->getParserOptions();
472  # Render printable version, use printable version cache
473  if ( $outputPage->isPrintable() ) {
474  $parserOptions->setIsPrintable( true );
475  $parserOptions->setEditSection( false );
476  } elseif ( !$this->isCurrent() || !$this->getTitle()->quickUserCan( 'edit', $user ) ) {
477  $parserOptions->setEditSection( false );
478  }
479 
480  # Try client and file cache
481  if ( !$wgDebugToolbar && $oldid === 0 && $this->mPage->checkTouched() ) {
482  # Try to stream the output from file cache
483  if ( $wgUseFileCache && $this->tryFileCache() ) {
484  wfDebug( __METHOD__ . ": done file cache\n" );
485  # tell wgOut that output is taken care of
486  $outputPage->disable();
487  $this->mPage->doViewUpdates( $user, $oldid );
488 
489  return;
490  }
491  }
492 
493  # Should the parser cache be used?
494  $useParserCache = $this->mPage->shouldCheckParserCache( $parserOptions, $oldid );
495  wfDebug( 'Article::view using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
496  if ( $user->getStubThreshold() ) {
497  MediaWikiServices::getInstance()->getStatsdDataFactory()->increment( 'pcache_miss_stub' );
498  }
499 
500  $this->showRedirectedFromHeader();
501  $this->showNamespaceHeader();
502 
503  # Iterate through the possible ways of constructing the output text.
504  # Keep going until $outputDone is set, or we run out of things to do.
505  $pass = 0;
506  $outputDone = false;
507  $this->mParserOutput = false;
508 
509  while ( !$outputDone && ++$pass ) {
510  switch ( $pass ) {
511  case 1:
512  // Avoid PHP 7.1 warning of passing $this by reference
513  $articlePage = $this;
514  Hooks::run( 'ArticleViewHeader', [ &$articlePage, &$outputDone, &$useParserCache ] );
515  break;
516  case 2:
517  # Early abort if the page doesn't exist
518  if ( !$this->mPage->exists() ) {
519  wfDebug( __METHOD__ . ": showing missing article\n" );
520  $this->showMissingArticle();
521  $this->mPage->doViewUpdates( $user );
522  return;
523  }
524 
525  # Try the parser cache
526  if ( $useParserCache ) {
527  $this->mParserOutput = $parserCache->get( $this->mPage, $parserOptions );
528 
529  if ( $this->mParserOutput !== false ) {
530  if ( $oldid ) {
531  wfDebug( __METHOD__ . ": showing parser cache contents for current rev permalink\n" );
532  $this->setOldSubtitle( $oldid );
533  } else {
534  wfDebug( __METHOD__ . ": showing parser cache contents\n" );
535  }
536  $outputPage->addParserOutput( $this->mParserOutput );
537  # Ensure that UI elements requiring revision ID have
538  # the correct version information.
539  $outputPage->setRevisionId( $this->mPage->getLatest() );
540  # Preload timestamp to avoid a DB hit
541  $cachedTimestamp = $this->mParserOutput->getTimestamp();
542  if ( $cachedTimestamp !== null ) {
543  $outputPage->setRevisionTimestamp( $cachedTimestamp );
544  $this->mPage->setTimestamp( $cachedTimestamp );
545  }
546  $outputDone = true;
547  }
548  }
549  break;
550  case 3:
551  # This will set $this->mRevision if needed
552  $this->fetchContentObject();
553 
554  # Are we looking at an old revision
555  if ( $oldid && $this->mRevision ) {
556  $this->setOldSubtitle( $oldid );
557 
558  if ( !$this->showDeletedRevisionHeader() ) {
559  wfDebug( __METHOD__ . ": cannot view deleted revision\n" );
560  return;
561  }
562  }
563 
564  # Ensure that UI elements requiring revision ID have
565  # the correct version information.
566  $outputPage->setRevisionId( $this->getRevIdFetched() );
567  # Preload timestamp to avoid a DB hit
568  $outputPage->setRevisionTimestamp( $this->mPage->getTimestamp() );
569 
570  if ( !Hooks::run( 'ArticleContentViewCustom',
571  [ $this->fetchContentObject(), $this->getTitle(), $outputPage ] )
572  ) {
573  # Allow extensions do their own custom view for certain pages
574  $outputDone = true;
575  }
576  break;
577  case 4:
578  # Run the parse, protected by a pool counter
579  wfDebug( __METHOD__ . ": doing uncached parse\n" );
580 
581  $content = $this->getContentObject();
582  $poolArticleView = new PoolWorkArticleView( $this->getPage(), $parserOptions,
583  $this->getRevIdFetched(), $useParserCache, $content );
584 
585  if ( !$poolArticleView->execute() ) {
586  $error = $poolArticleView->getError();
587  if ( $error ) {
588  $outputPage->clearHTML(); // for release() errors
589  $outputPage->enableClientCache( false );
590  $outputPage->setRobotPolicy( 'noindex,nofollow' );
591 
592  $errortext = $error->getWikiText( false, 'view-pool-error' );
593  $outputPage->addWikiText( '<div class="errorbox">' . $errortext . '</div>' );
594  }
595  # Connection or timeout error
596  return;
597  }
598 
599  $this->mParserOutput = $poolArticleView->getParserOutput();
600  $outputPage->addParserOutput( $this->mParserOutput );
601  if ( $content->getRedirectTarget() ) {
602  $outputPage->addSubtitle( "<span id=\"redirectsub\">" .
603  $this->getContext()->msg( 'redirectpagesub' )->parse() . "</span>" );
604  }
605 
606  # Don't cache a dirty ParserOutput object
607  if ( $poolArticleView->getIsDirty() ) {
608  $outputPage->setCdnMaxage( 0 );
609  $outputPage->addHTML( "<!-- parser cache is expired, " .
610  "sending anyway due to pool overload-->\n" );
611  }
612 
613  $outputDone = true;
614  break;
615  # Should be unreachable, but just in case...
616  default:
617  break 2;
618  }
619  }
620 
621  # Get the ParserOutput actually *displayed* here.
622  # Note that $this->mParserOutput is the *current*/oldid version output.
623  $pOutput = ( $outputDone instanceof ParserOutput )
624  ? $outputDone // object fetched by hook
625  : $this->mParserOutput;
626 
627  # Adjust title for main page & pages with displaytitle
628  if ( $pOutput ) {
629  $this->adjustDisplayTitle( $pOutput );
630  }
631 
632  # For the main page, overwrite the <title> element with the con-
633  # tents of 'pagetitle-view-mainpage' instead of the default (if
634  # that's not empty).
635  # This message always exists because it is in the i18n files
636  if ( $this->getTitle()->isMainPage() ) {
637  $msg = wfMessage( 'pagetitle-view-mainpage' )->inContentLanguage();
638  if ( !$msg->isDisabled() ) {
639  $outputPage->setHTMLTitle( $msg->title( $this->getTitle() )->text() );
640  }
641  }
642 
643  # Use adaptive TTLs for CDN so delayed/failed purges are noticed less often.
644  # This could use getTouched(), but that could be scary for major template edits.
645  $outputPage->adaptCdnTTL( $this->mPage->getTimestamp(), IExpiringStore::TTL_DAY );
646 
647  # Check for any __NOINDEX__ tags on the page using $pOutput
648  $policy = $this->getRobotPolicy( 'view', $pOutput );
649  $outputPage->setIndexPolicy( $policy['index'] );
650  $outputPage->setFollowPolicy( $policy['follow'] );
651 
652  $this->showViewFooter();
653  $this->mPage->doViewUpdates( $user, $oldid );
654 
655  # Load the postEdit module if the user just saved this revision
656  # See also EditPage::setPostEditCookie
657  $request = $this->getContext()->getRequest();
659  $postEdit = $request->getCookie( $cookieKey );
660  if ( $postEdit ) {
661  # Clear the cookie. This also prevents caching of the response.
662  $request->response()->clearCookie( $cookieKey );
663  $outputPage->addJsConfigVars( 'wgPostEdit', $postEdit );
664  $outputPage->addModules( 'mediawiki.action.view.postEdit' );
665  }
666  }
667 
672  public function adjustDisplayTitle( ParserOutput $pOutput ) {
673  # Adjust the title if it was set by displaytitle, -{T|}- or language conversion
674  $titleText = $pOutput->getTitleText();
675  if ( strval( $titleText ) !== '' ) {
676  $this->getContext()->getOutput()->setPageTitle( $titleText );
677  }
678  }
679 
684  protected function showDiffPage() {
685  $request = $this->getContext()->getRequest();
686  $user = $this->getContext()->getUser();
687  $diff = $request->getVal( 'diff' );
688  $rcid = $request->getVal( 'rcid' );
689  $diffOnly = $request->getBool( 'diffonly', $user->getOption( 'diffonly' ) );
690  $purge = $request->getVal( 'action' ) == 'purge';
691  $unhide = $request->getInt( 'unhide' ) == 1;
692  $oldid = $this->getOldID();
693 
694  $rev = $this->getRevisionFetched();
695 
696  if ( !$rev ) {
697  $this->getContext()->getOutput()->setPageTitle( wfMessage( 'errorpagetitle' ) );
698  $msg = $this->getContext()->msg( 'difference-missing-revision' )
699  ->params( $oldid )
700  ->numParams( 1 )
701  ->parseAsBlock();
702  $this->getContext()->getOutput()->addHTML( $msg );
703  return;
704  }
705 
706  $contentHandler = $rev->getContentHandler();
707  $de = $contentHandler->createDifferenceEngine(
708  $this->getContext(),
709  $oldid,
710  $diff,
711  $rcid,
712  $purge,
713  $unhide
714  );
715 
716  // DifferenceEngine directly fetched the revision:
717  $this->mRevIdFetched = $de->mNewid;
718  $de->showDiffPage( $diffOnly );
719 
720  // Run view updates for the newer revision being diffed (and shown
721  // below the diff if not $diffOnly).
722  list( $old, $new ) = $de->mapDiffPrevNext( $oldid, $diff );
723  // New can be false, convert it to 0 - this conveniently means the latest revision
724  $this->mPage->doViewUpdates( $user, (int)$new );
725  }
726 
734  public function getRobotPolicy( $action, $pOutput = null ) {
735  global $wgArticleRobotPolicies, $wgNamespaceRobotPolicies, $wgDefaultRobotPolicy;
736 
737  $ns = $this->getTitle()->getNamespace();
738 
739  # Don't index user and user talk pages for blocked users (T13443)
740  if ( ( $ns == NS_USER || $ns == NS_USER_TALK ) && !$this->getTitle()->isSubpage() ) {
741  $specificTarget = null;
742  $vagueTarget = null;
743  $titleText = $this->getTitle()->getText();
744  if ( IP::isValid( $titleText ) ) {
745  $vagueTarget = $titleText;
746  } else {
747  $specificTarget = $titleText;
748  }
749  if ( Block::newFromTarget( $specificTarget, $vagueTarget ) instanceof Block ) {
750  return [
751  'index' => 'noindex',
752  'follow' => 'nofollow'
753  ];
754  }
755  }
756 
757  if ( $this->mPage->getId() === 0 || $this->getOldID() ) {
758  # Non-articles (special pages etc), and old revisions
759  return [
760  'index' => 'noindex',
761  'follow' => 'nofollow'
762  ];
763  } elseif ( $this->getContext()->getOutput()->isPrintable() ) {
764  # Discourage indexing of printable versions, but encourage following
765  return [
766  'index' => 'noindex',
767  'follow' => 'follow'
768  ];
769  } elseif ( $this->getContext()->getRequest()->getInt( 'curid' ) ) {
770  # For ?curid=x urls, disallow indexing
771  return [
772  'index' => 'noindex',
773  'follow' => 'follow'
774  ];
775  }
776 
777  # Otherwise, construct the policy based on the various config variables.
778  $policy = self::formatRobotPolicy( $wgDefaultRobotPolicy );
779 
780  if ( isset( $wgNamespaceRobotPolicies[$ns] ) ) {
781  # Honour customised robot policies for this namespace
782  $policy = array_merge(
783  $policy,
784  self::formatRobotPolicy( $wgNamespaceRobotPolicies[$ns] )
785  );
786  }
787  if ( $this->getTitle()->canUseNoindex() && is_object( $pOutput ) && $pOutput->getIndexPolicy() ) {
788  # __INDEX__ and __NOINDEX__ magic words, if allowed. Incorporates
789  # a final sanity check that we have really got the parser output.
790  $policy = array_merge(
791  $policy,
792  [ 'index' => $pOutput->getIndexPolicy() ]
793  );
794  }
795 
796  if ( isset( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] ) ) {
797  # (T16900) site config can override user-defined __INDEX__ or __NOINDEX__
798  $policy = array_merge(
799  $policy,
800  self::formatRobotPolicy( $wgArticleRobotPolicies[$this->getTitle()->getPrefixedText()] )
801  );
802  }
803 
804  return $policy;
805  }
806 
814  public static function formatRobotPolicy( $policy ) {
815  if ( is_array( $policy ) ) {
816  return $policy;
817  } elseif ( !$policy ) {
818  return [];
819  }
820 
821  $policy = explode( ',', $policy );
822  $policy = array_map( 'trim', $policy );
823 
824  $arr = [];
825  foreach ( $policy as $var ) {
826  if ( in_array( $var, [ 'index', 'noindex' ] ) ) {
827  $arr['index'] = $var;
828  } elseif ( in_array( $var, [ 'follow', 'nofollow' ] ) ) {
829  $arr['follow'] = $var;
830  }
831  }
832 
833  return $arr;
834  }
835 
843  public function showRedirectedFromHeader() {
845 
846  $context = $this->getContext();
847  $outputPage = $context->getOutput();
848  $request = $context->getRequest();
849  $rdfrom = $request->getVal( 'rdfrom' );
850 
851  // Construct a URL for the current page view, but with the target title
852  $query = $request->getValues();
853  unset( $query['rdfrom'] );
854  unset( $query['title'] );
855  if ( $this->getTitle()->isRedirect() ) {
856  // Prevent double redirects
857  $query['redirect'] = 'no';
858  }
859  $redirectTargetUrl = $this->getTitle()->getLinkURL( $query );
860 
861  if ( isset( $this->mRedirectedFrom ) ) {
862  // Avoid PHP 7.1 warning of passing $this by reference
863  $articlePage = $this;
864 
865  // This is an internally redirected page view.
866  // We'll need a backlink to the source page for navigation.
867  if ( Hooks::run( 'ArticleViewRedirect', [ &$articlePage ] ) ) {
868  $redir = Linker::linkKnown(
869  $this->mRedirectedFrom,
870  null,
871  [],
872  [ 'redirect' => 'no' ]
873  );
874 
875  $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
876  $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
877  . "</span>" );
878 
879  // Add the script to update the displayed URL and
880  // set the fragment if one was specified in the redirect
881  $outputPage->addJsConfigVars( [
882  'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
883  ] );
884  $outputPage->addModules( 'mediawiki.action.view.redirect' );
885 
886  // Add a <link rel="canonical"> tag
887  $outputPage->setCanonicalUrl( $this->getTitle()->getCanonicalURL() );
888 
889  // Tell the output object that the user arrived at this article through a redirect
890  $outputPage->setRedirectedFrom( $this->mRedirectedFrom );
891 
892  return true;
893  }
894  } elseif ( $rdfrom ) {
895  // This is an externally redirected view, from some other wiki.
896  // If it was reported from a trusted site, supply a backlink.
897  if ( $wgRedirectSources && preg_match( $wgRedirectSources, $rdfrom ) ) {
898  $redir = Linker::makeExternalLink( $rdfrom, $rdfrom );
899  $outputPage->addSubtitle( "<span class=\"mw-redirectedfrom\">" .
900  $context->msg( 'redirectedfrom' )->rawParams( $redir )->parse()
901  . "</span>" );
902 
903  // Add the script to update the displayed URL
904  $outputPage->addJsConfigVars( [
905  'wgInternalRedirectTargetUrl' => $redirectTargetUrl,
906  ] );
907  $outputPage->addModules( 'mediawiki.action.view.redirect' );
908 
909  return true;
910  }
911  }
912 
913  return false;
914  }
915 
920  public function showNamespaceHeader() {
921  if ( $this->getTitle()->isTalkPage() ) {
922  if ( !wfMessage( 'talkpageheader' )->isDisabled() ) {
923  $this->getContext()->getOutput()->wrapWikiMsg(
924  "<div class=\"mw-talkpageheader\">\n$1\n</div>",
925  [ 'talkpageheader' ]
926  );
927  }
928  }
929  }
930 
934  public function showViewFooter() {
935  # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page
936  if ( $this->getTitle()->getNamespace() == NS_USER_TALK
937  && IP::isValid( $this->getTitle()->getText() )
938  ) {
939  $this->getContext()->getOutput()->addWikiMsg( 'anontalkpagetext' );
940  }
941 
942  // Show a footer allowing the user to patrol the shown revision or page if possible
943  $patrolFooterShown = $this->showPatrolFooter();
944 
945  Hooks::run( 'ArticleViewFooter', [ $this, $patrolFooterShown ] );
946  }
947 
957  public function showPatrolFooter() {
958  global $wgUseNPPatrol, $wgUseRCPatrol, $wgUseFilePatrol, $wgEnableAPI, $wgEnableWriteAPI;
959 
960  $outputPage = $this->getContext()->getOutput();
961  $user = $this->getContext()->getUser();
962  $title = $this->getTitle();
963  $rc = false;
964 
965  if ( !$title->quickUserCan( 'patrol', $user )
966  || !( $wgUseRCPatrol || $wgUseNPPatrol
967  || ( $wgUseFilePatrol && $title->inNamespace( NS_FILE ) ) )
968  ) {
969  // Patrolling is disabled or the user isn't allowed to
970  return false;
971  }
972 
973  if ( $this->mRevision
974  && !RecentChange::isInRCLifespan( $this->mRevision->getTimestamp(), 21600 )
975  ) {
976  // The current revision is already older than what could be in the RC table
977  // 6h tolerance because the RC might not be cleaned out regularly
978  return false;
979  }
980 
981  // Check for cached results
982  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
983  $key = $cache->makeKey( 'unpatrollable-page', $title->getArticleID() );
984  if ( $cache->get( $key ) ) {
985  return false;
986  }
987 
988  $dbr = wfGetDB( DB_REPLICA );
989  $oldestRevisionTimestamp = $dbr->selectField(
990  'revision',
991  'MIN( rev_timestamp )',
992  [ 'rev_page' => $title->getArticleID() ],
993  __METHOD__
994  );
995 
996  // New page patrol: Get the timestamp of the oldest revison which
997  // the revision table holds for the given page. Then we look
998  // whether it's within the RC lifespan and if it is, we try
999  // to get the recentchanges row belonging to that entry
1000  // (with rc_new = 1).
1001  $recentPageCreation = false;
1002  if ( $oldestRevisionTimestamp
1003  && RecentChange::isInRCLifespan( $oldestRevisionTimestamp, 21600 )
1004  ) {
1005  // 6h tolerance because the RC might not be cleaned out regularly
1006  $recentPageCreation = true;
1008  [
1009  'rc_new' => 1,
1010  'rc_timestamp' => $oldestRevisionTimestamp,
1011  'rc_namespace' => $title->getNamespace(),
1012  'rc_cur_id' => $title->getArticleID()
1013  ],
1014  __METHOD__
1015  );
1016  if ( $rc ) {
1017  // Use generic patrol message for new pages
1018  $markPatrolledMsg = wfMessage( 'markaspatrolledtext' );
1019  }
1020  }
1021 
1022  // File patrol: Get the timestamp of the latest upload for this page,
1023  // check whether it is within the RC lifespan and if it is, we try
1024  // to get the recentchanges row belonging to that entry
1025  // (with rc_type = RC_LOG, rc_log_type = upload).
1026  $recentFileUpload = false;
1027  if ( ( !$rc || $rc->getAttribute( 'rc_patrolled' ) ) && $wgUseFilePatrol
1028  && $title->getNamespace() === NS_FILE ) {
1029  // Retrieve timestamp of most recent upload
1030  $newestUploadTimestamp = $dbr->selectField(
1031  'image',
1032  'MAX( img_timestamp )',
1033  [ 'img_name' => $title->getDBkey() ],
1034  __METHOD__
1035  );
1036  if ( $newestUploadTimestamp
1037  && RecentChange::isInRCLifespan( $newestUploadTimestamp, 21600 )
1038  ) {
1039  // 6h tolerance because the RC might not be cleaned out regularly
1040  $recentFileUpload = true;
1042  [
1043  'rc_type' => RC_LOG,
1044  'rc_log_type' => 'upload',
1045  'rc_timestamp' => $newestUploadTimestamp,
1046  'rc_namespace' => NS_FILE,
1047  'rc_cur_id' => $title->getArticleID()
1048  ],
1049  __METHOD__,
1050  [ 'USE INDEX' => 'rc_timestamp' ]
1051  );
1052  if ( $rc ) {
1053  // Use patrol message specific to files
1054  $markPatrolledMsg = wfMessage( 'markaspatrolledtext-file' );
1055  }
1056  }
1057  }
1058 
1059  if ( !$recentPageCreation && !$recentFileUpload ) {
1060  // Page creation and latest upload (for files) is too old to be in RC
1061 
1062  // We definitely can't patrol so cache the information
1063  // When a new file version is uploaded, the cache is cleared
1064  $cache->set( $key, '1' );
1065 
1066  return false;
1067  }
1068 
1069  if ( !$rc ) {
1070  // Don't cache: This can be hit if the page gets accessed very fast after
1071  // its creation / latest upload or in case we have high replica DB lag. In case
1072  // the revision is too old, we will already return above.
1073  return false;
1074  }
1075 
1076  if ( $rc->getAttribute( 'rc_patrolled' ) ) {
1077  // Patrolled RC entry around
1078 
1079  // Cache the information we gathered above in case we can't patrol
1080  // Don't cache in case we can patrol as this could change
1081  $cache->set( $key, '1' );
1082 
1083  return false;
1084  }
1085 
1086  if ( $rc->getPerformer()->equals( $user ) ) {
1087  // Don't show a patrol link for own creations/uploads. If the user could
1088  // patrol them, they already would be patrolled
1089  return false;
1090  }
1091 
1092  $outputPage->preventClickjacking();
1093  if ( $wgEnableAPI && $wgEnableWriteAPI && $user->isAllowed( 'writeapi' ) ) {
1094  $outputPage->addModules( 'mediawiki.page.patrol.ajax' );
1095  }
1096 
1098  $title,
1099  $markPatrolledMsg->escaped(),
1100  [],
1101  [
1102  'action' => 'markpatrolled',
1103  'rcid' => $rc->getAttribute( 'rc_id' ),
1104  ]
1105  );
1106 
1107  $outputPage->addHTML(
1108  "<div class='patrollink' data-mw='interface'>" .
1109  wfMessage( 'markaspatrolledlink' )->rawParams( $link )->escaped() .
1110  '</div>'
1111  );
1112 
1113  return true;
1114  }
1115 
1122  public static function purgePatrolFooterCache( $articleID ) {
1123  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
1124  $cache->delete( $cache->makeKey( 'unpatrollable-page', $articleID ) );
1125  }
1126 
1131  public function showMissingArticle() {
1133 
1134  $outputPage = $this->getContext()->getOutput();
1135  // Whether the page is a root user page of an existing user (but not a subpage)
1136  $validUserPage = false;
1137 
1138  $title = $this->getTitle();
1139 
1140  # Show info in user (talk) namespace. Does the user exist? Is he blocked?
1141  if ( $title->getNamespace() == NS_USER
1142  || $title->getNamespace() == NS_USER_TALK
1143  ) {
1144  $rootPart = explode( '/', $title->getText() )[0];
1145  $user = User::newFromName( $rootPart, false /* allow IP users */ );
1146  $ip = User::isIP( $rootPart );
1147  $block = Block::newFromTarget( $user, $user );
1148 
1149  if ( !( $user && $user->isLoggedIn() ) && !$ip ) { # User does not exist
1150  $outputPage->wrapWikiMsg( "<div class=\"mw-userpage-userdoesnotexist error\">\n\$1\n</div>",
1151  [ 'userpage-userdoesnotexist-view', wfEscapeWikiText( $rootPart ) ] );
1152  } elseif ( !is_null( $block ) && $block->getType() != Block::TYPE_AUTO ) {
1153  # Show log extract if the user is currently blocked
1155  $outputPage,
1156  'block',
1157  MWNamespace::getCanonicalName( NS_USER ) . ':' . $block->getTarget(),
1158  '',
1159  [
1160  'lim' => 1,
1161  'showIfEmpty' => false,
1162  'msgKey' => [
1163  'blocked-notice-logextract',
1164  $user->getName() # Support GENDER in notice
1165  ]
1166  ]
1167  );
1168  $validUserPage = !$title->isSubpage();
1169  } else {
1170  $validUserPage = !$title->isSubpage();
1171  }
1172  }
1173 
1174  Hooks::run( 'ShowMissingArticle', [ $this ] );
1175 
1176  # Show delete and move logs if there were any such events.
1177  # The logging query can DOS the site when bots/crawlers cause 404 floods,
1178  # so be careful showing this. 404 pages must be cheap as they are hard to cache.
1179  $cache = MediaWikiServices::getInstance()->getMainObjectStash();
1180  $key = $cache->makeKey( 'page-recent-delete', md5( $title->getPrefixedText() ) );
1181  $loggedIn = $this->getContext()->getUser()->isLoggedIn();
1182  if ( $loggedIn || $cache->get( $key ) ) {
1183  $logTypes = [ 'delete', 'move', 'protect' ];
1184 
1185  $dbr = wfGetDB( DB_REPLICA );
1186 
1187  $conds = [ 'log_action != ' . $dbr->addQuotes( 'revision' ) ];
1188  // Give extensions a chance to hide their (unrelated) log entries
1189  Hooks::run( 'Article::MissingArticleConditions', [ &$conds, $logTypes ] );
1191  $outputPage,
1192  $logTypes,
1193  $title,
1194  '',
1195  [
1196  'lim' => 10,
1197  'conds' => $conds,
1198  'showIfEmpty' => false,
1199  'msgKey' => [ $loggedIn
1200  ? 'moveddeleted-notice'
1201  : 'moveddeleted-notice-recent'
1202  ]
1203  ]
1204  );
1205  }
1206 
1207  if ( !$this->mPage->hasViewableContent() && $wgSend404Code && !$validUserPage ) {
1208  // If there's no backing content, send a 404 Not Found
1209  // for better machine handling of broken links.
1210  $this->getContext()->getRequest()->response()->statusHeader( 404 );
1211  }
1212 
1213  // Also apply the robot policy for nonexisting pages (even if a 404 was used for sanity)
1214  $policy = $this->getRobotPolicy( 'view' );
1215  $outputPage->setIndexPolicy( $policy['index'] );
1216  $outputPage->setFollowPolicy( $policy['follow'] );
1217 
1218  $hookResult = Hooks::run( 'BeforeDisplayNoArticleText', [ $this ] );
1219 
1220  if ( !$hookResult ) {
1221  return;
1222  }
1223 
1224  # Show error message
1225  $oldid = $this->getOldID();
1226  if ( !$oldid && $title->getNamespace() === NS_MEDIAWIKI && $title->hasSourceText() ) {
1227  $outputPage->addParserOutput( $this->getContentObject()->getParserOutput( $title ) );
1228  } else {
1229  if ( $oldid ) {
1230  $text = wfMessage( 'missing-revision', $oldid )->plain();
1231  } elseif ( $title->quickUserCan( 'create', $this->getContext()->getUser() )
1232  && $title->quickUserCan( 'edit', $this->getContext()->getUser() )
1233  ) {
1234  $message = $this->getContext()->getUser()->isLoggedIn() ? 'noarticletext' : 'noarticletextanon';
1235  $text = wfMessage( $message )->plain();
1236  } else {
1237  $text = wfMessage( 'noarticletext-nopermission' )->plain();
1238  }
1239 
1240  $dir = $this->getContext()->getLanguage()->getDir();
1241  $lang = $this->getContext()->getLanguage()->getCode();
1242  $outputPage->addWikiText( Xml::openElement( 'div', [
1243  'class' => "noarticletext mw-content-$dir",
1244  'dir' => $dir,
1245  'lang' => $lang,
1246  ] ) . "\n$text\n</div>" );
1247  }
1248  }
1249 
1256  public function showDeletedRevisionHeader() {
1257  if ( !$this->mRevision->isDeleted( Revision::DELETED_TEXT ) ) {
1258  // Not deleted
1259  return true;
1260  }
1261 
1262  $outputPage = $this->getContext()->getOutput();
1263  $user = $this->getContext()->getUser();
1264  // If the user is not allowed to see it...
1265  if ( !$this->mRevision->userCan( Revision::DELETED_TEXT, $user ) ) {
1266  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1267  'rev-deleted-text-permission' );
1268 
1269  return false;
1270  // If the user needs to confirm that they want to see it...
1271  } elseif ( $this->getContext()->getRequest()->getInt( 'unhide' ) != 1 ) {
1272  # Give explanation and add a link to view the revision...
1273  $oldid = intval( $this->getOldID() );
1274  $link = $this->getTitle()->getFullURL( "oldid={$oldid}&unhide=1" );
1275  $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
1276  'rev-suppressed-text-unhide' : 'rev-deleted-text-unhide';
1277  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1278  [ $msg, $link ] );
1279 
1280  return false;
1281  // We are allowed to see...
1282  } else {
1283  $msg = $this->mRevision->isDeleted( Revision::DELETED_RESTRICTED ) ?
1284  'rev-suppressed-text-view' : 'rev-deleted-text-view';
1285  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n", $msg );
1286 
1287  return true;
1288  }
1289  }
1290 
1299  public function setOldSubtitle( $oldid = 0 ) {
1300  // Avoid PHP 7.1 warning of passing $this by reference
1301  $articlePage = $this;
1302 
1303  if ( !Hooks::run( 'DisplayOldSubtitle', [ &$articlePage, &$oldid ] ) ) {
1304  return;
1305  }
1306 
1307  $context = $this->getContext();
1308  $unhide = $context->getRequest()->getInt( 'unhide' ) == 1;
1309 
1310  # Cascade unhide param in links for easy deletion browsing
1311  $extraParams = [];
1312  if ( $unhide ) {
1313  $extraParams['unhide'] = 1;
1314  }
1315 
1316  if ( $this->mRevision && $this->mRevision->getId() === $oldid ) {
1317  $revision = $this->mRevision;
1318  } else {
1319  $revision = Revision::newFromId( $oldid );
1320  }
1321 
1322  $timestamp = $revision->getTimestamp();
1323 
1324  $current = ( $oldid == $this->mPage->getLatest() );
1325  $language = $context->getLanguage();
1326  $user = $context->getUser();
1327 
1328  $td = $language->userTimeAndDate( $timestamp, $user );
1329  $tddate = $language->userDate( $timestamp, $user );
1330  $tdtime = $language->userTime( $timestamp, $user );
1331 
1332  # Show user links if allowed to see them. If hidden, then show them only if requested...
1333  $userlinks = Linker::revUserTools( $revision, !$unhide );
1334 
1335  $infomsg = $current && !$context->msg( 'revision-info-current' )->isDisabled()
1336  ? 'revision-info-current'
1337  : 'revision-info';
1338 
1339  $outputPage = $context->getOutput();
1340  $revisionInfo = "<div id=\"mw-{$infomsg}\">" .
1341  $context->msg( $infomsg, $td )
1342  ->rawParams( $userlinks )
1343  ->params( $revision->getId(), $tddate, $tdtime, $revision->getUserText() )
1344  ->rawParams( Linker::revComment( $revision, true, true ) )
1345  ->parse() .
1346  "</div>";
1347 
1348  $lnk = $current
1349  ? $context->msg( 'currentrevisionlink' )->escaped()
1351  $this->getTitle(),
1352  $context->msg( 'currentrevisionlink' )->escaped(),
1353  [],
1354  $extraParams
1355  );
1356  $curdiff = $current
1357  ? $context->msg( 'diff' )->escaped()
1359  $this->getTitle(),
1360  $context->msg( 'diff' )->escaped(),
1361  [],
1362  [
1363  'diff' => 'cur',
1364  'oldid' => $oldid
1365  ] + $extraParams
1366  );
1367  $prev = $this->getTitle()->getPreviousRevisionID( $oldid );
1368  $prevlink = $prev
1370  $this->getTitle(),
1371  $context->msg( 'previousrevision' )->escaped(),
1372  [],
1373  [
1374  'direction' => 'prev',
1375  'oldid' => $oldid
1376  ] + $extraParams
1377  )
1378  : $context->msg( 'previousrevision' )->escaped();
1379  $prevdiff = $prev
1381  $this->getTitle(),
1382  $context->msg( 'diff' )->escaped(),
1383  [],
1384  [
1385  'diff' => 'prev',
1386  'oldid' => $oldid
1387  ] + $extraParams
1388  )
1389  : $context->msg( 'diff' )->escaped();
1390  $nextlink = $current
1391  ? $context->msg( 'nextrevision' )->escaped()
1393  $this->getTitle(),
1394  $context->msg( 'nextrevision' )->escaped(),
1395  [],
1396  [
1397  'direction' => 'next',
1398  'oldid' => $oldid
1399  ] + $extraParams
1400  );
1401  $nextdiff = $current
1402  ? $context->msg( 'diff' )->escaped()
1404  $this->getTitle(),
1405  $context->msg( 'diff' )->escaped(),
1406  [],
1407  [
1408  'diff' => 'next',
1409  'oldid' => $oldid
1410  ] + $extraParams
1411  );
1412 
1413  $cdel = Linker::getRevDeleteLink( $user, $revision, $this->getTitle() );
1414  if ( $cdel !== '' ) {
1415  $cdel .= ' ';
1416  }
1417 
1418  // the outer div is need for styling the revision info and nav in MobileFrontend
1419  $outputPage->addSubtitle( "<div class=\"mw-revision\">" . $revisionInfo .
1420  "<div id=\"mw-revision-nav\">" . $cdel .
1421  $context->msg( 'revision-nav' )->rawParams(
1422  $prevdiff, $prevlink, $lnk, $curdiff, $nextlink, $nextdiff
1423  )->escaped() . "</div></div>" );
1424  }
1425 
1439  public function viewRedirect( $target, $appendSubtitle = true, $forceKnown = false ) {
1440  $lang = $this->getTitle()->getPageLanguage();
1441  $out = $this->getContext()->getOutput();
1442  if ( $appendSubtitle ) {
1443  $out->addSubtitle( wfMessage( 'redirectpagesub' ) );
1444  }
1445  $out->addModuleStyles( 'mediawiki.action.view.redirectPage' );
1446  return static::getRedirectHeaderHtml( $lang, $target, $forceKnown );
1447  }
1448 
1461  public static function getRedirectHeaderHtml( Language $lang, $target, $forceKnown = false ) {
1462  if ( !is_array( $target ) ) {
1463  $target = [ $target ];
1464  }
1465 
1466  $html = '<ul class="redirectText">';
1468  foreach ( $target as $title ) {
1469  $html .= '<li>' . Linker::link(
1470  $title,
1471  htmlspecialchars( $title->getFullText() ),
1472  [],
1473  // Make sure wiki page redirects are not followed
1474  $title->isRedirect() ? [ 'redirect' => 'no' ] : [],
1475  ( $forceKnown ? [ 'known', 'noclasses' ] : [] )
1476  ) . '</li>';
1477  }
1478  $html .= '</ul>';
1479 
1480  $redirectToText = wfMessage( 'redirectto' )->inLanguage( $lang )->escaped();
1481 
1482  return '<div class="redirectMsg">' .
1483  '<p>' . $redirectToText . '</p>' .
1484  $html .
1485  '</div>';
1486  }
1487 
1496  public function addHelpLink( $to, $overrideBaseUrl = false ) {
1497  $msg = wfMessage(
1498  'namespace-' . $this->getTitle()->getNamespace() . '-helppage'
1499  );
1500 
1501  $out = $this->getContext()->getOutput();
1502  if ( !$msg->isDisabled() ) {
1503  $helpUrl = Skin::makeUrl( $msg->plain() );
1504  $out->addHelpLink( $helpUrl, true );
1505  } else {
1506  $out->addHelpLink( $to, $overrideBaseUrl );
1507  }
1508  }
1509 
1513  public function render() {
1514  $this->getContext()->getRequest()->response()->header( 'X-Robots-Tag: noindex' );
1515  $this->getContext()->getOutput()->setArticleBodyOnly( true );
1516  $this->getContext()->getOutput()->enableSectionEditLinks( false );
1517  $this->view();
1518  }
1519 
1523  public function protect() {
1524  $form = new ProtectionForm( $this );
1525  $form->execute();
1526  }
1527 
1531  public function unprotect() {
1532  $this->protect();
1533  }
1534 
1538  public function delete() {
1539  # This code desperately needs to be totally rewritten
1540 
1541  $title = $this->getTitle();
1542  $context = $this->getContext();
1543  $user = $context->getUser();
1544  $request = $context->getRequest();
1545 
1546  # Check permissions
1547  $permissionErrors = $title->getUserPermissionsErrors( 'delete', $user );
1548  if ( count( $permissionErrors ) ) {
1549  throw new PermissionsError( 'delete', $permissionErrors );
1550  }
1551 
1552  # Read-only check...
1553  if ( wfReadOnly() ) {
1554  throw new ReadOnlyError;
1555  }
1556 
1557  # Better double-check that it hasn't been deleted yet!
1558  $this->mPage->loadPageData(
1559  $request->wasPosted() ? WikiPage::READ_LATEST : WikiPage::READ_NORMAL
1560  );
1561  if ( !$this->mPage->exists() ) {
1562  $deleteLogPage = new LogPage( 'delete' );
1563  $outputPage = $context->getOutput();
1564  $outputPage->setPageTitle( $context->msg( 'cannotdelete-title', $title->getPrefixedText() ) );
1565  $outputPage->wrapWikiMsg( "<div class=\"error mw-error-cannotdelete\">\n$1\n</div>",
1566  [ 'cannotdelete', wfEscapeWikiText( $title->getPrefixedText() ) ]
1567  );
1568  $outputPage->addHTML(
1569  Xml::element( 'h2', null, $deleteLogPage->getName()->text() )
1570  );
1572  $outputPage,
1573  'delete',
1574  $title
1575  );
1576 
1577  return;
1578  }
1579 
1580  $deleteReasonList = $request->getText( 'wpDeleteReasonList', 'other' );
1581  $deleteReason = $request->getText( 'wpReason' );
1582 
1583  if ( $deleteReasonList == 'other' ) {
1584  $reason = $deleteReason;
1585  } elseif ( $deleteReason != '' ) {
1586  // Entry from drop down menu + additional comment
1587  $colonseparator = wfMessage( 'colon-separator' )->inContentLanguage()->text();
1588  $reason = $deleteReasonList . $colonseparator . $deleteReason;
1589  } else {
1590  $reason = $deleteReasonList;
1591  }
1592 
1593  if ( $request->wasPosted() && $user->matchEditToken( $request->getVal( 'wpEditToken' ),
1594  [ 'delete', $this->getTitle()->getPrefixedText() ] )
1595  ) {
1596  # Flag to hide all contents of the archived revisions
1597  $suppress = $request->getCheck( 'wpSuppress' ) && $user->isAllowed( 'suppressrevision' );
1598 
1599  $this->doDelete( $reason, $suppress );
1600 
1601  WatchAction::doWatchOrUnwatch( $request->getCheck( 'wpWatch' ), $title, $user );
1602 
1603  return;
1604  }
1605 
1606  // Generate deletion reason
1607  $hasHistory = false;
1608  if ( !$reason ) {
1609  try {
1610  $reason = $this->generateReason( $hasHistory );
1611  } catch ( Exception $e ) {
1612  # if a page is horribly broken, we still want to be able to
1613  # delete it. So be lenient about errors here.
1614  wfDebug( "Error while building auto delete summary: $e" );
1615  $reason = '';
1616  }
1617  }
1618 
1619  // If the page has a history, insert a warning
1620  if ( $hasHistory ) {
1621  $title = $this->getTitle();
1622 
1623  // The following can use the real revision count as this is only being shown for users
1624  // that can delete this page.
1625  // This, as a side-effect, also makes sure that the following query isn't being run for
1626  // pages with a larger history, unless the user has the 'bigdelete' right
1627  // (and is about to delete this page).
1628  $dbr = wfGetDB( DB_REPLICA );
1629  $revisions = $edits = (int)$dbr->selectField(
1630  'revision',
1631  'COUNT(rev_page)',
1632  [ 'rev_page' => $title->getArticleID() ],
1633  __METHOD__
1634  );
1635 
1636  // @todo FIXME: i18n issue/patchwork message
1637  $context->getOutput()->addHTML(
1638  '<strong class="mw-delete-warning-revisions">' .
1639  $context->msg( 'historywarning' )->numParams( $revisions )->parse() .
1640  $context->msg( 'word-separator' )->escaped() . Linker::linkKnown( $title,
1641  $context->msg( 'history' )->escaped(),
1642  [],
1643  [ 'action' => 'history' ] ) .
1644  '</strong>'
1645  );
1646 
1647  if ( $title->isBigDeletion() ) {
1648  global $wgDeleteRevisionsLimit;
1649  $context->getOutput()->wrapWikiMsg( "<div class='error'>\n$1\n</div>\n",
1650  [
1651  'delete-warning-toobig',
1652  $context->getLanguage()->formatNum( $wgDeleteRevisionsLimit )
1653  ]
1654  );
1655  }
1656  }
1657 
1658  $this->confirmDelete( $reason );
1659  }
1660 
1666  public function confirmDelete( $reason ) {
1667  wfDebug( "Article::confirmDelete\n" );
1668 
1669  $title = $this->getTitle();
1670  $ctx = $this->getContext();
1671  $outputPage = $ctx->getOutput();
1672  $outputPage->setPageTitle( wfMessage( 'delete-confirm', $title->getPrefixedText() ) );
1673  $outputPage->addBacklinkSubtitle( $title );
1674  $outputPage->setRobotPolicy( 'noindex,nofollow' );
1675 
1676  $backlinkCache = $title->getBacklinkCache();
1677  if ( $backlinkCache->hasLinks( 'pagelinks' ) || $backlinkCache->hasLinks( 'templatelinks' ) ) {
1678  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1679  'deleting-backlinks-warning' );
1680  }
1681 
1682  $subpageQueryLimit = 51;
1683  $subpages = $title->getSubpages( $subpageQueryLimit );
1684  $subpageCount = count( $subpages );
1685  if ( $subpageCount > 0 ) {
1686  $outputPage->wrapWikiMsg( "<div class='mw-warning plainlinks'>\n$1\n</div>\n",
1687  [ 'deleting-subpages-warning', Message::numParam( $subpageCount ) ] );
1688  }
1689  $outputPage->addWikiMsg( 'confirmdeletetext' );
1690 
1691  Hooks::run( 'ArticleConfirmDelete', [ $this, $outputPage, &$reason ] );
1692 
1693  $user = $this->getContext()->getUser();
1694  $checkWatch = $user->getBoolOption( 'watchdeletion' ) || $user->isWatched( $title );
1695 
1696  $outputPage->enableOOUI();
1697 
1698  $options = [];
1699  $options[] = [
1700  'data' => 'other',
1701  'label' => $ctx->msg( 'deletereasonotherlist' )->inContentLanguage()->text(),
1702  ];
1703  $list = $ctx->msg( 'deletereason-dropdown' )->inContentLanguage()->text();
1704  foreach ( explode( "\n", $list ) as $option ) {
1705  $value = trim( $option );
1706  if ( $value == '' ) {
1707  continue;
1708  } elseif ( substr( $value, 0, 1 ) == '*' && substr( $value, 1, 1 ) != '*' ) {
1709  $options[] = [ 'optgroup' => trim( substr( $value, 1 ) ) ];
1710  } elseif ( substr( $value, 0, 2 ) == '**' ) {
1711  $options[] = [ 'data' => trim( substr( $value, 2 ) ) ];
1712  } else {
1713  $options[] = [ 'data' => trim( $value ) ];
1714  }
1715  }
1716 
1717  $fields[] = new OOUI\FieldLayout(
1718  new OOUI\DropdownInputWidget( [
1719  'name' => 'wpDeleteReasonList',
1720  'inputId' => 'wpDeleteReasonList',
1721  'tabIndex' => 1,
1722  'infusable' => true,
1723  'value' => '',
1724  'options' => $options
1725  ] ),
1726  [
1727  'label' => $ctx->msg( 'deletecomment' )->text(),
1728  'align' => 'top',
1729  ]
1730  );
1731 
1732  $fields[] = new OOUI\FieldLayout(
1733  new OOUI\TextInputWidget( [
1734  'name' => 'wpReason',
1735  'inputId' => 'wpReason',
1736  'tabIndex' => 2,
1737  'maxLength' => 255,
1738  'infusable' => true,
1739  'value' => $reason,
1740  'autofocus' => true,
1741  ] ),
1742  [
1743  'label' => $ctx->msg( 'deleteotherreason' )->text(),
1744  'align' => 'top',
1745  ]
1746  );
1747 
1748  if ( $user->isLoggedIn() ) {
1749  $fields[] = new OOUI\FieldLayout(
1750  new OOUI\CheckboxInputWidget( [
1751  'name' => 'wpWatch',
1752  'inputId' => 'wpWatch',
1753  'tabIndex' => 3,
1754  'selected' => $checkWatch,
1755  ] ),
1756  [
1757  'label' => $ctx->msg( 'watchthis' )->text(),
1758  'align' => 'inline',
1759  'infusable' => true,
1760  ]
1761  );
1762  }
1763 
1764  if ( $user->isAllowed( 'suppressrevision' ) ) {
1765  $fields[] = new OOUI\FieldLayout(
1766  new OOUI\CheckboxInputWidget( [
1767  'name' => 'wpSuppress',
1768  'inputId' => 'wpSuppress',
1769  'tabIndex' => 4,
1770  ] ),
1771  [
1772  'label' => $ctx->msg( 'revdelete-suppress' )->text(),
1773  'align' => 'inline',
1774  'infusable' => true,
1775  ]
1776  );
1777  }
1778 
1779  $fields[] = new OOUI\FieldLayout(
1780  new OOUI\ButtonInputWidget( [
1781  'name' => 'wpConfirmB',
1782  'inputId' => 'wpConfirmB',
1783  'tabIndex' => 5,
1784  'value' => $ctx->msg( 'deletepage' )->text(),
1785  'label' => $ctx->msg( 'deletepage' )->text(),
1786  'flags' => [ 'primary', 'destructive' ],
1787  'type' => 'submit',
1788  ] ),
1789  [
1790  'align' => 'top',
1791  ]
1792  );
1793 
1794  $fieldset = new OOUI\FieldsetLayout( [
1795  'label' => $ctx->msg( 'delete-legend' )->text(),
1796  'id' => 'mw-delete-table',
1797  'items' => $fields,
1798  ] );
1799 
1800  $form = new OOUI\FormLayout( [
1801  'method' => 'post',
1802  'action' => $title->getLocalURL( 'action=delete' ),
1803  'id' => 'deleteconfirm',
1804  ] );
1805  $form->appendContent(
1806  $fieldset,
1807  new OOUI\HtmlSnippet(
1808  Html::hidden( 'wpEditToken', $user->getEditToken( [ 'delete', $title->getPrefixedText() ] ) )
1809  )
1810  );
1811 
1812  $outputPage->addHTML(
1813  new OOUI\PanelLayout( [
1814  'classes' => [ 'deletepage-wrapper' ],
1815  'expanded' => false,
1816  'padded' => true,
1817  'framed' => true,
1818  'content' => $form,
1819  ] )
1820  );
1821 
1822  if ( $user->isAllowed( 'editinterface' ) ) {
1824  $ctx->msg( 'deletereason-dropdown' )->inContentLanguage()->getTitle(),
1825  wfMessage( 'delete-edit-reasonlist' )->escaped(),
1826  [],
1827  [ 'action' => 'edit' ]
1828  );
1829  $outputPage->addHTML( '<p class="mw-delete-editreasons">' . $link . '</p>' );
1830  }
1831 
1832  $deleteLogPage = new LogPage( 'delete' );
1833  $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
1834  LogEventsList::showLogExtract( $outputPage, 'delete', $title );
1835  }
1836 
1842  public function doDelete( $reason, $suppress = false ) {
1843  $error = '';
1844  $context = $this->getContext();
1845  $outputPage = $context->getOutput();
1846  $user = $context->getUser();
1847  $status = $this->mPage->doDeleteArticleReal( $reason, $suppress, 0, true, $error, $user );
1848 
1849  if ( $status->isGood() ) {
1850  $deleted = $this->getTitle()->getPrefixedText();
1851 
1852  $outputPage->setPageTitle( wfMessage( 'actioncomplete' ) );
1853  $outputPage->setRobotPolicy( 'noindex,nofollow' );
1854 
1855  $loglink = '[[Special:Log/delete|' . wfMessage( 'deletionlog' )->text() . ']]';
1856 
1857  $outputPage->addWikiMsg( 'deletedtext', wfEscapeWikiText( $deleted ), $loglink );
1858 
1859  Hooks::run( 'ArticleDeleteAfterSuccess', [ $this->getTitle(), $outputPage ] );
1860 
1861  $outputPage->returnToMain( false );
1862  } else {
1863  $outputPage->setPageTitle(
1864  wfMessage( 'cannotdelete-title',
1865  $this->getTitle()->getPrefixedText() )
1866  );
1867 
1868  if ( $error == '' ) {
1869  $outputPage->addWikiText(
1870  "<div class=\"error mw-error-cannotdelete\">\n" . $status->getWikiText() . "\n</div>"
1871  );
1872  $deleteLogPage = new LogPage( 'delete' );
1873  $outputPage->addHTML( Xml::element( 'h2', null, $deleteLogPage->getName()->text() ) );
1874 
1876  $outputPage,
1877  'delete',
1878  $this->getTitle()
1879  );
1880  } else {
1881  $outputPage->addHTML( $error );
1882  }
1883  }
1884  }
1885 
1886  /* Caching functions */
1887 
1895  protected function tryFileCache() {
1896  static $called = false;
1897 
1898  if ( $called ) {
1899  wfDebug( "Article::tryFileCache(): called twice!?\n" );
1900  return false;
1901  }
1902 
1903  $called = true;
1904  if ( $this->isFileCacheable() ) {
1905  $cache = new HTMLFileCache( $this->getTitle(), 'view' );
1906  if ( $cache->isCacheGood( $this->mPage->getTouched() ) ) {
1907  wfDebug( "Article::tryFileCache(): about to load file\n" );
1908  $cache->loadFromFileCache( $this->getContext() );
1909  return true;
1910  } else {
1911  wfDebug( "Article::tryFileCache(): starting buffer\n" );
1912  ob_start( [ &$cache, 'saveToFileCache' ] );
1913  }
1914  } else {
1915  wfDebug( "Article::tryFileCache(): not cacheable\n" );
1916  }
1917 
1918  return false;
1919  }
1920 
1926  public function isFileCacheable( $mode = HTMLFileCache::MODE_NORMAL ) {
1927  $cacheable = false;
1928 
1929  if ( HTMLFileCache::useFileCache( $this->getContext(), $mode ) ) {
1930  $cacheable = $this->mPage->getId()
1931  && !$this->mRedirectedFrom && !$this->getTitle()->isRedirect();
1932  // Extension may have reason to disable file caching on some pages.
1933  if ( $cacheable ) {
1934  // Avoid PHP 7.1 warning of passing $this by reference
1935  $articlePage = $this;
1936  $cacheable = Hooks::run( 'IsFileCacheable', [ &$articlePage ] );
1937  }
1938  }
1939 
1940  return $cacheable;
1941  }
1942 
1956  public function getParserOutput( $oldid = null, User $user = null ) {
1957  // XXX: bypasses mParserOptions and thus setParserOptions()
1958 
1959  if ( $user === null ) {
1960  $parserOptions = $this->getParserOptions();
1961  } else {
1962  $parserOptions = $this->mPage->makeParserOptions( $user );
1963  }
1964 
1965  return $this->mPage->getParserOutput( $parserOptions, $oldid );
1966  }
1967 
1975  if ( $this->mParserOptions ) {
1976  throw new MWException( "can't change parser options after they have already been set" );
1977  }
1978 
1979  // clone, so if $options is modified later, it doesn't confuse the parser cache.
1980  $this->mParserOptions = clone $options;
1981  }
1982 
1987  public function getParserOptions() {
1988  if ( !$this->mParserOptions ) {
1989  $this->mParserOptions = $this->mPage->makeParserOptions( $this->getContext() );
1990  }
1991  // Clone to allow modifications of the return value without affecting cache
1992  return clone $this->mParserOptions;
1993  }
1994 
2001  public function setContext( $context ) {
2002  $this->mContext = $context;
2003  }
2004 
2011  public function getContext() {
2012  if ( $this->mContext instanceof IContextSource ) {
2013  return $this->mContext;
2014  } else {
2015  wfDebug( __METHOD__ . " called and \$mContext is null. " .
2016  "Return RequestContext::getMain(); for sanity\n" );
2017  return RequestContext::getMain();
2018  }
2019  }
2020 
2028  public function __get( $fname ) {
2029  if ( property_exists( $this->mPage, $fname ) ) {
2030  # wfWarn( "Access to raw $fname field " . __CLASS__ );
2031  return $this->mPage->$fname;
2032  }
2033  trigger_error( 'Inaccessible property via __get(): ' . $fname, E_USER_NOTICE );
2034  }
2035 
2043  public function __set( $fname, $fvalue ) {
2044  if ( property_exists( $this->mPage, $fname ) ) {
2045  # wfWarn( "Access to raw $fname field of " . __CLASS__ );
2046  $this->mPage->$fname = $fvalue;
2047  // Note: extensions may want to toss on new fields
2048  } elseif ( !in_array( $fname, [ 'mContext', 'mPage' ] ) ) {
2049  $this->mPage->$fname = $fvalue;
2050  } else {
2051  trigger_error( 'Inaccessible property via __set(): ' . $fname, E_USER_NOTICE );
2052  }
2053  }
2054 
2059  public function checkFlags( $flags ) {
2060  return $this->mPage->checkFlags( $flags );
2061  }
2062 
2067  public function checkTouched() {
2068  return $this->mPage->checkTouched();
2069  }
2070 
2075  public function clearPreparedEdit() {
2076  $this->mPage->clearPreparedEdit();
2077  }
2078 
2083  public function doDeleteArticleReal(
2084  $reason, $suppress = false, $u1 = null, $u2 = null, &$error = '', User $user = null,
2085  $tags = []
2086  ) {
2087  return $this->mPage->doDeleteArticleReal(
2088  $reason, $suppress, $u1, $u2, $error, $user, $tags
2089  );
2090  }
2091 
2096  public function doDeleteUpdates( $id, Content $content = null ) {
2097  return $this->mPage->doDeleteUpdates( $id, $content );
2098  }
2099 
2105  public function doEditContent( Content $content, $summary, $flags = 0, $baseRevId = false,
2106  User $user = null, $serialFormat = null
2107  ) {
2108  wfDeprecated( __METHOD__, '1.29' );
2109  return $this->mPage->doEditContent( $content, $summary, $flags, $baseRevId,
2110  $user, $serialFormat
2111  );
2112  }
2113 
2118  public function doEditUpdates( Revision $revision, User $user, array $options = [] ) {
2119  return $this->mPage->doEditUpdates( $revision, $user, $options );
2120  }
2121 
2128  public function doPurge() {
2129  return $this->mPage->doPurge();
2130  }
2131 
2137  public function getLastPurgeTimestamp() {
2138  wfDeprecated( __METHOD__, '1.29' );
2139  return $this->mPage->getLastPurgeTimestamp();
2140  }
2141 
2146  public function doViewUpdates( User $user, $oldid = 0 ) {
2147  $this->mPage->doViewUpdates( $user, $oldid );
2148  }
2149 
2154  public function exists() {
2155  return $this->mPage->exists();
2156  }
2157 
2162  public function followRedirect() {
2163  return $this->mPage->followRedirect();
2164  }
2165 
2170  public function getActionOverrides() {
2171  return $this->mPage->getActionOverrides();
2172  }
2173 
2178  public function getAutoDeleteReason( &$hasHistory ) {
2179  return $this->mPage->getAutoDeleteReason( $hasHistory );
2180  }
2181 
2186  public function getCategories() {
2187  return $this->mPage->getCategories();
2188  }
2189 
2194  public function getComment( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2195  return $this->mPage->getComment( $audience, $user );
2196  }
2197 
2202  public function getContentHandler() {
2203  return $this->mPage->getContentHandler();
2204  }
2205 
2210  public function getContentModel() {
2211  return $this->mPage->getContentModel();
2212  }
2213 
2218  public function getContributors() {
2219  return $this->mPage->getContributors();
2220  }
2221 
2226  public function getCreator( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2227  return $this->mPage->getCreator( $audience, $user );
2228  }
2229 
2234  public function getDeletionUpdates( Content $content = null ) {
2235  return $this->mPage->getDeletionUpdates( $content );
2236  }
2237 
2242  public function getHiddenCategories() {
2243  return $this->mPage->getHiddenCategories();
2244  }
2245 
2250  public function getId() {
2251  return $this->mPage->getId();
2252  }
2253 
2258  public function getLatest() {
2259  return $this->mPage->getLatest();
2260  }
2261 
2266  public function getLinksTimestamp() {
2267  return $this->mPage->getLinksTimestamp();
2268  }
2269 
2274  public function getMinorEdit() {
2275  return $this->mPage->getMinorEdit();
2276  }
2277 
2282  public function getOldestRevision() {
2283  return $this->mPage->getOldestRevision();
2284  }
2285 
2290  public function getRedirectTarget() {
2291  return $this->mPage->getRedirectTarget();
2292  }
2293 
2298  public function getRedirectURL( $rt ) {
2299  return $this->mPage->getRedirectURL( $rt );
2300  }
2301 
2306  public function getRevision() {
2307  return $this->mPage->getRevision();
2308  }
2309 
2314  public function getTimestamp() {
2315  return $this->mPage->getTimestamp();
2316  }
2317 
2322  public function getTouched() {
2323  return $this->mPage->getTouched();
2324  }
2325 
2330  public function getUndoContent( Revision $undo, Revision $undoafter = null ) {
2331  return $this->mPage->getUndoContent( $undo, $undoafter );
2332  }
2333 
2338  public function getUser( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2339  return $this->mPage->getUser( $audience, $user );
2340  }
2341 
2346  public function getUserText( $audience = Revision::FOR_PUBLIC, User $user = null ) {
2347  return $this->mPage->getUserText( $audience, $user );
2348  }
2349 
2354  public function hasViewableContent() {
2355  return $this->mPage->hasViewableContent();
2356  }
2357 
2362  public function insertOn( $dbw, $pageId = null ) {
2363  return $this->mPage->insertOn( $dbw, $pageId );
2364  }
2365 
2370  public function insertProtectNullRevision( $revCommentMsg, array $limit,
2371  array $expiry, $cascade, $reason, $user = null
2372  ) {
2373  return $this->mPage->insertProtectNullRevision( $revCommentMsg, $limit,
2374  $expiry, $cascade, $reason, $user
2375  );
2376  }
2377 
2382  public function insertRedirect() {
2383  return $this->mPage->insertRedirect();
2384  }
2385 
2390  public function insertRedirectEntry( Title $rt, $oldLatest = null ) {
2391  return $this->mPage->insertRedirectEntry( $rt, $oldLatest );
2392  }
2393 
2398  public function isCountable( $editInfo = false ) {
2399  return $this->mPage->isCountable( $editInfo );
2400  }
2401 
2406  public function isRedirect() {
2407  return $this->mPage->isRedirect();
2408  }
2409 
2414  public function loadFromRow( $data, $from ) {
2415  return $this->mPage->loadFromRow( $data, $from );
2416  }
2417 
2422  public function loadPageData( $from = 'fromdb' ) {
2423  $this->mPage->loadPageData( $from );
2424  }
2425 
2430  public function lockAndGetLatest() {
2431  return $this->mPage->lockAndGetLatest();
2432  }
2433 
2438  public function makeParserOptions( $context ) {
2439  return $this->mPage->makeParserOptions( $context );
2440  }
2441 
2446  public function pageDataFromId( $dbr, $id, $options = [] ) {
2447  return $this->mPage->pageDataFromId( $dbr, $id, $options );
2448  }
2449 
2454  public function pageDataFromTitle( $dbr, $title, $options = [] ) {
2455  return $this->mPage->pageDataFromTitle( $dbr, $title, $options );
2456  }
2457 
2462  public function prepareContentForEdit(
2463  Content $content, $revision = null, User $user = null,
2464  $serialFormat = null, $useCache = true
2465  ) {
2466  return $this->mPage->prepareContentForEdit(
2467  $content, $revision, $user,
2468  $serialFormat, $useCache
2469  );
2470  }
2471 
2476  public function protectDescription( array $limit, array $expiry ) {
2477  return $this->mPage->protectDescription( $limit, $expiry );
2478  }
2479 
2484  public function protectDescriptionLog( array $limit, array $expiry ) {
2485  return $this->mPage->protectDescriptionLog( $limit, $expiry );
2486  }
2487 
2492  public function replaceSectionAtRev( $sectionId, Content $sectionContent,
2493  $sectionTitle = '', $baseRevId = null
2494  ) {
2495  return $this->mPage->replaceSectionAtRev( $sectionId, $sectionContent,
2496  $sectionTitle, $baseRevId
2497  );
2498  }
2499 
2504  public function replaceSectionContent(
2505  $sectionId, Content $sectionContent, $sectionTitle = '', $edittime = null
2506  ) {
2507  return $this->mPage->replaceSectionContent(
2508  $sectionId, $sectionContent, $sectionTitle, $edittime
2509  );
2510  }
2511 
2516  public function setTimestamp( $ts ) {
2517  return $this->mPage->setTimestamp( $ts );
2518  }
2519 
2524  public function shouldCheckParserCache( ParserOptions $parserOptions, $oldId ) {
2525  return $this->mPage->shouldCheckParserCache( $parserOptions, $oldId );
2526  }
2527 
2532  public function supportsSections() {
2533  return $this->mPage->supportsSections();
2534  }
2535 
2541  return $this->mPage->triggerOpportunisticLinksUpdate( $parserOutput );
2542  }
2543 
2548  public function updateCategoryCounts( array $added, array $deleted, $id = 0 ) {
2549  return $this->mPage->updateCategoryCounts( $added, $deleted, $id );
2550  }
2551 
2556  public function updateIfNewerOn( $dbw, $revision ) {
2557  return $this->mPage->updateIfNewerOn( $dbw, $revision );
2558  }
2559 
2564  public function updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect = null ) {
2565  return $this->mPage->updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect = null );
2566  }
2567 
2572  public function updateRevisionOn( $dbw, $revision, $lastRevision = null,
2573  $lastRevIsRedirect = null
2574  ) {
2575  return $this->mPage->updateRevisionOn( $dbw, $revision, $lastRevision,
2576  $lastRevIsRedirect
2577  );
2578  }
2579 
2588  public function doUpdateRestrictions( array $limit, array $expiry, &$cascade,
2589  $reason, User $user
2590  ) {
2591  return $this->mPage->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $user );
2592  }
2593 
2601  public function updateRestrictions( $limit = [], $reason = '',
2602  &$cascade = 0, $expiry = []
2603  ) {
2604  return $this->mPage->doUpdateRestrictions(
2605  $limit,
2606  $expiry,
2607  $cascade,
2608  $reason,
2609  $this->getContext()->getUser()
2610  );
2611  }
2612 
2621  public function doDeleteArticle(
2622  $reason, $suppress = false, $u1 = null, $u2 = null, &$error = ''
2623  ) {
2624  return $this->mPage->doDeleteArticle( $reason, $suppress, $u1, $u2, $error );
2625  }
2626 
2636  public function doRollback( $fromP, $summary, $token, $bot, &$resultDetails, User $user = null ) {
2637  $user = is_null( $user ) ? $this->getContext()->getUser() : $user;
2638  return $this->mPage->doRollback( $fromP, $summary, $token, $bot, $resultDetails, $user );
2639  }
2640 
2649  public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser = null ) {
2650  $guser = is_null( $guser ) ? $this->getContext()->getUser() : $guser;
2651  return $this->mPage->commitRollback( $fromP, $summary, $bot, $resultDetails, $guser );
2652  }
2653 
2658  public function generateReason( &$hasHistory ) {
2659  $title = $this->mPage->getTitle();
2661  return $handler->getAutoDeleteReason( $title, $hasHistory );
2662  }
2663 
2669  public static function selectFields() {
2670  wfDeprecated( __METHOD__, '1.24' );
2671  return WikiPage::selectFields();
2672  }
2673 
2679  public static function onArticleCreate( $title ) {
2680  wfDeprecated( __METHOD__, '1.24' );
2682  }
2683 
2689  public static function onArticleDelete( $title ) {
2690  wfDeprecated( __METHOD__, '1.24' );
2692  }
2693 
2699  public static function onArticleEdit( $title ) {
2700  wfDeprecated( __METHOD__, '1.24' );
2702  }
2703 
2704  // ******
2705 }
pageDataFromId($dbr, $id, $options=[])
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2446
__set($fname, $fvalue)
Use PHP's magic __set handler to handle setting of raw WikiPage fields for backwards compatibility...
Definition: Article.php:2043
static newFromName($name, $validate= 'valid')
Static factory method for creation from username.
Definition: User.php:550
getRedirectTarget()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2290
Using a hook running we can avoid having all this option specific stuff in our mainline code Using the function array $article
Definition: hooks.txt:77
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:1122
const FOR_THIS_USER
Definition: Revision.php:99
viewRedirect($target, $appendSubtitle=true, $forceKnown=false)
Return the HTML for the top of a redirect page.
Definition: Article.php:1439
static newFromID($id, $flags=0)
Create a new Title from an article ID.
Definition: Title.php:405
lockAndGetLatest()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2430
static onArticleCreate(Title $title)
The onArticle*() functions are supposed to be a kind of hooks which should be called whenever any of ...
Definition: WikiPage.php:3251
static onArticleCreate($title)
Definition: Article.php:2679
getUndoContent(Revision $undo, Revision $undoafter=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2330
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:1964
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
Interface for objects which can provide a MediaWiki context on request.
static isInRCLifespan($timestamp, $tolerance=0)
Check whether the given timestamp is new enough to have a RC row with a given tolerance as the recent...
static revComment(Revision $rev, $local=false, $isPublic=false)
Wrap and format the given revision's comment block, if the current user is allowed to view it...
Definition: Linker.php:1472
WikiPage $mPage
The WikiPage object of this instance.
Definition: Article.php:40
wfGetDB($db, $groups=[], $wiki=false)
Get a Database object.
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub 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:782
getRobotPolicy($action, $pOutput=null)
Get the robot policy to be used for the current view.
Definition: Article.php:734
the array() calling protocol came about after MediaWiki 1.4rc1.
getRedirectedFrom()
Get the page this view was redirected from.
Definition: Article.php:156
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:1580
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content $content
Definition: hooks.txt:1048
updateRestrictions($limit=[], $reason= '', &$cascade=0, $expiry=[])
Definition: Article.php:2601
doEditContent(Content $content, $summary, $flags=0, $baseRevId=false, User $user=null, $serialFormat=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2105
getLatest()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2258
setParserOptions(ParserOptions $options)
Override the ParserOptions used to render the primary article wikitext.
Definition: Article.php:1974
magic word the default is to use $key to get the and $key value or $key value text $key value html to format the value $key
Definition: hooks.txt:2554
doEditUpdates(Revision $revision, User $user, array $options=[])
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2118
static getRevDeleteLink(User $user, Revision $rev, Title $title)
Get a revision-deletion link, or disabled link, or nothing, depending on user permissions & the setti...
Definition: Linker.php:2024
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set $status
Definition: hooks.txt:1048
static element($element, $attribs=null, $contents= '', $allowShortTag=true)
Format an XML element with given attributes and, optionally, text content.
Definition: Xml.php:39
exists()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2154
supportsSections()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2532
static makeUrl($name, $urlaction= '')
Definition: Skin.php:1148
doRollback($fromP, $summary, $token, $bot, &$resultDetails, User $user=null)
Definition: Article.php:2636
Show an error when the wiki is locked/read-only and the user tries to do something that requires writ...
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
doDeleteArticle($reason, $suppress=false, $u1=null, $u2=null, &$error= '')
Definition: Article.php:2621
protect()
action=protect handler
Definition: Article.php:1523
div flags Integer display flags(NO_ACTION_LINK, NO_EXTRA_USER_LINKS) 'LogException'returning false will NOT prevent logging $e
Definition: hooks.txt:2140
IContextSource $mContext
The context this Article is executed in.
Definition: Article.php:37
isCountable($editInfo=false)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2398
Wrapper allowing us to handle a system message as a Content object.
getPage()
Get the WikiPage object of this instance.
Definition: Article.php:184
getParserOutput($oldid=null, User $user=null)
#@-
Definition: Article.php:1956
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:2548
string bool $mRedirectUrl
URL to redirect to or false if none.
Definition: Article.php:67
loadPageData($from= 'fromdb')
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2422
if(!isset($args[0])) $lang
ParserOptions $mParserOptions
ParserOptions object for $wgUser articles.
Definition: Article.php:43
Content $mContentObject
Content of the revision we are working on.
Definition: Article.php:55
Special handling for category description pages, showing pages, subcategories and file that belong to...
isFileCacheable($mode=HTMLFileCache::MODE_NORMAL)
Check if the page can be cached.
Definition: Article.php:1926
doDeleteUpdates($id, Content $content=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2096
static newFromConds($conds, $fname=__METHOD__, $dbType=DB_REPLICA)
Find the first recent change matching some specific conditions.
adjustDisplayTitle(ParserOutput $pOutput)
Adjust title for pages with displaytitle, -{T|}- or language conversion.
Definition: Article.php:672
static hidden($name, $value, array $attribs=[])
Convenience function to produce an input element with type=hidden.
Definition: Html.php:746
shouldCheckParserCache(ParserOptions $parserOptions, $oldId)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2524
doUpdateRestrictions(array $limit, array $expiry, &$cascade, $reason, User $user)
Definition: Article.php:2588
Class for viewing MediaWiki article and history.
Definition: Article.php:35
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context the output can only depend on parameters provided to this hook not on global state indicating whether full HTML should be generated If generation of HTML may be but other information should still be present in the ParserOutput object to manipulate or replace but no entry for that model exists in $wgContentHandlers please use GetContentModels hook to make them known to core if desired 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 inclusive $limit
Definition: hooks.txt:1048
null for the local wiki Added in
Definition: hooks.txt:1580
Page view caching in the file system.
$value
followRedirect()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2162
confirmDelete($reason)
Output deletion confirmation dialog.
Definition: Article.php:1666
Class for viewing MediaWiki file description pages.
Definition: ImagePage.php:30
triggerOpportunisticLinksUpdate(ParserOutput $parserOutput)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2540
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:255
__get($fname)
Use PHP's magic __get handler to handle accessing of raw WikiPage fields for backwards compatibility...
Definition: Article.php:2028
it s the revision text itself In either if gzip is the revision text is gzipped $flags
Definition: hooks.txt:2801
getDeletionUpdates(Content $content=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2234
updateIfNewerOn($dbw, $revision)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2556
clearPreparedEdit()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2075
when a variable name is used in a it is silently declared as a new local masking the global
Definition: design.txt:93
getContributors()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2218
commitRollback($fromP, $summary, $bot, &$resultDetails, User $guser=null)
Definition: Article.php:2649
showMissingArticle()
Show the error text for a missing article.
Definition: Article.php:1131
wfDebug($text, $dest= 'all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
static showLogExtract(&$out, $types=[], $page= '', $user= '', $param=[])
Show log extract.
protectDescription(array $limit, array $expiry)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2476
Class to simplify the use of log pages.
Definition: LogPage.php:31
usually copyright or history_copyright This message must be in HTML not wikitext & $link
Definition: hooks.txt:2981
getContext()
Gets the context this Article is executed in.
Definition: Article.php:2011
isRedirect()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2406
__construct(Title $title, $oldId=null)
Constructor and clear the article.
Definition: Article.php:83
setTimestamp($ts)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2516
protectDescriptionLog(array $limit, array $expiry)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2484
static onArticleDelete($title)
Definition: Article.php:2689
Interface for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
Definition: Page.php:24
makeParserOptions($context)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2438
wfEscapeWikiText($text)
Escapes the given text so that it may be output using addWikiText() without any linking, formatting, etc.
wfReadOnly()
Check whether the wiki is in read-only mode.
static getMain()
Static methods.
prepareContentForEdit(Content $content, $revision=null, User $user=null, $serialFormat=null, $useCache=true)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2462
const FOR_PUBLIC
Definition: Revision.php:98
static getCanonicalName($index)
Returns the canonical (English) name for a given index.
$wgUseFileCache
This will cache static pages for non-logged-in users to reduce database traffic on public sites...
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 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 unsetoffset-wrap String Wrap the message in html(usually something like"&lt
showViewFooter()
Show the footer section of an ordinary page view.
Definition: Article.php:934
static newFromTarget($specificTarget, $vagueTarget=null, $fromMaster=false)
Given a target and the target's type, get an existing Block object if possible.
Definition: Block.php:1113
getRevisionFetched()
Get the fetched Revision object depending on request parameters or null on failure.
Definition: Article.php:405
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context called by AbstractContent::getParserOutput May be used to override the normal model specific rendering of page content as context as context $options
Definition: hooks.txt:1048
insertProtectNullRevision($revCommentMsg, array $limit, array $expiry, $cascade, $reason, $user=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2370
const NS_MEDIA
Definition: Defines.php:50
static isValid($ip)
Validate an IP address.
Definition: IP.php:113
showRedirectedFromHeader()
If this request is a redirect view, send "redirected from" subtitle to the output.
Definition: Article.php:843
ParserOutput $mParserOutput
Definition: Article.php:76
getRevision()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2306
it s the revision text itself In either if gzip is set
Definition: hooks.txt:2801
generateReason(&$hasHistory)
Definition: Article.php:2658
static getRedirectHeaderHtml(Language $lang, $target, $forceKnown=false)
Return the HTML for the top of a redirect page.
Definition: Article.php:1461
insertRedirect()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2382
int null $mOldId
The oldid of the article that is to be shown, 0 for the current revision.
Definition: Article.php:61
static openElement($element, $attribs=null)
This opens an XML element.
Definition: Xml.php:109
hasViewableContent()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2354
getComment($audience=Revision::FOR_PUBLIC, User $user=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2194
getTitle()
Get the title object of the article.
Definition: Article.php:174
loadFromRow($data, $from)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2414
$cache
Definition: mcc.php:33
getActionOverrides()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2170
doViewUpdates(User $user, $oldid=0)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2146
getTitle()
Get the title object of the article.
Definition: WikiPage.php:238
const NS_CATEGORY
Definition: Defines.php:76
doPurge()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2128
doDeleteArticleReal($reason, $suppress=false, $u1=null, $u2=null, &$error= '', User $user=null, $tags=[])
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2083
static newFromTitle($title, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition: Article.php:113
wfDeprecated($function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
render()
Handle action=render.
Definition: Article.php:1513
static isIP($name)
Does the string match an anonymous IP address?
Definition: User.php:825
const DELETED_RESTRICTED
Definition: Revision.php:93
getCategories()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2186
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:933
static linkKnown($target, $html=null, $customAttribs=[], $query=[], $options=[ 'known'])
Identical to link(), except $options defaults to 'known'.
Definition: Linker.php:164
replaceSectionAtRev($sectionId, Content $sectionContent, $sectionTitle= '', $baseRevId=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2492
static run($event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:131
design txt This is a brief overview of the new design More thorough and up to date information is available on the documentation wiki at etc Handles the details of getting and saving to the user table of the and dealing with sessions and cookies OutputPage Encapsulates the entire HTML page that will be sent in response to any server request It is used by calling its functions to add text
Definition: design.txt:12
checkFlags($flags)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2059
const NS_FILE
Definition: Defines.php:68
static makeContent($text, Title $title=null, $modelId=null, $format=null)
Convenience function for creating a Content object from a given textual representation.
$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:86
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:1749
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
getContentHandler()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2202
const NS_MEDIAWIKI
Definition: Defines.php:70
static link($target, $html=null, $customAttribs=[], $query=[], $options=[])
This function returns an HTML link to the given target.
Definition: Linker.php:107
static onArticleEdit(Title $title, Revision $revision=null)
Purge caches on page update etc.
Definition: WikiPage.php:3323
Title $mRedirectedFrom
Title from which we were redirected here.
Definition: Article.php:64
const DELETED_TEXT
Definition: Revision.php:90
getCreator($audience=Revision::FOR_PUBLIC, User $user=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2226
static singleton()
Get an instance of this object.
Definition: ParserCache.php:66
addHelpLink($to, $overrideBaseUrl=false)
Adds help link with an icon via page indicators.
Definition: Article.php:1496
newPage(Title $title)
Definition: Article.php:92
Class representing a MediaWiki article and history.
Definition: WikiPage.php:37
static newFromId($id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:116
getOldestRevision()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2282
string $mContent
Text of the revision we are working on.
Definition: Article.php:49
static makeExternalLink($url, $text, $escape=true, $linktype= '', $attribs=[], $title=null)
Make an external link.
Definition: Linker.php:843
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:1580
clear()
Clear the object.
Definition: Article.php:191
bool $mContentLoaded
Is the content ($mContent) already loaded?
Definition: Article.php:58
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
getLastPurgeTimestamp()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2137
getAutoDeleteReason(&$hasHistory)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2178
checkTouched()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2067
updateRevisionOn($dbw, $revision, $lastRevision=null, $lastRevIsRedirect=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2572
static selectFields()
Definition: Article.php:2669
static newFromID($id)
Constructor from a page id.
Definition: Article.php:101
if(!defined( 'MEDIAWIKI')) $fname
This file is not a valid entry point, perform no further processing unless MEDIAWIKI is defined...
Definition: Setup.php:36
getId()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2250
getTimestamp()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2314
pageDataFromTitle($dbr, $title, $options=[])
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2454
getMinorEdit()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2274
view()
This is the default action of the index.php entry point: just view the page of the given title...
Definition: Article.php:428
static onArticleDelete(Title $title)
Clears caches when article is deleted.
Definition: WikiPage.php:3280
static numParam($num)
Definition: Message.php:1028
getContentObject()
Returns a Content object representing the pages effective display content, not necessarily the revisi...
Definition: Article.php:216
this hook is for auditing only RecentChangesLinked and Watchlist RecentChangesLinked and Watchlist Do not use this to implement individual filters if they are compatible with the ChangesListFilter and ChangesListFilterGroup structure use sub classes of those in conjunction with the ChangesListSpecialPageStructuredFilters hook This hook can be used to implement filters that do not implement that or custom behavior that is not an individual filter e g Watchlist and Watchlist you will want to construct new ChangesListBooleanFilter or ChangesListStringOptionsFilter objects When constructing you specify which group they belong to You can reuse existing or create your you must register them with $special registerFilterGroup removed from all revisions and log entries to which it was applied This gives extensions a chance to take it off their books as the deletion has already been partly carried out by this point or something similar the user will be unable to create the tag set and then return false from the hook function Ensure you consume the ChangeTagAfterDelete hook to carry out custom deletion actions as context $parserOutput
Definition: hooks.txt:1048
getOldID()
Definition: Article.php:242
doDelete($reason, $suppress=false)
Perform a deletion and output success or failure messages.
Definition: Article.php:1842
showNamespaceHeader()
Show a header specific to the namespace currently being viewed, like [[MediaWiki:Talkpagetext]].
Definition: Article.php:920
Show an error when a user tries to do something they do not have the necessary permissions for...
updateRedirectOn($dbw, $redirectTitle, $lastRevIsRedirect=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2564
tryFileCache()
checkLastModified returns true if it has taken care of all output to the client that is necessary for...
Definition: Article.php:1895
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:2580
insertRedirectEntry(Title $rt, $oldLatest=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2390
const POST_EDIT_COOKIE_KEY_PREFIX
Prefix of key for cookie used to pass post-edit state.
Definition: EditPage.php:189
int $mRevIdFetched
Revision ID of revision we are working on.
Definition: Article.php:70
fetchContentObject()
Get text content object Does NOT follow redirects.
Definition: Article.php:313
const DB_REPLICA
Definition: defines.php:25
setOldSubtitle($oldid=0)
Generate the navigation links when browsing through an article revisions It shows the information as:...
Definition: Article.php:1299
isCurrent()
Returns true if the currently-referenced revision is the current edit to this page (and it exists)...
Definition: Article.php:389
unprotect()
action=unprotect handler (alias)
Definition: Article.php:1531
Handles the page protection UI and backend.
getTouched()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2322
static onArticleEdit($title)
Definition: Article.php:2699
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that probably a stub 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:782
getUser($audience=Revision::FOR_PUBLIC, User $user=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2338
static newFromWikiPage(WikiPage $page, IContextSource $context)
Create an Article object of the appropriate class for the given page.
Definition: Article.php:145
static selectFields()
Return the list of revision fields that should be selected to create a new page.
Definition: WikiPage.php:287
static doWatchOrUnwatch($watch, Title $title, User $user)
Watch or unwatch a page.
Definition: WatchAction.php:93
$wgSend404Code
Some web hosts attempt to rewrite all responses with a 404 (not found) status code, mangling or hiding MediaWiki's output.
replaceSectionContent($sectionId, Content $sectionContent, $sectionTitle= '', $edittime=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2504
setContext($context)
Sets the context this Article is executed in.
Definition: Article.php:2001
getRevIdFetched()
Use this to fetch the rev ID used on page views.
Definition: Article.php:416
const NS_USER_TALK
Definition: Defines.php:65
static revUserTools($rev, $isPublic=false)
Generate a user tool link cluster if the current user is allowed to view it.
Definition: Linker.php:1060
showDiffPage()
Show a diff page according to current request variables.
Definition: Article.php:684
getRedirectURL($rt)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2298
Definition: Block.php:27
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:2580
showDeletedRevisionHeader()
If the revision requested for view is deleted, check permissions.
Definition: Article.php:1256
static makeTitle($ns, $title, $fragment= '', $interwiki= '')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:514
setRedirectedFrom(Title $from)
Tell the page view functions that this view was redirected from another page on the wiki...
Definition: Article.php:165
please add to it if you re going to add events to the MediaWiki code where normally authentication against an external auth plugin would be creating a local account $user
Definition: hooks.txt:244
getContentModel()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2210
getLinksTimestamp()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2266
insertOn($dbw, $pageId=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2362
getParserOptions()
Get parser options suitable for rendering the primary article wikitext.
Definition: Article.php:1987
const RC_LOG
Definition: Defines.php:142
Revision $mRevision
Revision we are working on.
Definition: Article.php:73
getUserText($audience=Revision::FOR_PUBLIC, User $user=null)
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2346
showPatrolFooter()
If patrol is possible, output a patrol UI box.
Definition: Article.php:957
static formatRobotPolicy($policy)
Converts a String robot policy into an associative array, to allow merging of several policies using ...
Definition: Article.php:814
getHiddenCategories()
Call to WikiPage function for backwards compatibility.
Definition: Article.php:2242