MediaWiki  1.32.0
WikiPage.php
Go to the documentation of this file.
1 <?php
33 use Wikimedia\Assert\Assert;
37 
44 class WikiPage implements Page, IDBAccessObject {
45  // Constants for $mDataLoadedFrom and related
46 
50  public $mTitle = null;
51 
55  public $mDataLoaded = false; // !< Boolean
56  public $mIsRedirect = false; // !< Boolean
57  public $mLatest = false; // !< Integer (false means "not loaded")
61  public $mPreparedEdit = false;
62 
66  protected $mId = null;
67 
72 
76  protected $mRedirectTarget = null;
77 
81  protected $mLastRevision = null;
82 
86  protected $mTimestamp = '';
87 
91  protected $mTouched = '19700101000000';
92 
96  protected $mLinksUpdated = '19700101000000';
97 
101  private $derivedDataUpdater = null;
102 
107  public function __construct( Title $title ) {
108  $this->mTitle = $title;
109  }
110 
115  public function __clone() {
116  $this->mTitle = clone $this->mTitle;
117  }
118 
127  public static function factory( Title $title ) {
128  $ns = $title->getNamespace();
129 
130  if ( $ns == NS_MEDIA ) {
131  throw new MWException( "NS_MEDIA is a virtual namespace; use NS_FILE." );
132  } elseif ( $ns < 0 ) {
133  throw new MWException( "Invalid or virtual namespace $ns given." );
134  }
135 
136  $page = null;
137  if ( !Hooks::run( 'WikiPageFactory', [ $title, &$page ] ) ) {
138  return $page;
139  }
140 
141  switch ( $ns ) {
142  case NS_FILE:
143  $page = new WikiFilePage( $title );
144  break;
145  case NS_CATEGORY:
146  $page = new WikiCategoryPage( $title );
147  break;
148  default:
149  $page = new WikiPage( $title );
150  }
151 
152  return $page;
153  }
154 
165  public static function newFromID( $id, $from = 'fromdb' ) {
166  // page ids are never 0 or negative, see T63166
167  if ( $id < 1 ) {
168  return null;
169  }
170 
171  $from = self::convertSelectType( $from );
172  $db = wfGetDB( $from === self::READ_LATEST ? DB_MASTER : DB_REPLICA );
173  $pageQuery = self::getQueryInfo();
174  $row = $db->selectRow(
175  $pageQuery['tables'], $pageQuery['fields'], [ 'page_id' => $id ], __METHOD__,
176  [], $pageQuery['joins']
177  );
178  if ( !$row ) {
179  return null;
180  }
181  return self::newFromRow( $row, $from );
182  }
183 
195  public static function newFromRow( $row, $from = 'fromdb' ) {
196  $page = self::factory( Title::newFromRow( $row ) );
197  $page->loadFromRow( $row, $from );
198  return $page;
199  }
200 
207  protected static function convertSelectType( $type ) {
208  switch ( $type ) {
209  case 'fromdb':
210  return self::READ_NORMAL;
211  case 'fromdbmaster':
212  return self::READ_LATEST;
213  case 'forupdate':
214  return self::READ_LOCKING;
215  default:
216  // It may already be an integer or whatever else
217  return $type;
218  }
219  }
220 
224  private function getRevisionStore() {
225  return MediaWikiServices::getInstance()->getRevisionStore();
226  }
227 
231  private function getRevisionRenderer() {
232  return MediaWikiServices::getInstance()->getRevisionRenderer();
233  }
234 
238  private function getParserCache() {
239  return MediaWikiServices::getInstance()->getParserCache();
240  }
241 
245  private function getDBLoadBalancer() {
246  return MediaWikiServices::getInstance()->getDBLoadBalancer();
247  }
248 
255  public function getActionOverrides() {
256  return $this->getContentHandler()->getActionOverrides();
257  }
258 
268  public function getContentHandler() {
270  }
271 
276  public function getTitle() {
277  return $this->mTitle;
278  }
279 
284  public function clear() {
285  $this->mDataLoaded = false;
286  $this->mDataLoadedFrom = self::READ_NONE;
287 
288  $this->clearCacheFields();
289  }
290 
295  protected function clearCacheFields() {
296  $this->mId = null;
297  $this->mRedirectTarget = null; // Title object if set
298  $this->mLastRevision = null; // Latest revision
299  $this->mTouched = '19700101000000';
300  $this->mLinksUpdated = '19700101000000';
301  $this->mTimestamp = '';
302  $this->mIsRedirect = false;
303  $this->mLatest = false;
304  // T59026: do not clear $this->derivedDataUpdater since getDerivedDataUpdater() already
305  // checks the requested rev ID and content against the cached one. For most
306  // content types, the output should not change during the lifetime of this cache.
307  // Clearing it can cause extra parses on edit for no reason.
308  }
309 
315  public function clearPreparedEdit() {
316  $this->mPreparedEdit = false;
317  }
318 
326  public static function selectFields() {
328 
329  wfDeprecated( __METHOD__, '1.31' );
330 
331  $fields = [
332  'page_id',
333  'page_namespace',
334  'page_title',
335  'page_restrictions',
336  'page_is_redirect',
337  'page_is_new',
338  'page_random',
339  'page_touched',
340  'page_links_updated',
341  'page_latest',
342  'page_len',
343  ];
344 
345  if ( $wgContentHandlerUseDB ) {
346  $fields[] = 'page_content_model';
347  }
348 
349  if ( $wgPageLanguageUseDB ) {
350  $fields[] = 'page_lang';
351  }
352 
353  return $fields;
354  }
355 
365  public static function getQueryInfo() {
367 
368  $ret = [
369  'tables' => [ 'page' ],
370  'fields' => [
371  'page_id',
372  'page_namespace',
373  'page_title',
374  'page_restrictions',
375  'page_is_redirect',
376  'page_is_new',
377  'page_random',
378  'page_touched',
379  'page_links_updated',
380  'page_latest',
381  'page_len',
382  ],
383  'joins' => [],
384  ];
385 
386  if ( $wgContentHandlerUseDB ) {
387  $ret['fields'][] = 'page_content_model';
388  }
389 
390  if ( $wgPageLanguageUseDB ) {
391  $ret['fields'][] = 'page_lang';
392  }
393 
394  return $ret;
395  }
396 
404  protected function pageData( $dbr, $conditions, $options = [] ) {
405  $pageQuery = self::getQueryInfo();
406 
407  // Avoid PHP 7.1 warning of passing $this by reference
408  $wikiPage = $this;
409 
410  Hooks::run( 'ArticlePageDataBefore', [
411  &$wikiPage, &$pageQuery['fields'], &$pageQuery['tables'], &$pageQuery['joins']
412  ] );
413 
414  $row = $dbr->selectRow(
415  $pageQuery['tables'],
416  $pageQuery['fields'],
417  $conditions,
418  __METHOD__,
419  $options,
420  $pageQuery['joins']
421  );
422 
423  Hooks::run( 'ArticlePageDataAfter', [ &$wikiPage, &$row ] );
424 
425  return $row;
426  }
427 
437  public function pageDataFromTitle( $dbr, $title, $options = [] ) {
438  return $this->pageData( $dbr, [
439  'page_namespace' => $title->getNamespace(),
440  'page_title' => $title->getDBkey() ], $options );
441  }
442 
451  public function pageDataFromId( $dbr, $id, $options = [] ) {
452  return $this->pageData( $dbr, [ 'page_id' => $id ], $options );
453  }
454 
467  public function loadPageData( $from = 'fromdb' ) {
468  $from = self::convertSelectType( $from );
469  if ( is_int( $from ) && $from <= $this->mDataLoadedFrom ) {
470  // We already have the data from the correct location, no need to load it twice.
471  return;
472  }
473 
474  if ( is_int( $from ) ) {
475  list( $index, $opts ) = DBAccessObjectUtils::getDBOptions( $from );
476  $loadBalancer = $this->getDBLoadBalancer();
477  $db = $loadBalancer->getConnection( $index );
478  $data = $this->pageDataFromTitle( $db, $this->mTitle, $opts );
479 
480  if ( !$data
481  && $index == DB_REPLICA
482  && $loadBalancer->getServerCount() > 1
483  && $loadBalancer->hasOrMadeRecentMasterChanges()
484  ) {
485  $from = self::READ_LATEST;
486  list( $index, $opts ) = DBAccessObjectUtils::getDBOptions( $from );
487  $db = $loadBalancer->getConnection( $index );
488  $data = $this->pageDataFromTitle( $db, $this->mTitle, $opts );
489  }
490  } else {
491  // No idea from where the caller got this data, assume replica DB.
492  $data = $from;
493  $from = self::READ_NORMAL;
494  }
495 
496  $this->loadFromRow( $data, $from );
497  }
498 
512  public function wasLoadedFrom( $from ) {
513  $from = self::convertSelectType( $from );
514 
515  if ( !is_int( $from ) ) {
516  // No idea from where the caller got this data, assume replica DB.
517  $from = self::READ_NORMAL;
518  }
519 
520  if ( is_int( $from ) && $from <= $this->mDataLoadedFrom ) {
521  return true;
522  }
523 
524  return false;
525  }
526 
538  public function loadFromRow( $data, $from ) {
539  $lc = MediaWikiServices::getInstance()->getLinkCache();
540  $lc->clearLink( $this->mTitle );
541 
542  if ( $data ) {
543  $lc->addGoodLinkObjFromRow( $this->mTitle, $data );
544 
545  $this->mTitle->loadFromRow( $data );
546 
547  // Old-fashioned restrictions
548  $this->mTitle->loadRestrictions( $data->page_restrictions );
549 
550  $this->mId = intval( $data->page_id );
551  $this->mTouched = wfTimestamp( TS_MW, $data->page_touched );
552  $this->mLinksUpdated = wfTimestampOrNull( TS_MW, $data->page_links_updated );
553  $this->mIsRedirect = intval( $data->page_is_redirect );
554  $this->mLatest = intval( $data->page_latest );
555  // T39225: $latest may no longer match the cached latest Revision object.
556  // Double-check the ID of any cached latest Revision object for consistency.
557  if ( $this->mLastRevision && $this->mLastRevision->getId() != $this->mLatest ) {
558  $this->mLastRevision = null;
559  $this->mTimestamp = '';
560  }
561  } else {
562  $lc->addBadLinkObj( $this->mTitle );
563 
564  $this->mTitle->loadFromRow( false );
565 
566  $this->clearCacheFields();
567 
568  $this->mId = 0;
569  }
570 
571  $this->mDataLoaded = true;
572  $this->mDataLoadedFrom = self::convertSelectType( $from );
573  }
574 
578  public function getId() {
579  if ( !$this->mDataLoaded ) {
580  $this->loadPageData();
581  }
582  return $this->mId;
583  }
584 
588  public function exists() {
589  if ( !$this->mDataLoaded ) {
590  $this->loadPageData();
591  }
592  return $this->mId > 0;
593  }
594 
603  public function hasViewableContent() {
604  return $this->mTitle->isKnown();
605  }
606 
612  public function isRedirect() {
613  if ( !$this->mDataLoaded ) {
614  $this->loadPageData();
615  }
616 
617  return (bool)$this->mIsRedirect;
618  }
619 
630  public function getContentModel() {
631  if ( $this->exists() ) {
633 
634  return $cache->getWithSetCallback(
635  $cache->makeKey( 'page-content-model', $this->getLatest() ),
636  $cache::TTL_MONTH,
637  function () {
638  $rev = $this->getRevision();
639  if ( $rev ) {
640  // Look at the revision's actual content model
641  return $rev->getContentModel();
642  } else {
643  $title = $this->mTitle->getPrefixedDBkey();
644  wfWarn( "Page $title exists but has no (visible) revisions!" );
645  return $this->mTitle->getContentModel();
646  }
647  }
648  );
649  }
650 
651  // use the default model for this page
652  return $this->mTitle->getContentModel();
653  }
654 
659  public function checkTouched() {
660  if ( !$this->mDataLoaded ) {
661  $this->loadPageData();
662  }
663  return ( $this->mId && !$this->mIsRedirect );
664  }
665 
670  public function getTouched() {
671  if ( !$this->mDataLoaded ) {
672  $this->loadPageData();
673  }
674  return $this->mTouched;
675  }
676 
681  public function getLinksTimestamp() {
682  if ( !$this->mDataLoaded ) {
683  $this->loadPageData();
684  }
685  return $this->mLinksUpdated;
686  }
687 
692  public function getLatest() {
693  if ( !$this->mDataLoaded ) {
694  $this->loadPageData();
695  }
696  return (int)$this->mLatest;
697  }
698 
703  public function getOldestRevision() {
704  // Try using the replica DB first, then try the master
705  $rev = $this->mTitle->getFirstRevision();
706  if ( !$rev ) {
707  $rev = $this->mTitle->getFirstRevision( Title::GAID_FOR_UPDATE );
708  }
709  return $rev;
710  }
711 
716  protected function loadLastEdit() {
717  if ( $this->mLastRevision !== null ) {
718  return; // already loaded
719  }
720 
721  $latest = $this->getLatest();
722  if ( !$latest ) {
723  return; // page doesn't exist or is missing page_latest info
724  }
725 
726  if ( $this->mDataLoadedFrom == self::READ_LOCKING ) {
727  // T39225: if session S1 loads the page row FOR UPDATE, the result always
728  // includes the latest changes committed. This is true even within REPEATABLE-READ
729  // transactions, where S1 normally only sees changes committed before the first S1
730  // SELECT. Thus we need S1 to also gets the revision row FOR UPDATE; otherwise, it
731  // may not find it since a page row UPDATE and revision row INSERT by S2 may have
732  // happened after the first S1 SELECT.
733  // https://dev.mysql.com/doc/refman/5.0/en/set-transaction.html#isolevel_repeatable-read
734  $flags = Revision::READ_LOCKING;
735  $revision = Revision::newFromPageId( $this->getId(), $latest, $flags );
736  } elseif ( $this->mDataLoadedFrom == self::READ_LATEST ) {
737  // Bug T93976: if page_latest was loaded from the master, fetch the
738  // revision from there as well, as it may not exist yet on a replica DB.
739  // Also, this keeps the queries in the same REPEATABLE-READ snapshot.
740  $flags = Revision::READ_LATEST;
741  $revision = Revision::newFromPageId( $this->getId(), $latest, $flags );
742  } else {
743  $dbr = wfGetDB( DB_REPLICA );
744  $revision = Revision::newKnownCurrent( $dbr, $this->getTitle(), $latest );
745  }
746 
747  if ( $revision ) { // sanity
748  $this->setLastEdit( $revision );
749  }
750  }
751 
756  protected function setLastEdit( Revision $revision ) {
757  $this->mLastRevision = $revision;
758  $this->mTimestamp = $revision->getTimestamp();
759  }
760 
765  public function getRevision() {
766  $this->loadLastEdit();
767  if ( $this->mLastRevision ) {
768  return $this->mLastRevision;
769  }
770  return null;
771  }
772 
777  public function getRevisionRecord() {
778  $this->loadLastEdit();
779  if ( $this->mLastRevision ) {
780  return $this->mLastRevision->getRevisionRecord();
781  }
782  return null;
783  }
784 
798  public function getContent( $audience = Revision::FOR_PUBLIC, User $user = null ) {
799  $this->loadLastEdit();
800  if ( $this->mLastRevision ) {
801  return $this->mLastRevision->getContent( $audience, $user );
802  }
803  return null;
804  }
805 
809  public function getTimestamp() {
810  // Check if the field has been filled by WikiPage::setTimestamp()
811  if ( !$this->mTimestamp ) {
812  $this->loadLastEdit();
813  }
814 
815  return wfTimestamp( TS_MW, $this->mTimestamp );
816  }
817 
823  public function setTimestamp( $ts ) {
824  $this->mTimestamp = wfTimestamp( TS_MW, $ts );
825  }
826 
836  public function getUser( $audience = Revision::FOR_PUBLIC, User $user = null ) {
837  $this->loadLastEdit();
838  if ( $this->mLastRevision ) {
839  return $this->mLastRevision->getUser( $audience, $user );
840  } else {
841  return -1;
842  }
843  }
844 
855  public function getCreator( $audience = Revision::FOR_PUBLIC, User $user = null ) {
856  $revision = $this->getOldestRevision();
857  if ( $revision ) {
858  $userName = $revision->getUserText( $audience, $user );
859  return User::newFromName( $userName, false );
860  } else {
861  return null;
862  }
863  }
864 
874  public function getUserText( $audience = Revision::FOR_PUBLIC, User $user = null ) {
875  $this->loadLastEdit();
876  if ( $this->mLastRevision ) {
877  return $this->mLastRevision->getUserText( $audience, $user );
878  } else {
879  return '';
880  }
881  }
882 
892  public function getComment( $audience = Revision::FOR_PUBLIC, User $user = null ) {
893  $this->loadLastEdit();
894  if ( $this->mLastRevision ) {
895  return $this->mLastRevision->getComment( $audience, $user );
896  } else {
897  return '';
898  }
899  }
900 
906  public function getMinorEdit() {
907  $this->loadLastEdit();
908  if ( $this->mLastRevision ) {
909  return $this->mLastRevision->isMinor();
910  } else {
911  return false;
912  }
913  }
914 
923  public function isCountable( $editInfo = false ) {
924  global $wgArticleCountMethod;
925 
926  // NOTE: Keep in sync with DerivedPageDataUpdater::isCountable.
927 
928  if ( !$this->mTitle->isContentPage() ) {
929  return false;
930  }
931 
932  if ( $editInfo ) {
933  // NOTE: only the main slot can make a page a redirect
934  $content = $editInfo->pstContent;
935  } else {
936  $content = $this->getContent();
937  }
938 
939  if ( !$content || $content->isRedirect() ) {
940  return false;
941  }
942 
943  $hasLinks = null;
944 
945  if ( $wgArticleCountMethod === 'link' ) {
946  // nasty special case to avoid re-parsing to detect links
947 
948  if ( $editInfo ) {
949  // ParserOutput::getLinks() is a 2D array of page links, so
950  // to be really correct we would need to recurse in the array
951  // but the main array should only have items in it if there are
952  // links.
953  $hasLinks = (bool)count( $editInfo->output->getLinks() );
954  } else {
955  // NOTE: keep in sync with revisionRenderer::getLinkCount
956  $hasLinks = (bool)wfGetDB( DB_REPLICA )->selectField( 'pagelinks', 1,
957  [ 'pl_from' => $this->getId() ], __METHOD__ );
958  }
959  }
960 
961  return $content->isCountable( $hasLinks );
962  }
963 
971  public function getRedirectTarget() {
972  if ( !$this->mTitle->isRedirect() ) {
973  return null;
974  }
975 
976  if ( $this->mRedirectTarget !== null ) {
977  return $this->mRedirectTarget;
978  }
979 
980  // Query the redirect table
981  $dbr = wfGetDB( DB_REPLICA );
982  $row = $dbr->selectRow( 'redirect',
983  [ 'rd_namespace', 'rd_title', 'rd_fragment', 'rd_interwiki' ],
984  [ 'rd_from' => $this->getId() ],
985  __METHOD__
986  );
987 
988  // rd_fragment and rd_interwiki were added later, populate them if empty
989  if ( $row && !is_null( $row->rd_fragment ) && !is_null( $row->rd_interwiki ) ) {
990  // (T203942) We can't redirect to Media namespace because it's virtual.
991  // We don't want to modify Title objects farther down the
992  // line. So, let's fix this here by changing to File namespace.
993  if ( $row->rd_namespace == NS_MEDIA ) {
994  $namespace = NS_FILE;
995  } else {
996  $namespace = $row->rd_namespace;
997  }
998  $this->mRedirectTarget = Title::makeTitle(
999  $namespace, $row->rd_title,
1000  $row->rd_fragment, $row->rd_interwiki
1001  );
1002  return $this->mRedirectTarget;
1003  }
1004 
1005  // This page doesn't have an entry in the redirect table
1006  $this->mRedirectTarget = $this->insertRedirect();
1007  return $this->mRedirectTarget;
1008  }
1009 
1018  public function insertRedirect() {
1019  $content = $this->getContent();
1020  $retval = $content ? $content->getUltimateRedirectTarget() : null;
1021  if ( !$retval ) {
1022  return null;
1023  }
1024 
1025  // Update the DB post-send if the page has not cached since now
1026  $latest = $this->getLatest();
1028  function () use ( $retval, $latest ) {
1029  $this->insertRedirectEntry( $retval, $latest );
1030  },
1032  wfGetDB( DB_MASTER )
1033  );
1034 
1035  return $retval;
1036  }
1037 
1043  public function insertRedirectEntry( Title $rt, $oldLatest = null ) {
1044  $dbw = wfGetDB( DB_MASTER );
1045  $dbw->startAtomic( __METHOD__ );
1046 
1047  if ( !$oldLatest || $oldLatest == $this->lockAndGetLatest() ) {
1048  $dbw->upsert(
1049  'redirect',
1050  [
1051  'rd_from' => $this->getId(),
1052  'rd_namespace' => $rt->getNamespace(),
1053  'rd_title' => $rt->getDBkey(),
1054  'rd_fragment' => $rt->getFragment(),
1055  'rd_interwiki' => $rt->getInterwiki(),
1056  ],
1057  [ 'rd_from' ],
1058  [
1059  'rd_namespace' => $rt->getNamespace(),
1060  'rd_title' => $rt->getDBkey(),
1061  'rd_fragment' => $rt->getFragment(),
1062  'rd_interwiki' => $rt->getInterwiki(),
1063  ],
1064  __METHOD__
1065  );
1066  }
1067 
1068  $dbw->endAtomic( __METHOD__ );
1069  }
1070 
1076  public function followRedirect() {
1077  return $this->getRedirectURL( $this->getRedirectTarget() );
1078  }
1079 
1087  public function getRedirectURL( $rt ) {
1088  if ( !$rt ) {
1089  return false;
1090  }
1091 
1092  if ( $rt->isExternal() ) {
1093  if ( $rt->isLocal() ) {
1094  // Offsite wikis need an HTTP redirect.
1095  // This can be hard to reverse and may produce loops,
1096  // so they may be disabled in the site configuration.
1097  $source = $this->mTitle->getFullURL( 'redirect=no' );
1098  return $rt->getFullURL( [ 'rdfrom' => $source ] );
1099  } else {
1100  // External pages without "local" bit set are not valid
1101  // redirect targets
1102  return false;
1103  }
1104  }
1105 
1106  if ( $rt->isSpecialPage() ) {
1107  // Gotta handle redirects to special pages differently:
1108  // Fill the HTTP response "Location" header and ignore the rest of the page we're on.
1109  // Some pages are not valid targets.
1110  if ( $rt->isValidRedirectTarget() ) {
1111  return $rt->getFullURL();
1112  } else {
1113  return false;
1114  }
1115  }
1116 
1117  return $rt;
1118  }
1119 
1125  public function getContributors() {
1126  // @todo: This is expensive; cache this info somewhere.
1127 
1128  $dbr = wfGetDB( DB_REPLICA );
1129 
1130  $actorMigration = ActorMigration::newMigration();
1131  $actorQuery = $actorMigration->getJoin( 'rev_user' );
1132 
1133  $tables = array_merge( [ 'revision' ], $actorQuery['tables'], [ 'user' ] );
1134 
1135  $fields = [
1136  'user_id' => $actorQuery['fields']['rev_user'],
1137  'user_name' => $actorQuery['fields']['rev_user_text'],
1138  'actor_id' => $actorQuery['fields']['rev_actor'],
1139  'user_real_name' => 'MIN(user_real_name)',
1140  'timestamp' => 'MAX(rev_timestamp)',
1141  ];
1142 
1143  $conds = [ 'rev_page' => $this->getId() ];
1144 
1145  // The user who made the top revision gets credited as "this page was last edited by
1146  // John, based on contributions by Tom, Dick and Harry", so don't include them twice.
1147  $user = $this->getUser()
1148  ? User::newFromId( $this->getUser() )
1149  : User::newFromName( $this->getUserText(), false );
1150  $conds[] = 'NOT(' . $actorMigration->getWhere( $dbr, 'rev_user', $user )['conds'] . ')';
1151 
1152  // Username hidden?
1153  $conds[] = "{$dbr->bitAnd( 'rev_deleted', Revision::DELETED_USER )} = 0";
1154 
1155  $jconds = [
1156  'user' => [ 'LEFT JOIN', $actorQuery['fields']['rev_user'] . ' = user_id' ],
1157  ] + $actorQuery['joins'];
1158 
1159  $options = [
1160  'GROUP BY' => [ $fields['user_id'], $fields['user_name'] ],
1161  'ORDER BY' => 'timestamp DESC',
1162  ];
1163 
1164  $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options, $jconds );
1165  return new UserArrayFromResult( $res );
1166  }
1167 
1175  public function shouldCheckParserCache( ParserOptions $parserOptions, $oldId ) {
1176  return $parserOptions->getStubThreshold() == 0
1177  && $this->exists()
1178  && ( $oldId === null || $oldId === 0 || $oldId === $this->getLatest() )
1179  && $this->getContentHandler()->isParserCacheSupported();
1180  }
1181 
1197  public function getParserOutput(
1198  ParserOptions $parserOptions, $oldid = null, $forceParse = false
1199  ) {
1200  $useParserCache =
1201  ( !$forceParse ) && $this->shouldCheckParserCache( $parserOptions, $oldid );
1202 
1203  if ( $useParserCache && !$parserOptions->isSafeToCache() ) {
1204  throw new InvalidArgumentException(
1205  'The supplied ParserOptions are not safe to cache. Fix the options or set $forceParse = true.'
1206  );
1207  }
1208 
1209  wfDebug( __METHOD__ .
1210  ': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
1211  if ( $parserOptions->getStubThreshold() ) {
1212  wfIncrStats( 'pcache.miss.stub' );
1213  }
1214 
1215  if ( $useParserCache ) {
1216  $parserOutput = $this->getParserCache()
1217  ->get( $this, $parserOptions );
1218  if ( $parserOutput !== false ) {
1219  return $parserOutput;
1220  }
1221  }
1222 
1223  if ( $oldid === null || $oldid === 0 ) {
1224  $oldid = $this->getLatest();
1225  }
1226 
1227  $pool = new PoolWorkArticleView( $this, $parserOptions, $oldid, $useParserCache );
1228  $pool->execute();
1229 
1230  return $pool->getParserOutput();
1231  }
1232 
1238  public function doViewUpdates( User $user, $oldid = 0 ) {
1239  if ( wfReadOnly() ) {
1240  return;
1241  }
1242 
1243  // Update newtalk / watchlist notification status;
1244  // Avoid outage if the master is not reachable by using a deferred updated
1246  function () use ( $user, $oldid ) {
1247  Hooks::run( 'PageViewUpdates', [ $this, $user ] );
1248 
1249  $user->clearNotification( $this->mTitle, $oldid );
1250  },
1252  );
1253  }
1254 
1261  public function doPurge() {
1262  // Avoid PHP 7.1 warning of passing $this by reference
1263  $wikiPage = $this;
1264 
1265  if ( !Hooks::run( 'ArticlePurge', [ &$wikiPage ] ) ) {
1266  return false;
1267  }
1268 
1269  $this->mTitle->invalidateCache();
1270 
1271  // Clear file cache
1273  // Send purge after above page_touched update was committed
1275  new CdnCacheUpdate( $this->mTitle->getCdnUrls() ),
1277  );
1278 
1279  if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
1280  $messageCache = MessageCache::singleton();
1281  $messageCache->updateMessageOverride( $this->mTitle, $this->getContent() );
1282  }
1283 
1284  return true;
1285  }
1286 
1303  public function insertOn( $dbw, $pageId = null ) {
1304  $pageIdForInsert = $pageId ? [ 'page_id' => $pageId ] : [];
1305  $dbw->insert(
1306  'page',
1307  [
1308  'page_namespace' => $this->mTitle->getNamespace(),
1309  'page_title' => $this->mTitle->getDBkey(),
1310  'page_restrictions' => '',
1311  'page_is_redirect' => 0, // Will set this shortly...
1312  'page_is_new' => 1,
1313  'page_random' => wfRandom(),
1314  'page_touched' => $dbw->timestamp(),
1315  'page_latest' => 0, // Fill this in shortly...
1316  'page_len' => 0, // Fill this in shortly...
1317  ] + $pageIdForInsert,
1318  __METHOD__,
1319  'IGNORE'
1320  );
1321 
1322  if ( $dbw->affectedRows() > 0 ) {
1323  $newid = $pageId ? (int)$pageId : $dbw->insertId();
1324  $this->mId = $newid;
1325  $this->mTitle->resetArticleID( $newid );
1326 
1327  return $newid;
1328  } else {
1329  return false; // nothing changed
1330  }
1331  }
1332 
1348  public function updateRevisionOn( $dbw, $revision, $lastRevision = null,
1349  $lastRevIsRedirect = null
1350  ) {
1351  global $wgContentHandlerUseDB;
1352 
1353  // TODO: move into PageUpdater or PageStore
1354  // NOTE: when doing that, make sure cached fields get reset in doEditContent,
1355  // and in the compat stub!
1356 
1357  // Assertion to try to catch T92046
1358  if ( (int)$revision->getId() === 0 ) {
1359  throw new InvalidArgumentException(
1360  __METHOD__ . ': Revision has ID ' . var_export( $revision->getId(), 1 )
1361  );
1362  }
1363 
1364  $content = $revision->getContent();
1365  $len = $content ? $content->getSize() : 0;
1366  $rt = $content ? $content->getUltimateRedirectTarget() : null;
1367 
1368  $conditions = [ 'page_id' => $this->getId() ];
1369 
1370  if ( !is_null( $lastRevision ) ) {
1371  // An extra check against threads stepping on each other
1372  $conditions['page_latest'] = $lastRevision;
1373  }
1374 
1375  $revId = $revision->getId();
1376  Assert::parameter( $revId > 0, '$revision->getId()', 'must be > 0' );
1377 
1378  $row = [ /* SET */
1379  'page_latest' => $revId,
1380  'page_touched' => $dbw->timestamp( $revision->getTimestamp() ),
1381  'page_is_new' => ( $lastRevision === 0 ) ? 1 : 0,
1382  'page_is_redirect' => $rt !== null ? 1 : 0,
1383  'page_len' => $len,
1384  ];
1385 
1386  if ( $wgContentHandlerUseDB ) {
1387  $row['page_content_model'] = $revision->getContentModel();
1388  }
1389 
1390  $dbw->update( 'page',
1391  $row,
1392  $conditions,
1393  __METHOD__ );
1394 
1395  $result = $dbw->affectedRows() > 0;
1396  if ( $result ) {
1397  $this->updateRedirectOn( $dbw, $rt, $lastRevIsRedirect );
1398  $this->setLastEdit( $revision );
1399  $this->mLatest = $revision->getId();
1400  $this->mIsRedirect = (bool)$rt;
1401  // Update the LinkCache.
1402  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
1403  $linkCache->addGoodLinkObj(
1404  $this->getId(),
1405  $this->mTitle,
1406  $len,
1407  $this->mIsRedirect,
1408  $this->mLatest,
1409  $revision->getContentModel()
1410  );
1411  }
1412 
1413  return $result;
1414  }
1415 
1427  public function updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect = null ) {
1428  // Always update redirects (target link might have changed)
1429  // Update/Insert if we don't know if the last revision was a redirect or not
1430  // Delete if changing from redirect to non-redirect
1431  $isRedirect = !is_null( $redirectTitle );
1432 
1433  if ( !$isRedirect && $lastRevIsRedirect === false ) {
1434  return true;
1435  }
1436 
1437  if ( $isRedirect ) {
1438  $this->insertRedirectEntry( $redirectTitle );
1439  } else {
1440  // This is not a redirect, remove row from redirect table
1441  $where = [ 'rd_from' => $this->getId() ];
1442  $dbw->delete( 'redirect', $where, __METHOD__ );
1443  }
1444 
1445  if ( $this->getTitle()->getNamespace() == NS_FILE ) {
1446  RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $this->getTitle() );
1447  }
1448 
1449  return ( $dbw->affectedRows() != 0 );
1450  }
1451 
1462  public function updateIfNewerOn( $dbw, $revision ) {
1463  $row = $dbw->selectRow(
1464  [ 'revision', 'page' ],
1465  [ 'rev_id', 'rev_timestamp', 'page_is_redirect' ],
1466  [
1467  'page_id' => $this->getId(),
1468  'page_latest=rev_id' ],
1469  __METHOD__ );
1470 
1471  if ( $row ) {
1472  if ( wfTimestamp( TS_MW, $row->rev_timestamp ) >= $revision->getTimestamp() ) {
1473  return false;
1474  }
1475  $prev = $row->rev_id;
1476  $lastRevIsRedirect = (bool)$row->page_is_redirect;
1477  } else {
1478  // No or missing previous revision; mark the page as new
1479  $prev = 0;
1480  $lastRevIsRedirect = null;
1481  }
1482 
1483  $ret = $this->updateRevisionOn( $dbw, $revision, $prev, $lastRevIsRedirect );
1484 
1485  return $ret;
1486  }
1487 
1500  public static function hasDifferencesOutsideMainSlot( Revision $a, Revision $b ) {
1501  $aSlots = $a->getRevisionRecord()->getSlots();
1502  $bSlots = $b->getRevisionRecord()->getSlots();
1503  $changedRoles = $aSlots->getRolesWithDifferentContent( $bSlots );
1504 
1505  return ( $changedRoles !== [ SlotRecord::MAIN ] && $changedRoles !== [] );
1506  }
1507 
1519  public function getUndoContent( Revision $undo, Revision $undoafter ) {
1520  // TODO: MCR: replace this with a method that returns a RevisionSlotsUpdate
1521 
1522  if ( self::hasDifferencesOutsideMainSlot( $undo, $undoafter ) ) {
1523  // Cannot yet undo edits that involve anything other the main slot.
1524  return false;
1525  }
1526 
1527  $handler = $undo->getContentHandler();
1528  return $handler->getUndoContent( $this->getRevision(), $undo, $undoafter );
1529  }
1530 
1541  public function supportsSections() {
1542  return $this->getContentHandler()->supportsSections();
1543  }
1544 
1559  public function replaceSectionContent(
1560  $sectionId, Content $sectionContent, $sectionTitle = '', $edittime = null
1561  ) {
1562  $baseRevId = null;
1563  if ( $edittime && $sectionId !== 'new' ) {
1564  $lb = $this->getDBLoadBalancer();
1565  $dbr = $lb->getConnection( DB_REPLICA );
1566  $rev = Revision::loadFromTimestamp( $dbr, $this->mTitle, $edittime );
1567  // Try the master if this thread may have just added it.
1568  // This could be abstracted into a Revision method, but we don't want
1569  // to encourage loading of revisions by timestamp.
1570  if ( !$rev
1571  && $lb->getServerCount() > 1
1572  && $lb->hasOrMadeRecentMasterChanges()
1573  ) {
1574  $dbw = $lb->getConnection( DB_MASTER );
1575  $rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime );
1576  }
1577  if ( $rev ) {
1578  $baseRevId = $rev->getId();
1579  }
1580  }
1581 
1582  return $this->replaceSectionAtRev( $sectionId, $sectionContent, $sectionTitle, $baseRevId );
1583  }
1584 
1598  public function replaceSectionAtRev( $sectionId, Content $sectionContent,
1599  $sectionTitle = '', $baseRevId = null
1600  ) {
1601  if ( strval( $sectionId ) === '' ) {
1602  // Whole-page edit; let the whole text through
1603  $newContent = $sectionContent;
1604  } else {
1605  if ( !$this->supportsSections() ) {
1606  throw new MWException( "sections not supported for content model " .
1607  $this->getContentHandler()->getModelID() );
1608  }
1609 
1610  // T32711: always use current version when adding a new section
1611  if ( is_null( $baseRevId ) || $sectionId === 'new' ) {
1612  $oldContent = $this->getContent();
1613  } else {
1614  $rev = Revision::newFromId( $baseRevId );
1615  if ( !$rev ) {
1616  wfDebug( __METHOD__ . " asked for bogus section (page: " .
1617  $this->getId() . "; section: $sectionId)\n" );
1618  return null;
1619  }
1620 
1621  $oldContent = $rev->getContent();
1622  }
1623 
1624  if ( !$oldContent ) {
1625  wfDebug( __METHOD__ . ": no page text\n" );
1626  return null;
1627  }
1628 
1629  $newContent = $oldContent->replaceSection( $sectionId, $sectionContent, $sectionTitle );
1630  }
1631 
1632  return $newContent;
1633  }
1634 
1644  public function checkFlags( $flags ) {
1645  if ( !( $flags & EDIT_NEW ) && !( $flags & EDIT_UPDATE ) ) {
1646  if ( $this->exists() ) {
1647  $flags |= EDIT_UPDATE;
1648  } else {
1649  $flags |= EDIT_NEW;
1650  }
1651  }
1652 
1653  return $flags;
1654  }
1655 
1659  private function newDerivedDataUpdater() {
1661 
1663  $this, // NOTE: eventually, PageUpdater should not know about WikiPage
1664  $this->getRevisionStore(),
1665  $this->getRevisionRenderer(),
1666  $this->getParserCache(),
1669  MediaWikiServices::getInstance()->getContentLanguage(),
1670  MediaWikiServices::getInstance()->getDBLoadBalancerFactory()
1671  );
1672 
1675 
1676  return $derivedDataUpdater;
1677  }
1678 
1706  private function getDerivedDataUpdater(
1707  User $forUser = null,
1708  RevisionRecord $forRevision = null,
1709  RevisionSlotsUpdate $forUpdate = null,
1710  $forEdit = false
1711  ) {
1712  if ( !$forRevision && !$forUpdate ) {
1713  // NOTE: can't re-use an existing derivedDataUpdater if we don't know what the caller is
1714  // going to use it with.
1715  $this->derivedDataUpdater = null;
1716  }
1717 
1718  if ( $this->derivedDataUpdater && !$this->derivedDataUpdater->isContentPrepared() ) {
1719  // NOTE: can't re-use an existing derivedDataUpdater if other code that has a reference
1720  // to it did not yet initialize it, because we don't know what data it will be
1721  // initialized with.
1722  $this->derivedDataUpdater = null;
1723  }
1724 
1725  // XXX: It would be nice to have an LRU cache instead of trying to re-use a single instance.
1726  // However, there is no good way to construct a cache key. We'd need to check against all
1727  // cached instances.
1728 
1729  if ( $this->derivedDataUpdater
1730  && !$this->derivedDataUpdater->isReusableFor(
1731  $forUser,
1732  $forRevision,
1733  $forUpdate,
1734  $forEdit ? $this->getLatest() : null
1735  )
1736  ) {
1737  $this->derivedDataUpdater = null;
1738  }
1739 
1740  if ( !$this->derivedDataUpdater ) {
1741  $this->derivedDataUpdater = $this->newDerivedDataUpdater();
1742  }
1743 
1745  }
1746 
1762  public function newPageUpdater( User $user, RevisionSlotsUpdate $forUpdate = null ) {
1764 
1765  $pageUpdater = new PageUpdater(
1766  $user,
1767  $this, // NOTE: eventually, PageUpdater should not know about WikiPage
1768  $this->getDerivedDataUpdater( $user, null, $forUpdate, true ),
1769  $this->getDBLoadBalancer(),
1770  $this->getRevisionStore()
1771  );
1772 
1773  $pageUpdater->setUsePageCreationLog( $wgPageCreationLog );
1774  $pageUpdater->setAjaxEditStash( $wgAjaxEditStash );
1775  $pageUpdater->setUseAutomaticEditSummaries( $wgUseAutomaticEditSummaries );
1776 
1777  return $pageUpdater;
1778  }
1779 
1842  public function doEditContent(
1843  Content $content, $summary, $flags = 0, $originalRevId = false,
1844  User $user = null, $serialFormat = null, $tags = [], $undidRevId = 0
1845  ) {
1846  global $wgUser, $wgUseNPPatrol, $wgUseRCPatrol;
1847 
1848  if ( !( $summary instanceof CommentStoreComment ) ) {
1849  $summary = CommentStoreComment::newUnsavedComment( trim( $summary ) );
1850  }
1851 
1852  if ( !$user ) {
1853  $user = $wgUser;
1854  }
1855 
1856  // TODO: this check is here for backwards-compatibility with 1.31 behavior.
1857  // Checking the minoredit right should be done in the same place the 'bot' right is
1858  // checked for the EDIT_FORCE_BOT flag, which is currently in EditPage::attemptSave.
1859  if ( ( $flags & EDIT_MINOR ) && !$user->isAllowed( 'minoredit' ) ) {
1860  $flags = ( $flags & ~EDIT_MINOR );
1861  }
1862 
1863  $slotsUpdate = new RevisionSlotsUpdate();
1864  $slotsUpdate->modifyContent( SlotRecord::MAIN, $content );
1865 
1866  // NOTE: while doEditContent() executes, callbacks to getDerivedDataUpdater and
1867  // prepareContentForEdit will generally use the DerivedPageDataUpdater that is also
1868  // used by this PageUpdater. However, there is no guarantee for this.
1869  $updater = $this->newPageUpdater( $user, $slotsUpdate );
1870  $updater->setContent( SlotRecord::MAIN, $content );
1871  $updater->setOriginalRevisionId( $originalRevId );
1872  $updater->setUndidRevisionId( $undidRevId );
1873 
1874  $needsPatrol = $wgUseRCPatrol || ( $wgUseNPPatrol && !$this->exists() );
1875 
1876  // TODO: this logic should not be in the storage layer, it's here for compatibility
1877  // with 1.31 behavior. Applying the 'autopatrol' right should be done in the same
1878  // place the 'bot' right is handled, which is currently in EditPage::attemptSave.
1879  if ( $needsPatrol && $this->getTitle()->userCan( 'autopatrol', $user ) ) {
1880  $updater->setRcPatrolStatus( RecentChange::PRC_AUTOPATROLLED );
1881  }
1882 
1883  $updater->addTags( $tags );
1884 
1885  $revRec = $updater->saveRevision(
1886  $summary,
1887  $flags
1888  );
1889 
1890  // $revRec will be null if the edit failed, or if no new revision was created because
1891  // the content did not change.
1892  if ( $revRec ) {
1893  // update cached fields
1894  // TODO: this is currently redundant to what is done in updateRevisionOn.
1895  // But updateRevisionOn() should move into PageStore, and then this will be needed.
1896  $this->setLastEdit( new Revision( $revRec ) ); // TODO: use RevisionRecord
1897  $this->mLatest = $revRec->getId();
1898  }
1899 
1900  return $updater->getStatus();
1901  }
1902 
1917  public function makeParserOptions( $context ) {
1919 
1920  if ( $this->getTitle()->isConversionTable() ) {
1921  // @todo ConversionTable should become a separate content model, so
1922  // we don't need special cases like this one.
1923  $options->disableContentConversion();
1924  }
1925 
1926  return $options;
1927  }
1928 
1949  public function prepareContentForEdit(
1950  Content $content,
1951  $revision = null,
1952  User $user = null,
1953  $serialFormat = null,
1954  $useCache = true
1955  ) {
1956  global $wgUser;
1957 
1958  if ( !$user ) {
1959  $user = $wgUser;
1960  }
1961 
1962  if ( !is_object( $revision ) ) {
1963  $revid = $revision;
1964  // This code path is deprecated, and nothing is known to
1965  // use it, so performance here shouldn't be a worry.
1966  if ( $revid !== null ) {
1967  wfDeprecated( __METHOD__ . ' with $revision = revision ID', '1.25' );
1968  $store = $this->getRevisionStore();
1969  $revision = $store->getRevisionById( $revid, Revision::READ_LATEST );
1970  } else {
1971  $revision = null;
1972  }
1973  } elseif ( $revision instanceof Revision ) {
1974  $revision = $revision->getRevisionRecord();
1975  }
1976 
1977  $slots = RevisionSlotsUpdate::newFromContent( [ SlotRecord::MAIN => $content ] );
1978  $updater = $this->getDerivedDataUpdater( $user, $revision, $slots );
1979 
1980  if ( !$updater->isUpdatePrepared() ) {
1981  $updater->prepareContent( $user, $slots, $useCache );
1982 
1983  if ( $revision ) {
1984  $updater->prepareUpdate(
1985  $revision,
1986  [
1987  'causeAction' => 'prepare-edit',
1988  'causeAgent' => $user->getName(),
1989  ]
1990  );
1991  }
1992  }
1993 
1994  return $updater->getPreparedEdit();
1995  }
1996 
2024  public function doEditUpdates( Revision $revision, User $user, array $options = [] ) {
2025  $options += [
2026  'causeAction' => 'edit-page',
2027  'causeAgent' => $user->getName(),
2028  ];
2029 
2030  $revision = $revision->getRevisionRecord();
2031 
2032  $updater = $this->getDerivedDataUpdater( $user, $revision );
2033 
2034  $updater->prepareUpdate( $revision, $options );
2035 
2036  $updater->doUpdates();
2037  }
2038 
2052  public function updateParserCache( array $options = [] ) {
2053  $revision = $this->getRevisionRecord();
2054  if ( !$revision || !$revision->getId() ) {
2055  LoggerFactory::getInstance( 'wikipage' )->info(
2056  __METHOD__ . 'called with ' . ( $revision ? 'unsaved' : 'no' ) . ' revision'
2057  );
2058  return;
2059  }
2060  $user = User::newFromIdentity( $revision->getUser( RevisionRecord::RAW ) );
2061 
2062  $updater = $this->getDerivedDataUpdater( $user, $revision );
2063  $updater->prepareUpdate( $revision, $options );
2064  $updater->doParserCacheUpdate();
2065  }
2066 
2093  public function doSecondaryDataUpdates( array $options = [] ) {
2094  $options['recursive'] = $options['recursive'] ?? true;
2095  $revision = $this->getRevisionRecord();
2096  if ( !$revision || !$revision->getId() ) {
2097  LoggerFactory::getInstance( 'wikipage' )->info(
2098  __METHOD__ . 'called with ' . ( $revision ? 'unsaved' : 'no' ) . ' revision'
2099  );
2100  return;
2101  }
2102  $user = User::newFromIdentity( $revision->getUser( RevisionRecord::RAW ) );
2103 
2104  $updater = $this->getDerivedDataUpdater( $user, $revision );
2105  $updater->prepareUpdate( $revision, $options );
2106  $updater->doSecondaryDataUpdates( $options );
2107  }
2108 
2123  public function doUpdateRestrictions( array $limit, array $expiry,
2124  &$cascade, $reason, User $user, $tags = null
2125  ) {
2127 
2128  if ( wfReadOnly() ) {
2129  return Status::newFatal( wfMessage( 'readonlytext', wfReadOnlyReason() ) );
2130  }
2131 
2132  $this->loadPageData( 'fromdbmaster' );
2133  $restrictionTypes = $this->mTitle->getRestrictionTypes();
2134  $id = $this->getId();
2135 
2136  if ( !$cascade ) {
2137  $cascade = false;
2138  }
2139 
2140  // Take this opportunity to purge out expired restrictions
2142 
2143  // @todo: Same limitations as described in ProtectionForm.php (line 37);
2144  // we expect a single selection, but the schema allows otherwise.
2145  $isProtected = false;
2146  $protect = false;
2147  $changed = false;
2148 
2149  $dbw = wfGetDB( DB_MASTER );
2150 
2151  foreach ( $restrictionTypes as $action ) {
2152  if ( !isset( $expiry[$action] ) || $expiry[$action] === $dbw->getInfinity() ) {
2153  $expiry[$action] = 'infinity';
2154  }
2155  if ( !isset( $limit[$action] ) ) {
2156  $limit[$action] = '';
2157  } elseif ( $limit[$action] != '' ) {
2158  $protect = true;
2159  }
2160 
2161  // Get current restrictions on $action
2162  $current = implode( '', $this->mTitle->getRestrictions( $action ) );
2163  if ( $current != '' ) {
2164  $isProtected = true;
2165  }
2166 
2167  if ( $limit[$action] != $current ) {
2168  $changed = true;
2169  } elseif ( $limit[$action] != '' ) {
2170  // Only check expiry change if the action is actually being
2171  // protected, since expiry does nothing on an not-protected
2172  // action.
2173  if ( $this->mTitle->getRestrictionExpiry( $action ) != $expiry[$action] ) {
2174  $changed = true;
2175  }
2176  }
2177  }
2178 
2179  if ( !$changed && $protect && $this->mTitle->areRestrictionsCascading() != $cascade ) {
2180  $changed = true;
2181  }
2182 
2183  // If nothing has changed, do nothing
2184  if ( !$changed ) {
2185  return Status::newGood();
2186  }
2187 
2188  if ( !$protect ) { // No protection at all means unprotection
2189  $revCommentMsg = 'unprotectedarticle-comment';
2190  $logAction = 'unprotect';
2191  } elseif ( $isProtected ) {
2192  $revCommentMsg = 'modifiedarticleprotection-comment';
2193  $logAction = 'modify';
2194  } else {
2195  $revCommentMsg = 'protectedarticle-comment';
2196  $logAction = 'protect';
2197  }
2198 
2199  $logRelationsValues = [];
2200  $logRelationsField = null;
2201  $logParamsDetails = [];
2202 
2203  // Null revision (used for change tag insertion)
2204  $nullRevision = null;
2205 
2206  if ( $id ) { // Protection of existing page
2207  // Avoid PHP 7.1 warning of passing $this by reference
2208  $wikiPage = $this;
2209 
2210  if ( !Hooks::run( 'ArticleProtect', [ &$wikiPage, &$user, $limit, $reason ] ) ) {
2211  return Status::newGood();
2212  }
2213 
2214  // Only certain restrictions can cascade...
2215  $editrestriction = isset( $limit['edit'] )
2216  ? [ $limit['edit'] ]
2217  : $this->mTitle->getRestrictions( 'edit' );
2218  foreach ( array_keys( $editrestriction, 'sysop' ) as $key ) {
2219  $editrestriction[$key] = 'editprotected'; // backwards compatibility
2220  }
2221  foreach ( array_keys( $editrestriction, 'autoconfirmed' ) as $key ) {
2222  $editrestriction[$key] = 'editsemiprotected'; // backwards compatibility
2223  }
2224 
2225  $cascadingRestrictionLevels = $wgCascadingRestrictionLevels;
2226  foreach ( array_keys( $cascadingRestrictionLevels, 'sysop' ) as $key ) {
2227  $cascadingRestrictionLevels[$key] = 'editprotected'; // backwards compatibility
2228  }
2229  foreach ( array_keys( $cascadingRestrictionLevels, 'autoconfirmed' ) as $key ) {
2230  $cascadingRestrictionLevels[$key] = 'editsemiprotected'; // backwards compatibility
2231  }
2232 
2233  // The schema allows multiple restrictions
2234  if ( !array_intersect( $editrestriction, $cascadingRestrictionLevels ) ) {
2235  $cascade = false;
2236  }
2237 
2238  // insert null revision to identify the page protection change as edit summary
2239  $latest = $this->getLatest();
2240  $nullRevision = $this->insertProtectNullRevision(
2241  $revCommentMsg,
2242  $limit,
2243  $expiry,
2244  $cascade,
2245  $reason,
2246  $user
2247  );
2248 
2249  if ( $nullRevision === null ) {
2250  return Status::newFatal( 'no-null-revision', $this->mTitle->getPrefixedText() );
2251  }
2252 
2253  $logRelationsField = 'pr_id';
2254 
2255  // Update restrictions table
2256  foreach ( $limit as $action => $restrictions ) {
2257  $dbw->delete(
2258  'page_restrictions',
2259  [
2260  'pr_page' => $id,
2261  'pr_type' => $action
2262  ],
2263  __METHOD__
2264  );
2265  if ( $restrictions != '' ) {
2266  $cascadeValue = ( $cascade && $action == 'edit' ) ? 1 : 0;
2267  $dbw->insert(
2268  'page_restrictions',
2269  [
2270  'pr_page' => $id,
2271  'pr_type' => $action,
2272  'pr_level' => $restrictions,
2273  'pr_cascade' => $cascadeValue,
2274  'pr_expiry' => $dbw->encodeExpiry( $expiry[$action] )
2275  ],
2276  __METHOD__
2277  );
2278  $logRelationsValues[] = $dbw->insertId();
2279  $logParamsDetails[] = [
2280  'type' => $action,
2281  'level' => $restrictions,
2282  'expiry' => $expiry[$action],
2283  'cascade' => (bool)$cascadeValue,
2284  ];
2285  }
2286  }
2287 
2288  // Clear out legacy restriction fields
2289  $dbw->update(
2290  'page',
2291  [ 'page_restrictions' => '' ],
2292  [ 'page_id' => $id ],
2293  __METHOD__
2294  );
2295 
2296  // Avoid PHP 7.1 warning of passing $this by reference
2297  $wikiPage = $this;
2298 
2299  Hooks::run( 'NewRevisionFromEditComplete',
2300  [ $this, $nullRevision, $latest, $user ] );
2301  Hooks::run( 'ArticleProtectComplete', [ &$wikiPage, &$user, $limit, $reason ] );
2302  } else { // Protection of non-existing page (also known as "title protection")
2303  // Cascade protection is meaningless in this case
2304  $cascade = false;
2305 
2306  if ( $limit['create'] != '' ) {
2307  $commentFields = CommentStore::getStore()->insert( $dbw, 'pt_reason', $reason );
2308  $dbw->replace( 'protected_titles',
2309  [ [ 'pt_namespace', 'pt_title' ] ],
2310  [
2311  'pt_namespace' => $this->mTitle->getNamespace(),
2312  'pt_title' => $this->mTitle->getDBkey(),
2313  'pt_create_perm' => $limit['create'],
2314  'pt_timestamp' => $dbw->timestamp(),
2315  'pt_expiry' => $dbw->encodeExpiry( $expiry['create'] ),
2316  'pt_user' => $user->getId(),
2317  ] + $commentFields, __METHOD__
2318  );
2319  $logParamsDetails[] = [
2320  'type' => 'create',
2321  'level' => $limit['create'],
2322  'expiry' => $expiry['create'],
2323  ];
2324  } else {
2325  $dbw->delete( 'protected_titles',
2326  [
2327  'pt_namespace' => $this->mTitle->getNamespace(),
2328  'pt_title' => $this->mTitle->getDBkey()
2329  ], __METHOD__
2330  );
2331  }
2332  }
2333 
2334  $this->mTitle->flushRestrictions();
2335  InfoAction::invalidateCache( $this->mTitle );
2336 
2337  if ( $logAction == 'unprotect' ) {
2338  $params = [];
2339  } else {
2340  $protectDescriptionLog = $this->protectDescriptionLog( $limit, $expiry );
2341  $params = [
2342  '4::description' => $protectDescriptionLog, // parameter for IRC
2343  '5:bool:cascade' => $cascade,
2344  'details' => $logParamsDetails, // parameter for localize and api
2345  ];
2346  }
2347 
2348  // Update the protection log
2349  $logEntry = new ManualLogEntry( 'protect', $logAction );
2350  $logEntry->setTarget( $this->mTitle );
2351  $logEntry->setComment( $reason );
2352  $logEntry->setPerformer( $user );
2353  $logEntry->setParameters( $params );
2354  if ( !is_null( $nullRevision ) ) {
2355  $logEntry->setAssociatedRevId( $nullRevision->getId() );
2356  }
2357  $logEntry->setTags( $tags );
2358  if ( $logRelationsField !== null && count( $logRelationsValues ) ) {
2359  $logEntry->setRelations( [ $logRelationsField => $logRelationsValues ] );
2360  }
2361  $logId = $logEntry->insert();
2362  $logEntry->publish( $logId );
2363 
2364  return Status::newGood( $logId );
2365  }
2366 
2378  public function insertProtectNullRevision( $revCommentMsg, array $limit,
2379  array $expiry, $cascade, $reason, $user = null
2380  ) {
2381  $dbw = wfGetDB( DB_MASTER );
2382 
2383  // Prepare a null revision to be added to the history
2384  $editComment = wfMessage(
2385  $revCommentMsg,
2386  $this->mTitle->getPrefixedText(),
2387  $user ? $user->getName() : ''
2388  )->inContentLanguage()->text();
2389  if ( $reason ) {
2390  $editComment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
2391  }
2392  $protectDescription = $this->protectDescription( $limit, $expiry );
2393  if ( $protectDescription ) {
2394  $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
2395  $editComment .= wfMessage( 'parentheses' )->params( $protectDescription )
2396  ->inContentLanguage()->text();
2397  }
2398  if ( $cascade ) {
2399  $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
2400  $editComment .= wfMessage( 'brackets' )->params(
2401  wfMessage( 'protect-summary-cascade' )->inContentLanguage()->text()
2402  )->inContentLanguage()->text();
2403  }
2404 
2405  $nullRev = Revision::newNullRevision( $dbw, $this->getId(), $editComment, true, $user );
2406  if ( $nullRev ) {
2407  $nullRev->insertOn( $dbw );
2408 
2409  // Update page record and touch page
2410  $oldLatest = $nullRev->getParentId();
2411  $this->updateRevisionOn( $dbw, $nullRev, $oldLatest );
2412  }
2413 
2414  return $nullRev;
2415  }
2416 
2421  protected function formatExpiry( $expiry ) {
2422  if ( $expiry != 'infinity' ) {
2423  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
2424  return wfMessage(
2425  'protect-expiring',
2426  $contLang->timeanddate( $expiry, false, false ),
2427  $contLang->date( $expiry, false, false ),
2428  $contLang->time( $expiry, false, false )
2429  )->inContentLanguage()->text();
2430  } else {
2431  return wfMessage( 'protect-expiry-indefinite' )
2432  ->inContentLanguage()->text();
2433  }
2434  }
2435 
2443  public function protectDescription( array $limit, array $expiry ) {
2444  $protectDescription = '';
2445 
2446  foreach ( array_filter( $limit ) as $action => $restrictions ) {
2447  # $action is one of $wgRestrictionTypes = [ 'create', 'edit', 'move', 'upload' ].
2448  # All possible message keys are listed here for easier grepping:
2449  # * restriction-create
2450  # * restriction-edit
2451  # * restriction-move
2452  # * restriction-upload
2453  $actionText = wfMessage( 'restriction-' . $action )->inContentLanguage()->text();
2454  # $restrictions is one of $wgRestrictionLevels = [ '', 'autoconfirmed', 'sysop' ],
2455  # with '' filtered out. All possible message keys are listed below:
2456  # * protect-level-autoconfirmed
2457  # * protect-level-sysop
2458  $restrictionsText = wfMessage( 'protect-level-' . $restrictions )
2459  ->inContentLanguage()->text();
2460 
2461  $expiryText = $this->formatExpiry( $expiry[$action] );
2462 
2463  if ( $protectDescription !== '' ) {
2464  $protectDescription .= wfMessage( 'word-separator' )->inContentLanguage()->text();
2465  }
2466  $protectDescription .= wfMessage( 'protect-summary-desc' )
2467  ->params( $actionText, $restrictionsText, $expiryText )
2468  ->inContentLanguage()->text();
2469  }
2470 
2471  return $protectDescription;
2472  }
2473 
2485  public function protectDescriptionLog( array $limit, array $expiry ) {
2486  $protectDescriptionLog = '';
2487 
2488  $dirMark = MediaWikiServices::getInstance()->getContentLanguage()->getDirMark();
2489  foreach ( array_filter( $limit ) as $action => $restrictions ) {
2490  $expiryText = $this->formatExpiry( $expiry[$action] );
2491  $protectDescriptionLog .=
2492  $dirMark .
2493  "[$action=$restrictions] ($expiryText)";
2494  }
2495 
2496  return trim( $protectDescriptionLog );
2497  }
2498 
2508  protected static function flattenRestrictions( $limit ) {
2509  if ( !is_array( $limit ) ) {
2510  throw new MWException( __METHOD__ . ' given non-array restriction set' );
2511  }
2512 
2513  $bits = [];
2514  ksort( $limit );
2515 
2516  foreach ( array_filter( $limit ) as $action => $restrictions ) {
2517  $bits[] = "$action=$restrictions";
2518  }
2519 
2520  return implode( ':', $bits );
2521  }
2522 
2535  public function isBatchedDelete( $safetyMargin = 0 ) {
2537 
2538  $dbr = wfGetDB( DB_REPLICA );
2539  $revCount = $this->getRevisionStore()->countRevisionsByPageId( $dbr, $this->getId() );
2540  $revCount += $safetyMargin;
2541 
2542  return $revCount >= $wgDeleteRevisionsBatchSize;
2543  }
2544 
2564  public function doDeleteArticle(
2565  $reason, $suppress = false, $u1 = null, $u2 = null, &$error = '', User $user = null,
2566  $immediate = false
2567  ) {
2568  $status = $this->doDeleteArticleReal( $reason, $suppress, $u1, $u2, $error, $user,
2569  [], 'delete', $immediate );
2570 
2571  // Returns true if the page was actually deleted, or is scheduled for deletion
2572  return $status->isOK();
2573  }
2574 
2597  public function doDeleteArticleReal(
2598  $reason, $suppress = false, $u1 = null, $u2 = null, &$error = '', User $deleter = null,
2599  $tags = [], $logsubtype = 'delete', $immediate = false
2600  ) {
2601  global $wgUser;
2602 
2603  wfDebug( __METHOD__ . "\n" );
2604 
2606 
2607  // Avoid PHP 7.1 warning of passing $this by reference
2608  $wikiPage = $this;
2609 
2610  $deleter = is_null( $deleter ) ? $wgUser : $deleter;
2611  if ( !Hooks::run( 'ArticleDelete',
2612  [ &$wikiPage, &$deleter, &$reason, &$error, &$status, $suppress ]
2613  ) ) {
2614  if ( $status->isOK() ) {
2615  // Hook aborted but didn't set a fatal status
2616  $status->fatal( 'delete-hook-aborted' );
2617  }
2618  return $status;
2619  }
2620 
2621  return $this->doDeleteArticleBatched( $reason, $suppress, $deleter, $tags,
2622  $logsubtype, $immediate );
2623  }
2624 
2633  public function doDeleteArticleBatched(
2634  $reason, $suppress, User $deleter, $tags,
2635  $logsubtype, $immediate = false, $webRequestId = null
2636  ) {
2637  wfDebug( __METHOD__ . "\n" );
2638 
2640 
2641  $dbw = wfGetDB( DB_MASTER );
2642  $dbw->startAtomic( __METHOD__ );
2643 
2644  $this->loadPageData( self::READ_LATEST );
2645  $id = $this->getId();
2646  // T98706: lock the page from various other updates but avoid using
2647  // WikiPage::READ_LOCKING as that will carry over the FOR UPDATE to
2648  // the revisions queries (which also JOIN on user). Only lock the page
2649  // row and CAS check on page_latest to see if the trx snapshot matches.
2650  $lockedLatest = $this->lockAndGetLatest();
2651  if ( $id == 0 || $this->getLatest() != $lockedLatest ) {
2652  $dbw->endAtomic( __METHOD__ );
2653  // Page not there or trx snapshot is stale
2654  $status->error( 'cannotdelete',
2655  wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
2656  return $status;
2657  }
2658 
2659  // At this point we are now committed to returning an OK
2660  // status unless some DB query error or other exception comes up.
2661  // This way callers don't have to call rollback() if $status is bad
2662  // unless they actually try to catch exceptions (which is rare).
2663 
2664  // we need to remember the old content so we can use it to generate all deletion updates.
2665  $revision = $this->getRevision();
2666  try {
2667  $content = $this->getContent( Revision::RAW );
2668  } catch ( Exception $ex ) {
2669  wfLogWarning( __METHOD__ . ': failed to load content during deletion! '
2670  . $ex->getMessage() );
2671 
2672  $content = null;
2673  }
2674 
2675  // Archive revisions. In immediate mode, archive all revisions. Otherwise, archive
2676  // one batch of revisions and defer archival of any others to the job queue.
2677  $explictTrxLogged = false;
2678  while ( true ) {
2679  $done = $this->archiveRevisions( $dbw, $id, $suppress );
2680  if ( $done || !$immediate ) {
2681  break;
2682  }
2683  $dbw->endAtomic( __METHOD__ );
2684  if ( $dbw->explicitTrxActive() ) {
2685  // Explict transactions may never happen here in practice. Log to be sure.
2686  if ( !$explictTrxLogged ) {
2687  $explictTrxLogged = true;
2688  LoggerFactory::getInstance( 'wfDebug' )->debug(
2689  'explicit transaction active in ' . __METHOD__ . ' while deleting {title}', [
2690  'title' => $this->getTitle()->getText(),
2691  ] );
2692  }
2693  continue;
2694  }
2695  if ( $dbw->trxLevel() ) {
2696  $dbw->commit();
2697  }
2698  $lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
2699  $lbFactory->waitForReplication();
2700  $dbw->startAtomic( __METHOD__ );
2701  }
2702 
2703  // If done archiving, also delete the article.
2704  if ( !$done ) {
2705  $dbw->endAtomic( __METHOD__ );
2706 
2707  $jobParams = [
2708  'wikiPageId' => $id,
2709  'requestId' => $webRequestId ?? WebRequest::getRequestId(),
2710  'reason' => $reason,
2711  'suppress' => $suppress,
2712  'userId' => $deleter->getId(),
2713  'tags' => json_encode( $tags ),
2714  'logsubtype' => $logsubtype,
2715  ];
2716 
2717  $job = new DeletePageJob( $this->getTitle(), $jobParams );
2718  JobQueueGroup::singleton()->push( $job );
2719 
2720  $status->warning( 'delete-scheduled',
2721  wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
2722  } else {
2723  // Get archivedRevisionCount by db query, because there's no better alternative.
2724  // Jobs cannot pass a count of archived revisions to the next job, because additional
2725  // deletion operations can be started while the first is running. Jobs from each
2726  // gracefully interleave, but would not know about each other's count. Deduplication
2727  // in the job queue to avoid simultaneous deletion operations would add overhead.
2728  // Number of archived revisions cannot be known beforehand, because edits can be made
2729  // while deletion operations are being processed, changing the number of archivals.
2730  $archivedRevisionCount = $dbw->selectField(
2731  'archive', 'COUNT(*)',
2732  [
2733  'ar_namespace' => $this->getTitle()->getNamespace(),
2734  'ar_title' => $this->getTitle()->getDBkey(),
2735  'ar_page_id' => $id
2736  ], __METHOD__
2737  );
2738 
2739  // Clone the title and wikiPage, so we have the information we need when
2740  // we log and run the ArticleDeleteComplete hook.
2741  $logTitle = clone $this->mTitle;
2742  $wikiPageBeforeDelete = clone $this;
2743 
2744  // Now that it's safely backed up, delete it
2745  $dbw->delete( 'page', [ 'page_id' => $id ], __METHOD__ );
2746 
2747  // Log the deletion, if the page was suppressed, put it in the suppression log instead
2748  $logtype = $suppress ? 'suppress' : 'delete';
2749 
2750  $logEntry = new ManualLogEntry( $logtype, $logsubtype );
2751  $logEntry->setPerformer( $deleter );
2752  $logEntry->setTarget( $logTitle );
2753  $logEntry->setComment( $reason );
2754  $logEntry->setTags( $tags );
2755  $logid = $logEntry->insert();
2756 
2757  $dbw->onTransactionPreCommitOrIdle(
2758  function () use ( $logEntry, $logid ) {
2759  // T58776: avoid deadlocks (especially from FileDeleteForm)
2760  $logEntry->publish( $logid );
2761  },
2762  __METHOD__
2763  );
2764 
2765  $dbw->endAtomic( __METHOD__ );
2766 
2767  $this->doDeleteUpdates( $id, $content, $revision, $deleter );
2768 
2769  Hooks::run( 'ArticleDeleteComplete', [
2770  &$wikiPageBeforeDelete,
2771  &$deleter,
2772  $reason,
2773  $id,
2774  $content,
2775  $logEntry,
2776  $archivedRevisionCount
2777  ] );
2778  $status->value = $logid;
2779 
2780  // Show log excerpt on 404 pages rather than just a link
2781  $cache = MediaWikiServices::getInstance()->getMainObjectStash();
2782  $key = $cache->makeKey( 'page-recent-delete', md5( $logTitle->getPrefixedText() ) );
2783  $cache->set( $key, 1, $cache::TTL_DAY );
2784  }
2785 
2786  return $status;
2787  }
2788 
2798  protected function archiveRevisions( $dbw, $id, $suppress ) {
2802 
2803  // Given the lock above, we can be confident in the title and page ID values
2804  $namespace = $this->getTitle()->getNamespace();
2805  $dbKey = $this->getTitle()->getDBkey();
2806 
2807  $commentStore = CommentStore::getStore();
2808  $actorMigration = ActorMigration::newMigration();
2809 
2811  $bitfield = false;
2812 
2813  // Bitfields to further suppress the content
2814  if ( $suppress ) {
2815  $bitfield = Revision::SUPPRESSED_ALL;
2816  $revQuery['fields'] = array_diff( $revQuery['fields'], [ 'rev_deleted' ] );
2817  }
2818 
2819  // For now, shunt the revision data into the archive table.
2820  // Text is *not* removed from the text table; bulk storage
2821  // is left intact to avoid breaking block-compression or
2822  // immutable storage schemes.
2823  // In the future, we may keep revisions and mark them with
2824  // the rev_deleted field, which is reserved for this purpose.
2825 
2826  // Lock rows in `revision` and its temp tables, but not any others.
2827  // Note array_intersect() preserves keys from the first arg, and we're
2828  // assuming $revQuery has `revision` primary and isn't using subtables
2829  // for anything we care about.
2830  $dbw->lockForUpdate(
2831  array_intersect(
2832  $revQuery['tables'],
2833  [ 'revision', 'revision_comment_temp', 'revision_actor_temp' ]
2834  ),
2835  [ 'rev_page' => $id ],
2836  __METHOD__,
2837  [],
2838  $revQuery['joins']
2839  );
2840 
2841  // If SCHEMA_COMPAT_WRITE_OLD is set, also select all extra fields we still write,
2842  // so we can copy it to the archive table.
2843  // We know the fields exist, otherwise SCHEMA_COMPAT_WRITE_OLD could not function.
2845  $revQuery['fields'][] = 'rev_text_id';
2846 
2847  if ( $wgContentHandlerUseDB ) {
2848  $revQuery['fields'][] = 'rev_content_model';
2849  $revQuery['fields'][] = 'rev_content_format';
2850  }
2851  }
2852 
2853  // Get as many of the page revisions as we are allowed to. The +1 lets us recognize the
2854  // unusual case where there were exactly $wgDeleteRevisionBatchSize revisions remaining.
2855  $res = $dbw->select(
2856  $revQuery['tables'],
2857  $revQuery['fields'],
2858  [ 'rev_page' => $id ],
2859  __METHOD__,
2860  [ 'ORDER BY' => 'rev_timestamp ASC', 'LIMIT' => $wgDeleteRevisionsBatchSize + 1 ],
2861  $revQuery['joins']
2862  );
2863 
2864  // Build their equivalent archive rows
2865  $rowsInsert = [];
2866  $revids = [];
2867 
2869  $ipRevIds = [];
2870 
2871  $done = true;
2872  foreach ( $res as $row ) {
2873  if ( count( $revids ) >= $wgDeleteRevisionsBatchSize ) {
2874  $done = false;
2875  break;
2876  }
2877 
2878  $comment = $commentStore->getComment( 'rev_comment', $row );
2879  $user = User::newFromAnyId( $row->rev_user, $row->rev_user_text, $row->rev_actor );
2880  $rowInsert = [
2881  'ar_namespace' => $namespace,
2882  'ar_title' => $dbKey,
2883  'ar_timestamp' => $row->rev_timestamp,
2884  'ar_minor_edit' => $row->rev_minor_edit,
2885  'ar_rev_id' => $row->rev_id,
2886  'ar_parent_id' => $row->rev_parent_id,
2895  'ar_len' => $row->rev_len,
2896  'ar_page_id' => $id,
2897  'ar_deleted' => $suppress ? $bitfield : $row->rev_deleted,
2898  'ar_sha1' => $row->rev_sha1,
2899  ] + $commentStore->insert( $dbw, 'ar_comment', $comment )
2900  + $actorMigration->getInsertValues( $dbw, 'ar_user', $user );
2901 
2903  $rowInsert['ar_text_id'] = $row->rev_text_id;
2904 
2905  if ( $wgContentHandlerUseDB ) {
2906  $rowInsert['ar_content_model'] = $row->rev_content_model;
2907  $rowInsert['ar_content_format'] = $row->rev_content_format;
2908  }
2909  }
2910 
2911  $rowsInsert[] = $rowInsert;
2912  $revids[] = $row->rev_id;
2913 
2914  // Keep track of IP edits, so that the corresponding rows can
2915  // be deleted in the ip_changes table.
2916  if ( (int)$row->rev_user === 0 && IP::isValid( $row->rev_user_text ) ) {
2917  $ipRevIds[] = $row->rev_id;
2918  }
2919  }
2920 
2921  // This conditional is just a sanity check
2922  if ( count( $revids ) > 0 ) {
2923  // Copy them into the archive table
2924  $dbw->insert( 'archive', $rowsInsert, __METHOD__ );
2925 
2926  $dbw->delete( 'revision', [ 'rev_id' => $revids ], __METHOD__ );
2928  $dbw->delete( 'revision_comment_temp', [ 'revcomment_rev' => $revids ], __METHOD__ );
2929  }
2931  $dbw->delete( 'revision_actor_temp', [ 'revactor_rev' => $revids ], __METHOD__ );
2932  }
2933 
2934  // Also delete records from ip_changes as applicable.
2935  if ( count( $ipRevIds ) > 0 ) {
2936  $dbw->delete( 'ip_changes', [ 'ipc_rev_id' => $ipRevIds ], __METHOD__ );
2937  }
2938  }
2939 
2940  return $done;
2941  }
2942 
2949  public function lockAndGetLatest() {
2950  return (int)wfGetDB( DB_MASTER )->selectField(
2951  'page',
2952  'page_latest',
2953  [
2954  'page_id' => $this->getId(),
2955  // Typically page_id is enough, but some code might try to do
2956  // updates assuming the title is the same, so verify that
2957  'page_namespace' => $this->getTitle()->getNamespace(),
2958  'page_title' => $this->getTitle()->getDBkey()
2959  ],
2960  __METHOD__,
2961  [ 'FOR UPDATE' ]
2962  );
2963  }
2964 
2977  public function doDeleteUpdates(
2978  $id, Content $content = null, Revision $revision = null, User $user = null
2979  ) {
2980  if ( $id !== $this->getId() ) {
2981  throw new InvalidArgumentException( 'Mismatching page ID' );
2982  }
2983 
2984  try {
2985  $countable = $this->isCountable();
2986  } catch ( Exception $ex ) {
2987  // fallback for deleting broken pages for which we cannot load the content for
2988  // some reason. Note that doDeleteArticleReal() already logged this problem.
2989  $countable = false;
2990  }
2991 
2992  // Update site status
2994  [ 'edits' => 1, 'articles' => -$countable, 'pages' => -1 ]
2995  ) );
2996 
2997  // Delete pagelinks, update secondary indexes, etc
2998  $updates = $this->getDeletionUpdates(
2999  $revision ? $revision->getRevisionRecord() : $content
3000  );
3001  foreach ( $updates as $update ) {
3002  DeferredUpdates::addUpdate( $update );
3003  }
3004 
3005  $causeAgent = $user ? $user->getName() : 'unknown';
3006  // Reparse any pages transcluding this page
3008  $this->mTitle, 'templatelinks', 'delete-page', $causeAgent );
3009  // Reparse any pages including this image
3010  if ( $this->mTitle->getNamespace() == NS_FILE ) {
3012  $this->mTitle, 'imagelinks', 'delete-page', $causeAgent );
3013  }
3014 
3015  // Clear caches
3016  self::onArticleDelete( $this->mTitle );
3018  $this->mTitle, $revision, null, wfWikiID()
3019  );
3020 
3021  // Reset this object and the Title object
3022  $this->loadFromRow( false, self::READ_LATEST );
3023 
3024  // Search engine
3025  DeferredUpdates::addUpdate( new SearchUpdate( $id, $this->mTitle ) );
3026  }
3027 
3057  public function doRollback(
3058  $fromP, $summary, $token, $bot, &$resultDetails, User $user, $tags = null
3059  ) {
3060  $resultDetails = null;
3061 
3062  // Check permissions
3063  $editErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $user );
3064  $rollbackErrors = $this->mTitle->getUserPermissionsErrors( 'rollback', $user );
3065  $errors = array_merge( $editErrors, wfArrayDiff2( $rollbackErrors, $editErrors ) );
3066 
3067  if ( !$user->matchEditToken( $token, 'rollback' ) ) {
3068  $errors[] = [ 'sessionfailure' ];
3069  }
3070 
3071  if ( $user->pingLimiter( 'rollback' ) || $user->pingLimiter() ) {
3072  $errors[] = [ 'actionthrottledtext' ];
3073  }
3074 
3075  // If there were errors, bail out now
3076  if ( !empty( $errors ) ) {
3077  return $errors;
3078  }
3079 
3080  return $this->commitRollback( $fromP, $summary, $bot, $resultDetails, $user, $tags );
3081  }
3082 
3103  public function commitRollback( $fromP, $summary, $bot,
3104  &$resultDetails, User $guser, $tags = null
3105  ) {
3106  global $wgUseRCPatrol;
3107 
3108  $dbw = wfGetDB( DB_MASTER );
3109 
3110  if ( wfReadOnly() ) {
3111  return [ [ 'readonlytext' ] ];
3112  }
3113 
3114  // Begin revision creation cycle by creating a PageUpdater.
3115  // If the page is changed concurrently after grabParentRevision(), the rollback will fail.
3116  $updater = $this->newPageUpdater( $guser );
3117  $current = $updater->grabParentRevision();
3118 
3119  if ( is_null( $current ) ) {
3120  // Something wrong... no page?
3121  return [ [ 'notanarticle' ] ];
3122  }
3123 
3124  $currentEditorForPublic = $current->getUser( RevisionRecord::FOR_PUBLIC );
3125  $legacyCurrent = new Revision( $current );
3126  $from = str_replace( '_', ' ', $fromP );
3127 
3128  // User name given should match up with the top revision.
3129  // If the revision's user is not visible, then $from should be empty.
3130  if ( $from !== ( $currentEditorForPublic ? $currentEditorForPublic->getName() : '' ) ) {
3131  $resultDetails = [ 'current' => $legacyCurrent ];
3132  return [ [ 'alreadyrolled',
3133  htmlspecialchars( $this->mTitle->getPrefixedText() ),
3134  htmlspecialchars( $fromP ),
3135  htmlspecialchars( $currentEditorForPublic ? $currentEditorForPublic->getName() : '' )
3136  ] ];
3137  }
3138 
3139  // Get the last edit not by this person...
3140  // Note: these may not be public values
3141  $actorWhere = ActorMigration::newMigration()->getWhere(
3142  $dbw,
3143  'rev_user',
3144  $current->getUser( RevisionRecord::RAW )
3145  );
3146 
3147  $s = $dbw->selectRow(
3148  [ 'revision' ] + $actorWhere['tables'],
3149  [ 'rev_id', 'rev_timestamp', 'rev_deleted' ],
3150  [
3151  'rev_page' => $current->getPageId(),
3152  'NOT(' . $actorWhere['conds'] . ')',
3153  ],
3154  __METHOD__,
3155  [
3156  'USE INDEX' => [ 'revision' => 'page_timestamp' ],
3157  'ORDER BY' => 'rev_timestamp DESC'
3158  ],
3159  $actorWhere['joins']
3160  );
3161  if ( $s === false ) {
3162  // No one else ever edited this page
3163  return [ [ 'cantrollback' ] ];
3164  } elseif ( $s->rev_deleted & RevisionRecord::DELETED_TEXT
3165  || $s->rev_deleted & RevisionRecord::DELETED_USER
3166  ) {
3167  // Only admins can see this text
3168  return [ [ 'notvisiblerev' ] ];
3169  }
3170 
3171  // Generate the edit summary if necessary
3172  $target = $this->getRevisionStore()->getRevisionById(
3173  $s->rev_id,
3174  RevisionStore::READ_LATEST
3175  );
3176  if ( empty( $summary ) ) {
3177  if ( !$currentEditorForPublic ) { // no public user name
3178  $summary = wfMessage( 'revertpage-nouser' );
3179  } else {
3180  $summary = wfMessage( 'revertpage' );
3181  }
3182  }
3183  $legacyTarget = new Revision( $target );
3184  $targetEditorForPublic = $target->getUser( RevisionRecord::FOR_PUBLIC );
3185 
3186  // Allow the custom summary to use the same args as the default message
3187  $contLang = MediaWikiServices::getInstance()->getContentLanguage();
3188  $args = [
3189  $targetEditorForPublic ? $targetEditorForPublic->getName() : null,
3190  $currentEditorForPublic ? $currentEditorForPublic->getName() : null,
3191  $s->rev_id,
3192  $contLang->timeanddate( wfTimestamp( TS_MW, $s->rev_timestamp ) ),
3193  $current->getId(),
3194  $contLang->timeanddate( $current->getTimestamp() )
3195  ];
3196  if ( $summary instanceof Message ) {
3197  $summary = $summary->params( $args )->inContentLanguage()->text();
3198  } else {
3199  $summary = wfMsgReplaceArgs( $summary, $args );
3200  }
3201 
3202  // Trim spaces on user supplied text
3203  $summary = trim( $summary );
3204 
3205  // Save
3206  $flags = EDIT_UPDATE | EDIT_INTERNAL;
3207 
3208  if ( $guser->isAllowed( 'minoredit' ) ) {
3209  $flags |= EDIT_MINOR;
3210  }
3211 
3212  if ( $bot && ( $guser->isAllowedAny( 'markbotedits', 'bot' ) ) ) {
3213  $flags |= EDIT_FORCE_BOT;
3214  }
3215 
3216  // TODO: MCR: also log model changes in other slots, in case that becomes possible!
3217  $currentContent = $current->getContent( SlotRecord::MAIN );
3218  $targetContent = $target->getContent( SlotRecord::MAIN );
3219  $changingContentModel = $targetContent->getModel() !== $currentContent->getModel();
3220 
3221  if ( in_array( 'mw-rollback', ChangeTags::getSoftwareTags() ) ) {
3222  $tags[] = 'mw-rollback';
3223  }
3224 
3225  // Build rollback revision:
3226  // Restore old content
3227  // TODO: MCR: test this once we can store multiple slots
3228  foreach ( $target->getSlots()->getSlots() as $slot ) {
3229  $updater->inheritSlot( $slot );
3230  }
3231 
3232  // Remove extra slots
3233  // TODO: MCR: test this once we can store multiple slots
3234  foreach ( $current->getSlotRoles() as $role ) {
3235  if ( !$target->hasSlot( $role ) ) {
3236  $updater->removeSlot( $role );
3237  }
3238  }
3239 
3240  $updater->setOriginalRevisionId( $target->getId() );
3241  // Do not call setUndidRevisionId(), that causes an extra "mw-undo" tag to be added (T190374)
3242  $updater->addTags( $tags );
3243 
3244  // TODO: this logic should not be in the storage layer, it's here for compatibility
3245  // with 1.31 behavior. Applying the 'autopatrol' right should be done in the same
3246  // place the 'bot' right is handled, which is currently in EditPage::attemptSave.
3247  if ( $wgUseRCPatrol && $this->getTitle()->userCan( 'autopatrol', $guser ) ) {
3248  $updater->setRcPatrolStatus( RecentChange::PRC_AUTOPATROLLED );
3249  }
3250 
3251  // Actually store the rollback
3252  $rev = $updater->saveRevision(
3254  $flags
3255  );
3256 
3257  // Set patrolling and bot flag on the edits, which gets rollbacked.
3258  // This is done even on edit failure to have patrolling in that case (T64157).
3259  $set = [];
3260  if ( $bot && $guser->isAllowed( 'markbotedits' ) ) {
3261  // Mark all reverted edits as bot
3262  $set['rc_bot'] = 1;
3263  }
3264 
3265  if ( $wgUseRCPatrol ) {
3266  // Mark all reverted edits as patrolled
3267  $set['rc_patrolled'] = RecentChange::PRC_PATROLLED;
3268  }
3269 
3270  if ( count( $set ) ) {
3271  $actorWhere = ActorMigration::newMigration()->getWhere(
3272  $dbw,
3273  'rc_user',
3274  $current->getUser( RevisionRecord::RAW ),
3275  false
3276  );
3277  $dbw->update( 'recentchanges', $set,
3278  [ /* WHERE */
3279  'rc_cur_id' => $current->getPageId(),
3280  'rc_timestamp > ' . $dbw->addQuotes( $s->rev_timestamp ),
3281  $actorWhere['conds'], // No tables/joins are needed for rc_user
3282  ],
3283  __METHOD__
3284  );
3285  }
3286 
3287  if ( !$updater->wasSuccessful() ) {
3288  return $updater->getStatus()->getErrorsArray();
3289  }
3290 
3291  // Report if the edit was not created because it did not change the content.
3292  if ( $updater->isUnchanged() ) {
3293  $resultDetails = [ 'current' => $legacyCurrent ];
3294  return [ [ 'alreadyrolled',
3295  htmlspecialchars( $this->mTitle->getPrefixedText() ),
3296  htmlspecialchars( $fromP ),
3297  htmlspecialchars( $targetEditorForPublic ? $targetEditorForPublic->getName() : '' )
3298  ] ];
3299  }
3300 
3301  if ( $changingContentModel ) {
3302  // If the content model changed during the rollback,
3303  // make sure it gets logged to Special:Log/contentmodel
3304  $log = new ManualLogEntry( 'contentmodel', 'change' );
3305  $log->setPerformer( $guser );
3306  $log->setTarget( $this->mTitle );
3307  $log->setComment( $summary );
3308  $log->setParameters( [
3309  '4::oldmodel' => $currentContent->getModel(),
3310  '5::newmodel' => $targetContent->getModel(),
3311  ] );
3312 
3313  $logId = $log->insert( $dbw );
3314  $log->publish( $logId );
3315  }
3316 
3317  $revId = $rev->getId();
3318 
3319  Hooks::run( 'ArticleRollbackComplete', [ $this, $guser, $legacyTarget, $legacyCurrent ] );
3320 
3321  $resultDetails = [
3322  'summary' => $summary,
3323  'current' => $legacyCurrent,
3324  'target' => $legacyTarget,
3325  'newid' => $revId,
3326  'tags' => $tags
3327  ];
3328 
3329  // TODO: make this return a Status object and wrap $resultDetails in that.
3330  return [];
3331  }
3332 
3344  public static function onArticleCreate( Title $title ) {
3345  // TODO: move this into a PageEventEmitter service
3346 
3347  // Update existence markers on article/talk tabs...
3348  $other = $title->getOtherPage();
3349 
3350  $other->purgeSquid();
3351 
3352  $title->touchLinks();
3353  $title->purgeSquid();
3354  $title->deleteTitleProtection();
3355 
3356  MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title );
3357 
3358  // Invalidate caches of articles which include this page
3360  new HTMLCacheUpdate( $title, 'templatelinks', 'page-create' )
3361  );
3362 
3363  if ( $title->getNamespace() == NS_CATEGORY ) {
3364  // Load the Category object, which will schedule a job to create
3365  // the category table row if necessary. Checking a replica DB is ok
3366  // here, in the worst case it'll run an unnecessary recount job on
3367  // a category that probably doesn't have many members.
3368  Category::newFromTitle( $title )->getID();
3369  }
3370  }
3371 
3377  public static function onArticleDelete( Title $title ) {
3378  // TODO: move this into a PageEventEmitter service
3379 
3380  // Update existence markers on article/talk tabs...
3381  // Clear Backlink cache first so that purge jobs use more up-to-date backlink information
3382  BacklinkCache::get( $title )->clear();
3383  $other = $title->getOtherPage();
3384 
3385  $other->purgeSquid();
3386 
3387  $title->touchLinks();
3388  $title->purgeSquid();
3389 
3390  MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title );
3391 
3392  // File cache
3395 
3396  // Messages
3397  if ( $title->getNamespace() == NS_MEDIAWIKI ) {
3398  MessageCache::singleton()->updateMessageOverride( $title, null );
3399  }
3400 
3401  // Images
3402  if ( $title->getNamespace() == NS_FILE ) {
3404  new HTMLCacheUpdate( $title, 'imagelinks', 'page-delete' )
3405  );
3406  }
3407 
3408  // User talk pages
3409  if ( $title->getNamespace() == NS_USER_TALK ) {
3410  $user = User::newFromName( $title->getText(), false );
3411  if ( $user ) {
3412  $user->setNewtalk( false );
3413  }
3414  }
3415 
3416  // Image redirects
3417  RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $title );
3418 
3419  // Purge cross-wiki cache entities referencing this page
3421  }
3422 
3431  public static function onArticleEdit(
3432  Title $title,
3433  Revision $revision = null,
3434  $slotsChanged = null
3435  ) {
3436  // TODO: move this into a PageEventEmitter service
3437 
3438  if ( $slotsChanged === null || in_array( SlotRecord::MAIN, $slotsChanged ) ) {
3439  // Invalidate caches of articles which include this page.
3440  // Only for the main slot, because only the main slot is transcluded.
3441  // TODO: MCR: not true for TemplateStyles! [SlotHandler]
3443  new HTMLCacheUpdate( $title, 'templatelinks', 'page-edit' )
3444  );
3445  }
3446 
3447  // Invalidate the caches of all pages which redirect here
3449  new HTMLCacheUpdate( $title, 'redirect', 'page-edit' )
3450  );
3451 
3452  MediaWikiServices::getInstance()->getLinkCache()->invalidateTitle( $title );
3453 
3454  // Purge CDN for this page only
3455  $title->purgeSquid();
3456  // Clear file cache for this page only
3458 
3459  // Purge ?action=info cache
3460  $revid = $revision ? $revision->getId() : null;
3461  DeferredUpdates::addCallableUpdate( function () use ( $title, $revid ) {
3463  } );
3464 
3465  // Purge cross-wiki cache entities referencing this page
3467  }
3468 
3476  private static function purgeInterwikiCheckKey( Title $title ) {
3478 
3479  if ( !$wgEnableScaryTranscluding ) {
3480  return; // @todo: perhaps this wiki is only used as a *source* for content?
3481  }
3482 
3483  DeferredUpdates::addCallableUpdate( function () use ( $title ) {
3484  $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
3485  $cache->resetCheckKey(
3486  // Do not include the namespace since there can be multiple aliases to it
3487  // due to different namespace text definitions on different wikis. This only
3488  // means that some cache invalidations happen that are not strictly needed.
3489  $cache->makeGlobalKey( 'interwiki-page', wfWikiID(), $title->getDBkey() )
3490  );
3491  } );
3492  }
3493 
3500  public function getCategories() {
3501  $id = $this->getId();
3502  if ( $id == 0 ) {
3503  return TitleArray::newFromResult( new FakeResultWrapper( [] ) );
3504  }
3505 
3506  $dbr = wfGetDB( DB_REPLICA );
3507  $res = $dbr->select( 'categorylinks',
3508  [ 'cl_to AS page_title, ' . NS_CATEGORY . ' AS page_namespace' ],
3509  // Have to do that since Database::fieldNamesWithAlias treats numeric indexes
3510  // as not being aliases, and NS_CATEGORY is numeric
3511  [ 'cl_from' => $id ],
3512  __METHOD__ );
3513 
3514  return TitleArray::newFromResult( $res );
3515  }
3516 
3523  public function getHiddenCategories() {
3524  $result = [];
3525  $id = $this->getId();
3526 
3527  if ( $id == 0 ) {
3528  return [];
3529  }
3530 
3531  $dbr = wfGetDB( DB_REPLICA );
3532  $res = $dbr->select( [ 'categorylinks', 'page_props', 'page' ],
3533  [ 'cl_to' ],
3534  [ 'cl_from' => $id, 'pp_page=page_id', 'pp_propname' => 'hiddencat',
3535  'page_namespace' => NS_CATEGORY, 'page_title=cl_to' ],
3536  __METHOD__ );
3537 
3538  if ( $res !== false ) {
3539  foreach ( $res as $row ) {
3540  $result[] = Title::makeTitle( NS_CATEGORY, $row->cl_to );
3541  }
3542  }
3543 
3544  return $result;
3545  }
3546 
3554  public function getAutoDeleteReason( &$hasHistory ) {
3555  return $this->getContentHandler()->getAutoDeleteReason( $this->getTitle(), $hasHistory );
3556  }
3557 
3568  public function updateCategoryCounts( array $added, array $deleted, $id = 0 ) {
3569  $id = $id ?: $this->getId();
3570  $type = MWNamespace::getCategoryLinkType( $this->getTitle()->getNamespace() );
3571 
3572  $addFields = [ 'cat_pages = cat_pages + 1' ];
3573  $removeFields = [ 'cat_pages = cat_pages - 1' ];
3574  if ( $type !== 'page' ) {
3575  $addFields[] = "cat_{$type}s = cat_{$type}s + 1";
3576  $removeFields[] = "cat_{$type}s = cat_{$type}s - 1";
3577  }
3578 
3579  $dbw = wfGetDB( DB_MASTER );
3580 
3581  if ( count( $added ) ) {
3582  $existingAdded = $dbw->selectFieldValues(
3583  'category',
3584  'cat_title',
3585  [ 'cat_title' => $added ],
3586  __METHOD__
3587  );
3588 
3589  // For category rows that already exist, do a plain
3590  // UPDATE instead of INSERT...ON DUPLICATE KEY UPDATE
3591  // to avoid creating gaps in the cat_id sequence.
3592  if ( count( $existingAdded ) ) {
3593  $dbw->update(
3594  'category',
3595  $addFields,
3596  [ 'cat_title' => $existingAdded ],
3597  __METHOD__
3598  );
3599  }
3600 
3601  $missingAdded = array_diff( $added, $existingAdded );
3602  if ( count( $missingAdded ) ) {
3603  $insertRows = [];
3604  foreach ( $missingAdded as $cat ) {
3605  $insertRows[] = [
3606  'cat_title' => $cat,
3607  'cat_pages' => 1,
3608  'cat_subcats' => ( $type === 'subcat' ) ? 1 : 0,
3609  'cat_files' => ( $type === 'file' ) ? 1 : 0,
3610  ];
3611  }
3612  $dbw->upsert(
3613  'category',
3614  $insertRows,
3615  [ 'cat_title' ],
3616  $addFields,
3617  __METHOD__
3618  );
3619  }
3620  }
3621 
3622  if ( count( $deleted ) ) {
3623  $dbw->update(
3624  'category',
3625  $removeFields,
3626  [ 'cat_title' => $deleted ],
3627  __METHOD__
3628  );
3629  }
3630 
3631  foreach ( $added as $catName ) {
3632  $cat = Category::newFromName( $catName );
3633  Hooks::run( 'CategoryAfterPageAdded', [ $cat, $this ] );
3634  }
3635 
3636  foreach ( $deleted as $catName ) {
3637  $cat = Category::newFromName( $catName );
3638  Hooks::run( 'CategoryAfterPageRemoved', [ $cat, $this, $id ] );
3639  // Refresh counts on categories that should be empty now (after commit, T166757)
3640  DeferredUpdates::addCallableUpdate( function () use ( $cat ) {
3641  $cat->refreshCountsIfEmpty();
3642  } );
3643  }
3644  }
3645 
3652  public function triggerOpportunisticLinksUpdate( ParserOutput $parserOutput ) {
3653  if ( wfReadOnly() ) {
3654  return;
3655  }
3656 
3657  if ( !Hooks::run( 'OpportunisticLinksUpdate',
3658  [ $this, $this->mTitle, $parserOutput ]
3659  ) ) {
3660  return;
3661  }
3662 
3663  $config = RequestContext::getMain()->getConfig();
3664 
3665  $params = [
3666  'isOpportunistic' => true,
3667  'rootJobTimestamp' => $parserOutput->getCacheTime()
3668  ];
3669 
3670  if ( $this->mTitle->areRestrictionsCascading() ) {
3671  // If the page is cascade protecting, the links should really be up-to-date
3672  JobQueueGroup::singleton()->lazyPush(
3673  RefreshLinksJob::newPrioritized( $this->mTitle, $params )
3674  );
3675  } elseif ( !$config->get( 'MiserMode' ) && $parserOutput->hasDynamicContent() ) {
3676  // Assume the output contains "dynamic" time/random based magic words.
3677  // Only update pages that expired due to dynamic content and NOT due to edits
3678  // to referenced templates/files. When the cache expires due to dynamic content,
3679  // page_touched is unchanged. We want to avoid triggering redundant jobs due to
3680  // views of pages that were just purged via HTMLCacheUpdateJob. In that case, the
3681  // template/file edit already triggered recursive RefreshLinksJob jobs.
3682  if ( $this->getLinksTimestamp() > $this->getTouched() ) {
3683  // If a page is uncacheable, do not keep spamming a job for it.
3684  // Although it would be de-duplicated, it would still waste I/O.
3686  $key = $cache->makeKey( 'dynamic-linksupdate', 'last', $this->getId() );
3687  $ttl = max( $parserOutput->getCacheExpiry(), 3600 );
3688  if ( $cache->add( $key, time(), $ttl ) ) {
3689  JobQueueGroup::singleton()->lazyPush(
3690  RefreshLinksJob::newDynamic( $this->mTitle, $params )
3691  );
3692  }
3693  }
3694  }
3695  }
3696 
3706  public function getDeletionUpdates( $rev = null ) {
3707  if ( !$rev ) {
3708  wfDeprecated( __METHOD__ . ' without a RevisionRecord', '1.32' );
3709 
3710  try {
3711  $rev = $this->getRevisionRecord();
3712  } catch ( Exception $ex ) {
3713  // If we can't load the content, something is wrong. Perhaps that's why
3714  // the user is trying to delete the page, so let's not fail in that case.
3715  // Note that doDeleteArticleReal() will already have logged an issue with
3716  // loading the content.
3717  wfDebug( __METHOD__ . ' failed to load current revision of page ' . $this->getId() );
3718  }
3719  }
3720 
3721  if ( !$rev ) {
3722  $slotContent = [];
3723  } elseif ( $rev instanceof Content ) {
3724  wfDeprecated( __METHOD__ . ' with a Content object instead of a RevisionRecord', '1.32' );
3725 
3726  $slotContent = [ SlotRecord::MAIN => $rev ];
3727  } else {
3728  $slotContent = array_map( function ( SlotRecord $slot ) {
3729  return $slot->getContent( Revision::RAW );
3730  }, $rev->getSlots()->getSlots() );
3731  }
3732 
3733  $allUpdates = [ new LinksDeletionUpdate( $this ) ];
3734 
3735  // NOTE: once Content::getDeletionUpdates() is removed, we only need to content
3736  // model here, not the content object!
3737  // TODO: consolidate with similar logic in DerivedPageDataUpdater::getSecondaryDataUpdates()
3739  foreach ( $slotContent as $role => $content ) {
3740  $handler = $content->getContentHandler();
3741 
3742  $updates = $handler->getDeletionUpdates(
3743  $this->getTitle(),
3744  $role
3745  );
3746  $allUpdates = array_merge( $allUpdates, $updates );
3747 
3748  // TODO: remove B/C hack in 1.32!
3749  $legacyUpdates = $content->getDeletionUpdates( $this );
3750 
3751  // HACK: filter out redundant and incomplete LinksDeletionUpdate
3752  $legacyUpdates = array_filter( $legacyUpdates, function ( $update ) {
3753  return !( $update instanceof LinksDeletionUpdate );
3754  } );
3755 
3756  $allUpdates = array_merge( $allUpdates, $legacyUpdates );
3757  }
3758 
3759  Hooks::run( 'PageDeletionDataUpdates', [ $this->getTitle(), $rev, &$allUpdates ] );
3760 
3761  // TODO: hard deprecate old hook in 1.33
3762  Hooks::run( 'WikiPageDeletionUpdates', [ $this, $content, &$allUpdates ] );
3763  return $allUpdates;
3764  }
3765 
3773  public function isLocal() {
3774  return true;
3775  }
3776 
3786  public function getWikiDisplayName() {
3787  global $wgSitename;
3788  return $wgSitename;
3789  }
3790 
3799  public function getSourceURL() {
3800  return $this->getTitle()->getCanonicalURL();
3801  }
3802 
3809  $linkCache = MediaWikiServices::getInstance()->getLinkCache();
3810 
3811  return $linkCache->getMutableCacheKeys( $cache, $this->getTitle() );
3812  }
3813 
3814 }
WikiPage\getCategories
getCategories()
Returns a list of categories this page is a member of.
Definition: WikiPage.php:3500
Revision\FOR_PUBLIC
const FOR_PUBLIC
Definition: Revision.php:55
$status
Status::newGood()` to allow deletion, and then `return false` from the hook function. Ensure you consume the 'ChangeTagAfterDelete' hook to carry out custom deletion actions. $tag:name of the tag $user:user initiating the action & $status:Status object. See above. 'ChangeTagsListActive':Allows you to nominate which of the tags your extension uses are in active use. & $tags:list of all active tags. Append to this array. 'ChangeTagsAfterUpdateTags':Called after tags have been updated with the ChangeTags::updateTags function. Params:$addedTags:tags effectively added in the update $removedTags:tags effectively removed in the update $prevTags:tags that were present prior to the update $rc_id:recentchanges table id $rev_id:revision table id $log_id:logging table id $params:tag params $rc:RecentChange being tagged when the tagging accompanies the action, or null $user:User who performed the tagging when the tagging is subsequent to the action, or null 'ChangeTagsAllowedAdd':Called when checking if a user can add tags to a change. & $allowedTags:List of all the tags the user is allowed to add. Any tags the user wants to add( $addTags) that are not in this array will cause it to fail. You may add or remove tags to this array as required. $addTags:List of tags user intends to add. $user:User who is adding the tags. 'ChangeUserGroups':Called before user groups are changed. $performer:The User who will perform the change $user:The User whose groups will be changed & $add:The groups that will be added & $remove:The groups that will be removed 'Collation::factory':Called if $wgCategoryCollation is an unknown collation. $collationName:Name of the collation in question & $collationObject:Null. Replace with a subclass of the Collation class that implements the collation given in $collationName. 'ConfirmEmailComplete':Called after a user 's email has been confirmed successfully. $user:user(object) whose email is being confirmed 'ContentAlterParserOutput':Modify parser output for a given content object. Called by Content::getParserOutput after parsing has finished. Can be used for changes that depend on the result of the parsing but have to be done before LinksUpdate is called(such as adding tracking categories based on the rendered HTML). $content:The Content to render $title:Title of the page, as context $parserOutput:ParserOutput to manipulate 'ContentGetParserOutput':Customize parser output for a given content object, called by AbstractContent::getParserOutput. May be used to override the normal model-specific rendering of page content. $content:The Content to render $title:Title of the page, as context $revId:The revision ID, as context $options:ParserOptions for rendering. To avoid confusing the parser cache, the output can only depend on parameters provided to this hook function, not on global state. $generateHtml:boolean, indicating whether full HTML should be generated. If false, generation of HTML may be skipped, but other information should still be present in the ParserOutput object. & $output:ParserOutput, to manipulate or replace 'ContentHandlerDefaultModelFor':Called when the default content model is determined for a given title. May be used to assign a different model for that title. $title:the Title in question & $model:the model name. Use with CONTENT_MODEL_XXX constants. 'ContentHandlerForModelID':Called when a ContentHandler is requested for a given content model name, but no entry for that model exists in $wgContentHandlers. Note:if your extension implements additional models via this hook, please use GetContentModels hook to make them known to core. $modeName:the requested content model name & $handler:set this to a ContentHandler object, if desired. 'ContentModelCanBeUsedOn':Called to determine whether that content model can be used on a given page. This is especially useful to prevent some content models to be used in some special location. $contentModel:ID of the content model in question $title:the Title in question. & $ok:Output parameter, whether it is OK to use $contentModel on $title. Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok. 'ContribsPager::getQueryInfo':Before the contributions query is about to run & $pager:Pager object for contributions & $queryInfo:The query for the contribs Pager 'ContribsPager::reallyDoQuery':Called before really executing the query for My Contributions & $data:an array of results of all contribs queries $pager:The ContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'ContributionsLineEnding':Called before a contributions HTML line is finished $page:SpecialPage object for contributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'ContributionsToolLinks':Change tool links above Special:Contributions $id:User identifier $title:User page title & $tools:Array of tool links $specialPage:SpecialPage instance for context and services. Can be either SpecialContributions or DeletedContributionsPage. Extensions should type hint against a generic SpecialPage though. 'ConvertContent':Called by AbstractContent::convert when a conversion to another content model is requested. Handler functions that modify $result should generally return false to disable further attempts at conversion. $content:The Content object to be converted. $toModel:The ID of the content model to convert to. $lossy:boolean indicating whether lossy conversion is allowed. & $result:Output parameter, in case the handler function wants to provide a converted Content object. Note that $result->getContentModel() must return $toModel. 'ContentSecurityPolicyDefaultSource':Modify the allowed CSP load sources. This affects all directives except for the script directive. If you want to add a script source, see ContentSecurityPolicyScriptSource hook. & $defaultSrc:Array of Content-Security-Policy allowed sources $policyConfig:Current configuration for the Content-Security-Policy header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyDirectives':Modify the content security policy directives. Use this only if ContentSecurityPolicyDefaultSource and ContentSecurityPolicyScriptSource do not meet your needs. & $directives:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyScriptSource':Modify the allowed CSP script sources. Note that you also have to use ContentSecurityPolicyDefaultSource if you want non-script sources to be loaded from whatever you add. & $scriptSrc:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'CustomEditor':When invoking the page editor Return true to allow the normal editor to be used, or false if implementing a custom editor, e.g. for a special namespace, etc. $article:Article being edited $user:User performing the edit 'DatabaseOraclePostInit':Called after initialising an Oracle database $db:the DatabaseOracle object 'DeletedContribsPager::reallyDoQuery':Called before really executing the query for Special:DeletedContributions Similar to ContribsPager::reallyDoQuery & $data:an array of results of all contribs queries $pager:The DeletedContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'DeletedContributionsLineEnding':Called before a DeletedContributions HTML line is finished. Similar to ContributionsLineEnding $page:SpecialPage object for DeletedContributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'DeleteUnknownPreferences':Called by the cleanupPreferences.php maintenance script to build a WHERE clause with which to delete preferences that are not known about. This hook is used by extensions that have dynamically-named preferences that should not be deleted in the usual cleanup process. For example, the Gadgets extension creates preferences prefixed with 'gadget-', and so anything with that prefix is excluded from the deletion. &where:An array that will be passed as the $cond parameter to IDatabase::select() to determine what will be deleted from the user_properties table. $db:The IDatabase object, useful for accessing $db->buildLike() etc. 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function. $differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable 's value is null. This hook can be used to inject content into said class member variable. $differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the "mark as patrolled" link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink". $differenceEngine:DifferenceEngine object & $markAsPatrolledLink:The "mark as patrolled" link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions. & $rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision 's author, whether the revision was marked as a minor edit or not, etc. $differenceEngine:DifferenceEngine object & $newHeader:The string containing the various #mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
Definition: hooks.txt:1305
ParserOptions
Set options of the Parser.
Definition: ParserOptions.php:42
$wgUseAutomaticEditSummaries
$wgUseAutomaticEditSummaries
If user doesn't specify any edit summary when making a an edit, MediaWiki will try to automatically c...
Definition: DefaultSettings.php:6711
Revision\getTimestamp
getTimestamp()
Definition: Revision.php:1003
ContentHandler\getForModelID
static getForModelID( $modelId)
Returns the ContentHandler singleton for the given model ID.
Definition: ContentHandler.php:297
CommentStoreComment\newUnsavedComment
static newUnsavedComment( $comment, array $data=null)
Create a new, unsaved CommentStoreComment.
Definition: CommentStoreComment.php:66
$user
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 account $user
Definition: hooks.txt:244
Page
Interface for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
Definition: Page.php:24
CacheTime\getCacheExpiry
getCacheExpiry()
Returns the number of seconds after which this object should expire.
Definition: CacheTime.php:129
User\newFromId
static newFromId( $id)
Static factory method for creation from a given user ID.
Definition: User.php:615
Revision\newKnownCurrent
static newKnownCurrent(IDatabase $db, $pageIdOrTitle, $revId=0)
Load a revision based on a known page ID and current revision ID from the DB.
Definition: Revision.php:1299
WikiPage\onArticleCreate
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:3344
Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:45
WikiPage\loadPageData
loadPageData( $from='fromdb')
Load the object from a given source by title.
Definition: WikiPage.php:467
RepoGroup\singleton
static singleton()
Get a RepoGroup instance.
Definition: RepoGroup.php:59
Revision\SUPPRESSED_ALL
const SUPPRESSED_ALL
Definition: Revision.php:52
WikiPage\getAutoDeleteReason
getAutoDeleteReason(&$hasHistory)
Auto-generates a deletion reason.
Definition: WikiPage.php:3554
WikiPage\getRevisionRecord
getRevisionRecord()
Get the latest revision.
Definition: WikiPage.php:777
ParserOutput
Definition: ParserOutput.php:25
false
processing should stop and the error should be shown to the user * false
Definition: hooks.txt:187
Revision\SlotRecord\getContent
getContent()
Returns the Content of the given slot.
Definition: SlotRecord.php:304
WikiPage\getRedirectTarget
getRedirectTarget()
If this page is a redirect, get its target.
Definition: WikiPage.php:971
ObjectCache\getLocalClusterInstance
static getLocalClusterInstance()
Get the main cluster-local cache object.
Definition: ObjectCache.php:365
User\getId
getId()
Get the user's ID.
Definition: User.php:2438
Revision\newFromId
static newFromId( $id, $flags=0)
Load a page revision from a given revision ID number.
Definition: Revision.php:114
WikiPage\clearCacheFields
clearCacheFields()
Clear the object cache fields.
Definition: WikiPage.php:295
Title\getFragment
getFragment()
Get the Title fragment (i.e.
Definition: Title.php:1587
WikiPage\isBatchedDelete
isBatchedDelete( $safetyMargin=0)
Determines if deletion of this page would be batched (executed over time by the job queue) or not (co...
Definition: WikiPage.php:2535
$context
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:2675
$wgPageCreationLog
$wgPageCreationLog
Maintain a log of page creations at Special:Log/create?
Definition: DefaultSettings.php:7938
WikiPage\updateRevisionOn
updateRevisionOn( $dbw, $revision, $lastRevision=null, $lastRevIsRedirect=null)
Update the page record to point to a newly saved revision.
Definition: WikiPage.php:1348
WikiPage\wasLoadedFrom
wasLoadedFrom( $from)
Checks whether the page data was loaded using the given database access mode (or better).
Definition: WikiPage.php:512
TitleArray\newFromResult
static newFromResult( $res)
Definition: TitleArray.php:40
WikiPage\getUndoContent
getUndoContent(Revision $undo, Revision $undoafter)
Get the content that needs to be saved in order to undo all revisions between $undo and $undoafter.
Definition: WikiPage.php:1519
HTMLFileCache\clearFileCache
static clearFileCache(Title $title)
Clear the file caches for a page for all actions.
Definition: HTMLFileCache.php:233
WikiPage\getUser
getUser( $audience=Revision::FOR_PUBLIC, User $user=null)
Definition: WikiPage.php:836
EDIT_FORCE_BOT
const EDIT_FORCE_BOT
Definition: Defines.php:156
EDIT_INTERNAL
const EDIT_INTERNAL
Definition: Defines.php:159
MediaWiki\Storage\DerivedPageDataUpdater\setArticleCountMethod
setArticleCountMethod( $articleCountMethod)
Definition: DerivedPageDataUpdater.php:431
captcha-old.count
count
Definition: captcha-old.py:249
Revision\RevisionStore
Service for looking up page revisions.
Definition: RevisionStore.php:76
WikiPage\newPageUpdater
newPageUpdater(User $user, RevisionSlotsUpdate $forUpdate=null)
Returns a PageUpdater for creating new revisions on this page (or creating the page).
Definition: WikiPage.php:1762
$wgCommentTableSchemaMigrationStage
int $wgCommentTableSchemaMigrationStage
Comment table schema migration stage.
Definition: DefaultSettings.php:8967
WikiPage\hasViewableContent
hasViewableContent()
Check if this page is something we're going to be showing some sort of sensible content for.
Definition: WikiPage.php:603
WikiPage\getTouched
getTouched()
Get the page_touched field.
Definition: WikiPage.php:670
$result
The index of the header message $result[1]=The index of the body text message $result[2 through n]=Parameters passed to body text message. Please note the header message cannot receive/use parameters. 'ImportHandleLogItemXMLTag':When parsing a XML tag in a log item. Return false to stop further processing of the tag $reader:XMLReader object $logInfo:Array of information 'ImportHandlePageXMLTag':When parsing a XML tag in a page. Return false to stop further processing of the tag $reader:XMLReader object & $pageInfo:Array of information 'ImportHandleRevisionXMLTag':When parsing a XML tag in a page revision. Return false to stop further processing of the tag $reader:XMLReader object $pageInfo:Array of page information $revisionInfo:Array of revision information 'ImportHandleToplevelXMLTag':When parsing a top level XML tag. Return false to stop further processing of the tag $reader:XMLReader object 'ImportHandleUnknownUser':When a user doesn 't exist locally, this hook is called to give extensions an opportunity to auto-create it. If the auto-creation is successful, return false. $name:User name 'ImportHandleUploadXMLTag':When parsing a XML tag in a file upload. Return false to stop further processing of the tag $reader:XMLReader object $revisionInfo:Array of information 'ImportLogInterwikiLink':Hook to change the interwiki link used in log entries and edit summaries for transwiki imports. & $fullInterwikiPrefix:Interwiki prefix, may contain colons. & $pageTitle:String that contains page title. 'ImportSources':Called when reading from the $wgImportSources configuration variable. Can be used to lazy-load the import sources list. & $importSources:The value of $wgImportSources. Modify as necessary. See the comment in DefaultSettings.php for the detail of how to structure this array. 'InfoAction':When building information to display on the action=info page. $context:IContextSource object & $pageInfo:Array of information 'InitializeArticleMaybeRedirect':MediaWiki check to see if title is a redirect. & $title:Title object for the current page & $request:WebRequest & $ignoreRedirect:boolean to skip redirect check & $target:Title/string of redirect target & $article:Article object 'InternalParseBeforeLinks':during Parser 's internalParse method before links but after nowiki/noinclude/includeonly/onlyinclude and other processings. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InternalParseBeforeSanitize':during Parser 's internalParse method just before the parser removes unwanted/dangerous HTML tags and after nowiki/noinclude/includeonly/onlyinclude and other processings. Ideal for syntax-extensions after template/parser function execution which respect nowiki and HTML-comments. & $parser:Parser object & $text:string containing partially parsed text & $stripState:Parser 's internal StripState object 'InterwikiLoadPrefix':When resolving if a given prefix is an interwiki or not. Return true without providing an interwiki to continue interwiki search. $prefix:interwiki prefix we are looking for. & $iwData:output array describing the interwiki with keys iw_url, iw_local, iw_trans and optionally iw_api and iw_wikiid. 'InvalidateEmailComplete':Called after a user 's email has been invalidated successfully. $user:user(object) whose email is being invalidated 'IRCLineURL':When constructing the URL to use in an IRC notification. Callee may modify $url and $query, URL will be constructed as $url . $query & $url:URL to index.php & $query:Query string $rc:RecentChange object that triggered url generation 'IsFileCacheable':Override the result of Article::isFileCacheable()(if true) & $article:article(object) being checked 'IsTrustedProxy':Override the result of IP::isTrustedProxy() & $ip:IP being check & $result:Change this value to override the result of IP::isTrustedProxy() 'IsUploadAllowedFromUrl':Override the result of UploadFromUrl::isAllowedUrl() $url:URL used to upload from & $allowed:Boolean indicating if uploading is allowed for given URL 'isValidEmailAddr':Override the result of Sanitizer::validateEmail(), for instance to return false if the domain name doesn 't match your organization. $addr:The e-mail address entered by the user & $result:Set this and return false to override the internal checks 'isValidPassword':Override the result of User::isValidPassword() $password:The password entered by the user & $result:Set this and return false to override the internal checks $user:User the password is being validated for 'Language::getMessagesFileName':$code:The language code or the language we 're looking for a messages file for & $file:The messages file path, you can override this to change the location. 'LanguageGetMagic':DEPRECATED since 1.16! Use $magicWords in a file listed in $wgExtensionMessagesFiles instead. Use this to define synonyms of magic words depending of the language & $magicExtensions:associative array of magic words synonyms $lang:language code(string) 'LanguageGetNamespaces':Provide custom ordering for namespaces or remove namespaces. Do not use this hook to add namespaces. Use CanonicalNamespaces for that. & $namespaces:Array of namespaces indexed by their numbers 'LanguageGetSpecialPageAliases':DEPRECATED! Use $specialPageAliases in a file listed in $wgExtensionMessagesFiles instead. Use to define aliases of special pages names depending of the language & $specialPageAliases:associative array of magic words synonyms $lang:language code(string) 'LanguageGetTranslatedLanguageNames':Provide translated language names. & $names:array of language code=> language name $code:language of the preferred translations 'LanguageLinks':Manipulate a page 's language links. This is called in various places to allow extensions to define the effective language links for a page. $title:The page 's Title. & $links:Array with elements of the form "language:title" in the order that they will be output. & $linkFlags:Associative array mapping prefixed links to arrays of flags. Currently unused, but planned to provide support for marking individual language links in the UI, e.g. for featured articles. 'LanguageSelector':Hook to change the language selector available on a page. $out:The output page. $cssClassName:CSS class name of the language selector. 'LinkBegin':DEPRECATED since 1.28! Use HtmlPageLinkRendererBegin instead. Used when generating internal and interwiki links in Linker::link(), before processing starts. Return false to skip default processing and return $ret. See documentation for Linker::link() for details on the expected meanings of parameters. $skin:the Skin object $target:the Title that the link is pointing to & $html:the contents that the< a > tag should have(raw HTML) $result
Definition: hooks.txt:2034
wfTimestamp
wfTimestamp( $outputtype=TS_UNIX, $ts=0)
Get a timestamp string in one of various formats.
Definition: GlobalFunctions.php:1954
WikiPage\doViewUpdates
doViewUpdates(User $user, $oldid=0)
Do standard deferred updates after page view (existing or missing page)
Definition: WikiPage.php:1238
MediaWiki\Storage\DerivedPageDataUpdater\setRcWatchCategoryMembership
setRcWatchCategoryMembership( $rcWatchCategoryMembership)
Definition: DerivedPageDataUpdater.php:439
WikiPage\replaceSectionAtRev
replaceSectionAtRev( $sectionId, Content $sectionContent, $sectionTitle='', $baseRevId=null)
Definition: WikiPage.php:1598
WikiPage\checkFlags
checkFlags( $flags)
Check flags and add EDIT_NEW or EDIT_UPDATE to them as needed.
Definition: WikiPage.php:1644
$tables
this hook is for auditing only 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 & $tables
Definition: hooks.txt:1018
WikiPage\$mDataLoadedFrom
int $mDataLoadedFrom
One of the READ_* constants.
Definition: WikiPage.php:71
DeferredUpdates\addUpdate
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the deferred list to be run later by execute()
Definition: DeferredUpdates.php:79
WikiPage
Class representing a MediaWiki article and history.
Definition: WikiPage.php:44
StatusValue\newFatal
static newFatal( $message)
Factory function for fatal errors.
Definition: StatusValue.php:68
WikiPage\replaceSectionContent
replaceSectionContent( $sectionId, Content $sectionContent, $sectionTitle='', $edittime=null)
Definition: WikiPage.php:1559
WikiPage\getUserText
getUserText( $audience=Revision::FOR_PUBLIC, User $user=null)
Definition: WikiPage.php:874
PoolWorkArticleView
Definition: PoolWorkArticleView.php:28
NS_FILE
const NS_FILE
Definition: Defines.php:70
$params
$params
Definition: styleTest.css.php:44
WikiPage\makeParserOptions
makeParserOptions( $context)
Get parser options suitable for rendering the primary article wikitext.
Definition: WikiPage.php:1917
$wgMultiContentRevisionSchemaMigrationStage
int $wgMultiContentRevisionSchemaMigrationStage
RevisionStore table schema migration stage (content, slots, content_models & slot_roles tables).
Definition: DefaultSettings.php:8988
WikiPage\getRedirectURL
getRedirectURL( $rt)
Get the Title object or URL to use for a redirect.
Definition: WikiPage.php:1087
wfReadOnly
wfReadOnly()
Check whether the wiki is in read-only mode.
Definition: GlobalFunctions.php:1237
wfMsgReplaceArgs
wfMsgReplaceArgs( $message, $args)
Replace message parameter keys on the given formatted output.
Definition: GlobalFunctions.php:1365
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:592
WikiPage\getRevision
getRevision()
Get the latest revision.
Definition: WikiPage.php:765
RefreshLinksJob\newDynamic
static newDynamic(Title $title, array $params)
Definition: RefreshLinksJob.php:76
RefreshLinksJob\newPrioritized
static newPrioritized(Title $title, array $params)
Definition: RefreshLinksJob.php:64
Revision\getContentHandler
getContentHandler()
Returns the content handler appropriate for this revision's content model.
Definition: Revision.php:996
User\newFromAnyId
static newFromAnyId( $userId, $userName, $actorId)
Static factory method for creation from an ID, name, and/or actor ID.
Definition: User.php:682
$s
$s
Definition: mergeMessageFileList.php:187
WikiPage\getContributors
getContributors()
Get a list of users who have edited this article, not including the user who made the most recent rev...
Definition: WikiPage.php:1125
User\newFromIdentity
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:658
wfLogWarning
wfLogWarning( $msg, $callerOffset=1, $level=E_USER_WARNING)
Send a warning as a PHP error and the debug log.
Definition: GlobalFunctions.php:1145
$res
$res
Definition: database.txt:21
DBAccessObjectUtils\getDBOptions
static getDBOptions( $bitfield)
Get an appropriate DB index, options, and fallback DB index for a query.
Definition: DBAccessObjectUtils.php:52
WikiPage\getComment
getComment( $audience=Revision::FOR_PUBLIC, User $user=null)
Definition: WikiPage.php:892
Revision\newFromPageId
static newFromPageId( $pageId, $revId=0, $flags=0)
Load either the current, or a specified, revision that's attached to a given page ID.
Definition: Revision.php:152
IDBAccessObject
Interface for database access objects.
Definition: IDBAccessObject.php:55
Wikimedia\Rdbms\FakeResultWrapper
Overloads the relevant methods of the real ResultsWrapper so it doesn't go anywhere near an actual da...
Definition: FakeResultWrapper.php:11
$wgContentHandlerUseDB
$wgContentHandlerUseDB
Set to false to disable use of the database fields introduced by the ContentHandler facility.
Definition: DefaultSettings.php:8624
WikiPage\archiveRevisions
archiveRevisions( $dbw, $id, $suppress)
Archives revisions as part of page deletion.
Definition: WikiPage.php:2798
WikiPage\onArticleEdit
static onArticleEdit(Title $title, Revision $revision=null, $slotsChanged=null)
Purge caches on page update etc.
Definition: WikiPage.php:3431
WikiPage\getDBLoadBalancer
getDBLoadBalancer()
Definition: WikiPage.php:245
WikiPage\$mLatest
$mLatest
Definition: WikiPage.php:57
WikiPage\getActionOverrides
getActionOverrides()
Definition: WikiPage.php:255
$wgUseRCPatrol
$wgUseRCPatrol
Use RC Patrolling to check for vandalism (from recent changes and watchlists) New pages and new files...
Definition: DefaultSettings.php:6933
$revQuery
$revQuery
Definition: testCompression.php:51
$wgUseNPPatrol
$wgUseNPPatrol
Use new page patrolling to check new pages on Special:Newpages.
Definition: DefaultSettings.php:6949
WikiPage\flattenRestrictions
static flattenRestrictions( $limit)
Take an array of page restrictions and flatten it to a string suitable for insertion into the page_re...
Definition: WikiPage.php:2508
ActorMigration\newMigration
static newMigration()
Static constructor.
Definition: ActorMigration.php:111
WikiPage\$mTitle
Title $mTitle
Definition: WikiPage.php:50
php
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
WikiPage\$mTouched
string $mTouched
Definition: WikiPage.php:91
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
WikiPage\triggerOpportunisticLinksUpdate
triggerOpportunisticLinksUpdate(ParserOutput $parserOutput)
Opportunistically enqueue link update jobs given fresh parser output if useful.
Definition: WikiPage.php:3652
WikiPage\protectDescription
protectDescription(array $limit, array $expiry)
Builds the description to serve as comment for the edit.
Definition: WikiPage.php:2443
$dbr
$dbr
Definition: testCompression.php:50
IDBAccessObject\READ_LOCKING
const READ_LOCKING
Constants for object loading bitfield flags (higher => higher QoS)
Definition: IDBAccessObject.php:62
Revision
Definition: Revision.php:41
DerivedPageDataUpdater
This document provides an overview of the usage of PageUpdater and DerivedPageDataUpdater
Definition: pageupdater.txt:3
WikiPage\updateParserCache
updateParserCache(array $options=[])
Update the parser cache.
Definition: WikiPage.php:2052
WikiPage\supportsSections
supportsSections()
Returns true if this page's content model supports sections.
Definition: WikiPage.php:1541
$wgEnableScaryTranscluding
$wgEnableScaryTranscluding
Enable interwiki transcluding.
Definition: DefaultSettings.php:4407
Title\getDBkey
getDBkey()
Get the main part with underscores.
Definition: Title.php:951
Revision\getQueryInfo
static getQueryInfo( $options=[])
Return the tables, fields, and join conditions to be selected to create a new revision object.
Definition: Revision.php:521
WikiCategoryPage
Special handling for category pages.
Definition: WikiCategoryPage.php:26
MWException
MediaWiki exception.
Definition: MWException.php:26
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:964
WikiPage\factory
static factory(Title $title)
Create a WikiPage object of the appropriate class for the given title.
Definition: WikiPage.php:127
WikiPage\doDeleteArticleBatched
doDeleteArticleBatched( $reason, $suppress, User $deleter, $tags, $logsubtype, $immediate=false, $webRequestId=null)
Back-end article deletion.
Definition: WikiPage.php:2633
WikiPage\getMinorEdit
getMinorEdit()
Returns true if last revision was marked as "minor edit".
Definition: WikiPage.php:906
wfDeprecated
wfDeprecated( $function, $version=false, $component=false, $callerOffset=2)
Throws a warning that $function is deprecated.
Definition: GlobalFunctions.php:1118
WikiPage\doRollback
doRollback( $fromP, $summary, $token, $bot, &$resultDetails, User $user, $tags=null)
Roll back the most recent consecutive set of edits to a page from the same user; fails if there are n...
Definition: WikiPage.php:3057
Title\getNamespace
getNamespace()
Get the namespace index, i.e.
Definition: Title.php:974
wfArrayDiff2
wfArrayDiff2( $a, $b)
Like array_diff( $a, $b ) except that it works with two-dimensional arrays.
Definition: GlobalFunctions.php:111
BacklinkCache\get
static get(Title $title)
Create a new BacklinkCache or reuse any existing one.
Definition: BacklinkCache.php:113
Title\newFromRow
static newFromRow( $row)
Make a Title object from a DB row.
Definition: Title.php:475
wfIncrStats
wfIncrStats( $key, $count=1)
Increment a statistics counter.
Definition: GlobalFunctions.php:1227
WikiPage\doSecondaryDataUpdates
doSecondaryDataUpdates(array $options=[])
Do secondary data updates (such as updating link tables).
Definition: WikiPage.php:2093
WikiPage\selectFields
static selectFields()
Return the list of revision fields that should be selected to create a new page.
Definition: WikiPage.php:326
wfGetDB
wfGetDB( $db, $groups=[], $wiki=false)
Get a Database object.
Definition: GlobalFunctions.php:2693
WikiPage\clearPreparedEdit
clearPreparedEdit()
Clear the mPreparedEdit cache field, as may be needed by mutable content types.
Definition: WikiPage.php:315
Title\getInterwiki
getInterwiki()
Get the interwiki prefix.
Definition: Title.php:861
WikiPage\getParserOutput
getParserOutput(ParserOptions $parserOptions, $oldid=null, $forceParse=false)
Get a ParserOutput for the given ParserOptions and revision ID.
Definition: WikiPage.php:1197
WikiPage\getId
getId()
Definition: WikiPage.php:578
WikiPage\insertOn
insertOn( $dbw, $pageId=null)
Insert a new empty page record for this article.
Definition: WikiPage.php:1303
WikiPage\shouldCheckParserCache
shouldCheckParserCache(ParserOptions $parserOptions, $oldId)
Should the parser cache be used?
Definition: WikiPage.php:1175
UserArrayFromResult
Definition: UserArrayFromResult.php:25
WikiPage\getTitle
getTitle()
Get the title object of the article.
Definition: WikiPage.php:276
wfTimestampOrNull
wfTimestampOrNull( $outputtype=TS_UNIX, $ts=null)
Return a formatted timestamp, or null if input is null.
Definition: GlobalFunctions.php:1970
WikiPage\getContent
getContent( $audience=Revision::FOR_PUBLIC, User $user=null)
Get the content of the current revision.
Definition: WikiPage.php:798
WikiPage\exists
exists()
Definition: WikiPage.php:588
WikiPage\__clone
__clone()
Makes sure that the mTitle object is cloned to the newly cloned WikiPage.
Definition: WikiPage.php:115
WikiPage\onArticleDelete
static onArticleDelete(Title $title)
Clears caches when article is deleted.
Definition: WikiPage.php:3377
WikiPage\$mRedirectTarget
Title $mRedirectTarget
Definition: WikiPage.php:76
WikiPage\__construct
__construct(Title $title)
Constructor and clear the article.
Definition: WikiPage.php:107
WikiPage\checkTouched
checkTouched()
Loads page_touched and returns a value indicating if it should be used.
Definition: WikiPage.php:659
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
DeferredUpdates\POSTSEND
const POSTSEND
Definition: DeferredUpdates.php:64
WikiPage\getLinksTimestamp
getLinksTimestamp()
Get the page_links_updated field.
Definition: WikiPage.php:681
WikiPage\purgeInterwikiCheckKey
static purgeInterwikiCheckKey(Title $title)
#-
Definition: WikiPage.php:3476
ChangeTags\getSoftwareTags
static getSoftwareTags( $all=false)
Loads defined core tags, checks for invalid types (if not array), and filters for supported and enabl...
Definition: ChangeTags.php:57
SiteStatsUpdate\factory
static factory(array $deltas)
Definition: SiteStatsUpdate.php:66
Title\makeTitle
static makeTitle( $ns, $title, $fragment='', $interwiki='')
Create a new Title from a namespace index and a DB key.
Definition: Title.php:545
DB_REPLICA
const DB_REPLICA
Definition: defines.php:25
WikiPage\setTimestamp
setTimestamp( $ts)
Set the page timestamp (use only to avoid DB queries)
Definition: WikiPage.php:823
NS_CATEGORY
const NS_CATEGORY
Definition: Defines.php:78
WikiPage\getLatest
getLatest()
Get the page_latest field.
Definition: WikiPage.php:692
ParserOptions\getStubThreshold
getStubThreshold()
Thumb size preferred by the user.
Definition: ParserOptions.php:562
DB_MASTER
const DB_MASTER
Definition: defines.php:26
IDBAccessObject\READ_NONE
const READ_NONE
Definition: IDBAccessObject.php:70
array
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:988
WikiPage\doPurge
doPurge()
Perform the actions of a page purging.
Definition: WikiPage.php:1261
WikiPage\isCountable
isCountable( $editInfo=false)
Determine whether a page would be suitable for being counted as an article in the site_stats table ba...
Definition: WikiPage.php:923
list
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
MessageCache\singleton
static singleton()
Get the signleton instance of this class.
Definition: MessageCache.php:114
WikiPage\getContentModel
getContentModel()
Returns the page's content model id (see the CONTENT_MODEL_XXX constants).
Definition: WikiPage.php:630
WikiPage\pageDataFromTitle
pageDataFromTitle( $dbr, $title, $options=[])
Fetch a page record matching the Title object's namespace and title using a sanitized title string.
Definition: WikiPage.php:437
WikiPage\lockAndGetLatest
lockAndGetLatest()
Lock the page row for this title+id and return page_latest (or 0)
Definition: WikiPage.php:2949
MIGRATION_OLD
const MIGRATION_OLD
Definition: Defines.php:315
Wikimedia\Rdbms\LoadBalancer
Database connection, tracking, load balancing, and transaction manager for a cluster.
Definition: LoadBalancer.php:41
$wgPageLanguageUseDB
bool $wgPageLanguageUseDB
Enable page language feature Allows setting page language in database.
Definition: DefaultSettings.php:8712
Category\newFromTitle
static newFromTitle( $title)
Factory function.
Definition: Category.php:146
WikiPage\getDerivedDataUpdater
getDerivedDataUpdater(User $forUser=null, RevisionRecord $forRevision=null, RevisionSlotsUpdate $forUpdate=null, $forEdit=false)
Returns a DerivedPageDataUpdater for use with the given target revision or new content.
Definition: WikiPage.php:1706
WikiPage\updateIfNewerOn
updateIfNewerOn( $dbw, $revision)
If the given revision is newer than the currently set page_latest, update the page record.
Definition: WikiPage.php:1462
RecentChange\PRC_PATROLLED
const PRC_PATROLLED
Definition: RecentChange.php:78
WikiPage\setLastEdit
setLastEdit(Revision $revision)
Set the latest revision.
Definition: WikiPage.php:756
MediaWiki\Storage\RevisionSlotsUpdate
Value object representing a modification of revision slots.
Definition: RevisionSlotsUpdate.php:36
HTMLCacheUpdate
Class to invalidate the HTML cache of all the pages linking to a given title.
Definition: HTMLCacheUpdate.php:29
Revision\RevisionRenderer
The RevisionRenderer service provides access to rendered output for revisions.
Definition: RevisionRenderer.php:45
wfWikiID
wfWikiID()
Get an ASCII string identifying this wiki This is used as a prefix in memcached keys.
Definition: GlobalFunctions.php:2644
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:67
WikiPage\getDeletionUpdates
getDeletionUpdates( $rev=null)
Returns a list of updates to be performed when this page is deleted.
Definition: WikiPage.php:3706
WikiPage\protectDescriptionLog
protectDescriptionLog(array $limit, array $expiry)
Builds the description to serve as comment for the log entry.
Definition: WikiPage.php:2485
WikiPage\insertRedirect
insertRedirect()
Insert an entry for this page into the redirect table if the content is a redirect.
Definition: WikiPage.php:1018
WikiPage\doDeleteArticleReal
doDeleteArticleReal( $reason, $suppress=false, $u1=null, $u2=null, &$error='', User $deleter=null, $tags=[], $logsubtype='delete', $immediate=false)
Back-end article deletion Deletes the article with database consistency, writes logs,...
Definition: WikiPage.php:2597
EDIT_UPDATE
const EDIT_UPDATE
Definition: Defines.php:153
WikiPage\prepareContentForEdit
prepareContentForEdit(Content $content, $revision=null, User $user=null, $serialFormat=null, $useCache=true)
Prepare content which is about to be saved.
Definition: WikiPage.php:1949
NS_MEDIA
const NS_MEDIA
Definition: Defines.php:52
CdnCacheUpdate
Handles purging appropriate CDN URLs given a title (or titles)
Definition: CdnCacheUpdate.php:30
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
$retval
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 account incomplete not yet checked for validity & $retval
Definition: hooks.txt:244
WikiPage\getCreator
getCreator( $audience=Revision::FOR_PUBLIC, User $user=null)
Get the User object of the user who created the page.
Definition: WikiPage.php:855
MediaWiki\Storage\PageUpdater
Controller-like object for creating and updating pages by creating new revisions.
Definition: PageUpdater.php:72
SCHEMA_COMPAT_WRITE_OLD
const SCHEMA_COMPAT_WRITE_OLD
Definition: Defines.php:284
Title\GAID_FOR_UPDATE
const GAID_FOR_UPDATE
Used to be GAID_FOR_UPDATE define.
Definition: Title.php:54
WANObjectCache
Multi-datacenter aware caching interface.
Definition: WANObjectCache.php:118
WikiPage\newFromID
static newFromID( $id, $from='fromdb')
Constructor from a page id.
Definition: WikiPage.php:165
WikiPage\insertProtectNullRevision
insertProtectNullRevision( $revCommentMsg, array $limit, array $expiry, $cascade, $reason, $user=null)
Insert a new null revision for this page.
Definition: WikiPage.php:2378
$wgSitename
$wgSitename
Name of the site.
Definition: DefaultSettings.php:79
WikiPage\getSourceURL
getSourceURL()
Get the source URL for the content on this page, typically the canonical URL, but may be a remote lin...
Definition: WikiPage.php:3799
ParserOptions\newCanonical
static newCanonical( $context=null, $userLang=null)
Creates a "canonical" ParserOptions object.
Definition: ParserOptions.php:1061
wfEscapeWikiText
wfEscapeWikiText( $text)
Escapes the given text so that it may be output using addWikiText() without any linking,...
Definition: GlobalFunctions.php:1617
WikiPage\$derivedDataUpdater
DerivedPageDataUpdater null $derivedDataUpdater
Definition: WikiPage.php:101
SCHEMA_COMPAT_WRITE_NEW
const SCHEMA_COMPAT_WRITE_NEW
Definition: Defines.php:286
$ret
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 & $ret
Definition: hooks.txt:2036
LinksDeletionUpdate
Update object handling the cleanup of links tables after a page was deleted.
Definition: LinksDeletionUpdate.php:29
Revision\RAW
const RAW
Definition: Revision.php:57
WikiPage\commitRollback
commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser, $tags=null)
Backend implementation of doRollback(), please refer there for parameter and return value documentati...
Definition: WikiPage.php:3103
WikiPage\getHiddenCategories
getHiddenCategories()
Returns a list of hidden categories this page is a member of.
Definition: WikiPage.php:3523
WikiPage\newFromRow
static newFromRow( $row, $from='fromdb')
Constructor from a database row.
Definition: WikiPage.php:195
RecentChange\PRC_AUTOPATROLLED
const PRC_AUTOPATROLLED
Definition: RecentChange.php:79
$wgDeleteRevisionsBatchSize
$wgDeleteRevisionsBatchSize
Page deletions with > this number of revisions will use the job queue.
Definition: DefaultSettings.php:5546
$handler
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:813
RequestContext\getMain
static getMain()
Get the RequestContext object associated with the main request.
Definition: RequestContext.php:432
IP\isValid
static isValid( $ip)
Validate an IP address.
Definition: IP.php:111
WikiPage\doEditContent
doEditContent(Content $content, $summary, $flags=0, $originalRevId=false, User $user=null, $serialFormat=null, $tags=[], $undidRevId=0)
Change an existing article or create a new article.
Definition: WikiPage.php:1842
WikiPage\getQueryInfo
static getQueryInfo()
Return the tables, fields, and join conditions to be selected to create a new page object.
Definition: WikiPage.php:365
WikiPage\getOldestRevision
getOldestRevision()
Get the Revision object of the oldest revision.
Definition: WikiPage.php:703
WikiPage\loadLastEdit
loadLastEdit()
Loads everything except the text This isn't necessary for all uses, so it's only done if needed.
Definition: WikiPage.php:716
WikiPage\followRedirect
followRedirect()
Get the Title object or URL this page redirects to.
Definition: WikiPage.php:1076
Content
Base interface for content objects.
Definition: Content.php:34
EDIT_NEW
const EDIT_NEW
Definition: Defines.php:152
WikiPage\loadFromRow
loadFromRow( $data, $from)
Load the object from a database row.
Definition: WikiPage.php:538
$wgCascadingRestrictionLevels
$wgCascadingRestrictionLevels
Restriction levels that can be used with cascading protection.
Definition: DefaultSettings.php:5340
text
This list may contain false positives That usually means there is additional text with links below the first Each row contains links to the first and second as well as the first line of the second redirect text
Definition: All_system_messages.txt:1267
WikiPage\formatExpiry
formatExpiry( $expiry)
Definition: WikiPage.php:2421
$args
if( $line===false) $args
Definition: cdb.php:64
Title
Represents a title within MediaWiki.
Definition: Title.php:39
wfRandom
wfRandom()
Get a random decimal value between 0 and 1, in a way not likely to give duplicate values for any real...
Definition: GlobalFunctions.php:278
WikiPage\newDerivedDataUpdater
newDerivedDataUpdater()
Definition: WikiPage.php:1659
User\isAllowedAny
isAllowedAny()
Check if user is allowed to access a feature / make an action.
Definition: User.php:3827
wfReadOnlyReason
wfReadOnlyReason()
Check if the site is in read-only mode and return the message if so.
Definition: GlobalFunctions.php:1250
$cache
$cache
Definition: mcc.php:33
WikiPage\doEditUpdates
doEditUpdates(Revision $revision, User $user, array $options=[])
Do standard deferred updates after page edit.
Definition: WikiPage.php:2024
$options
null means default in associative array with keys and values unescaped Should be merged with default with a value of false meaning to suppress the attribute in associative array with keys and values unescaped & $options
Definition: hooks.txt:2036
ObjectCache\getMainWANInstance
static getMainWANInstance()
Get the main WAN cache object.
Definition: ObjectCache.php:378
DeferredUpdates\PRESEND
const PRESEND
Definition: DeferredUpdates.php:63
$job
if(count( $args)< 1) $job
Definition: recompressTracked.php:48
WebRequest\getRequestId
static getRequestId()
Get the unique request ID.
Definition: WebRequest.php:275
$wgAjaxEditStash
$wgAjaxEditStash
Have clients send edits to be prepared when filling in edit summaries.
Definition: DefaultSettings.php:8256
WikiPage\$mId
int $mId
Definition: WikiPage.php:66
JobQueueGroup\singleton
static singleton( $wiki=false)
Definition: JobQueueGroup.php:69
WikiPage\$mIsRedirect
$mIsRedirect
Definition: WikiPage.php:56
$rev
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:1808
MWNamespace\getCategoryLinkType
static getCategoryLinkType( $index)
Returns the link type to be used for categories.
Definition: MWNamespace.php:554
as
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
DeletePageJob
Class DeletePageJob.
Definition: DeletePageJob.php:6
WikiPage\getWikiDisplayName
getWikiDisplayName()
The display name for the site this content come from.
Definition: WikiPage.php:3786
LinksUpdate\queueRecursiveJobsForTable
static queueRecursiveJobsForTable(Title $title, $table, $action='unknown', $userName='unknown')
Queue a RefreshLinks job for any table.
Definition: LinksUpdate.php:349
Revision\loadFromTimestamp
static loadFromTimestamp( $db, $title, $timestamp)
Load the revision for the given title with the given timestamp.
Definition: Revision.php:291
WikiPage\convertSelectType
static convertSelectType( $type)
Convert 'fromdb', 'fromdbmaster' and 'forupdate' to READ_* constants.
Definition: WikiPage.php:207
WikiPage\updateCategoryCounts
updateCategoryCounts(array $added, array $deleted, $id=0)
Update all the appropriate counts in the category table, given that we've added the categories $added...
Definition: WikiPage.php:3568
WikiPage\getMutableCacheKeys
getMutableCacheKeys(WANObjectCache $cache)
Definition: WikiPage.php:3808
InfoAction\invalidateCache
static invalidateCache(Title $title, $revid=null)
Clear the info cache for a given Title.
Definition: InfoAction.php:70
LoggerFactory
MediaWiki Logger LoggerFactory implements a PSR[0] compatible message logging system Named Psr Log LoggerInterface instances can be obtained from the MediaWiki Logger LoggerFactory::getInstance() static method. MediaWiki\Logger\LoggerFactory expects a class implementing the MediaWiki\Logger\Spi interface to act as a factory for new Psr\Log\LoggerInterface instances. The "Spi" in MediaWiki\Logger\Spi stands for "service provider interface". An SPI is an API intended to be implemented or extended by a third party. This software design pattern is intended to enable framework extension and replaceable components. It is specifically used in the MediaWiki\Logger\LoggerFactory service to allow alternate PSR-3 logging implementations to be easily integrated with MediaWiki. The service provider interface allows the backend logging library to be implemented in multiple ways. The $wgMWLoggerDefaultSpi global provides the classname of the default MediaWiki\Logger\Spi implementation to be loaded at runtime. This can either be the name of a class implementing the MediaWiki\Logger\Spi with a zero argument const ructor or a callable that will return an MediaWiki\Logger\Spi instance. Alternately the MediaWiki\Logger\LoggerFactory MediaWiki Logger LoggerFactory
Definition: logger.txt:5
$source
$source
Definition: mwdoc-filter.php:46
$updater
$page->newPageUpdater($user) $updater
Definition: pageupdater.txt:63
ManualLogEntry
Class for creating new log entries and inserting them into the database.
Definition: LogEntry.php:437
$content
$content
Definition: pageupdater.txt:72
WikiPage\pageData
pageData( $dbr, $conditions, $options=[])
Fetch a page record with the given conditions.
Definition: WikiPage.php:404
Revision\newNullRevision
static newNullRevision( $dbw, $pageId, $summary, $minor, $user=null)
Create a new null-revision for insertion into a page's history.
Definition: Revision.php:1170
MediaWiki\Edit\PreparedEdit
Represents information returned by WikiPage::prepareContentForEdit()
Definition: PreparedEdit.php:34
WikiFilePage
Special handling for file pages.
Definition: WikiFilePage.php:30
WikiPage\isLocal
isLocal()
Whether this content displayed on this page comes from the local database.
Definition: WikiPage.php:3773
wfWarn
wfWarn( $msg, $callerOffset=1, $level=E_USER_NOTICE)
Send a warning either to the debug log or in a PHP error depending on $wgDevelopmentWarnings.
Definition: GlobalFunctions.php:1132
$wgArticleCountMethod
$wgArticleCountMethod
Method used to determine if a page in a content namespace should be counted as a valid article.
Definition: DefaultSettings.php:4450
NS_MEDIAWIKI
const NS_MEDIAWIKI
Definition: Defines.php:72
Category\newFromName
static newFromName( $name)
Factory function.
Definition: Category.php:126
EDIT_MINOR
const EDIT_MINOR
Definition: Defines.php:154
ParserOptions\isSafeToCache
isSafeToCache()
Test whether these options are safe to cache.
Definition: ParserOptions.php:1389
WikiPage\insertRedirectEntry
insertRedirectEntry(Title $rt, $oldLatest=null)
Insert or update the redirect table entry for this page to indicate it redirects to $rt.
Definition: WikiPage.php:1043
MediaWikiServices
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
WikiPage\getTimestamp
getTimestamp()
Definition: WikiPage.php:809
WikiPage\getRevisionRenderer
getRevisionRenderer()
Definition: WikiPage.php:231
WikiPage\updateRedirectOn
updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect=null)
Add row to the redirect table if this is a redirect, remove otherwise.
Definition: WikiPage.php:1427
Revision\getRevisionRecord
getRevisionRecord()
Definition: Revision.php:637
WikiPage\$mLinksUpdated
string $mLinksUpdated
Definition: WikiPage.php:96
WikiPage\doDeleteArticle
doDeleteArticle( $reason, $suppress=false, $u1=null, $u2=null, &$error='', User $user=null, $immediate=false)
Same as doDeleteArticleReal(), but returns a simple boolean.
Definition: WikiPage.php:2564
wfMessage
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt
WikiPage\isRedirect
isRedirect()
Tests if the article content represents a redirect.
Definition: WikiPage.php:612
WikiPage\$mLastRevision
Revision $mLastRevision
Definition: WikiPage.php:81
MediaWiki\Storage\DerivedPageDataUpdater
A handle for managing updates for derived page data on edit, import, purge, etc.
Definition: DerivedPageDataUpdater.php:95
CacheTime\getCacheTime
getCacheTime()
Definition: CacheTime.php:60
ResourceLoaderWikiModule\invalidateModuleCache
static invalidateModuleCache(Title $title, Revision $old=null, Revision $new=null, $wikiId)
Clear the preloadTitleInfo() cache for all wiki modules on this wiki on page change if it was a JS or...
Definition: ResourceLoaderWikiModule.php:534
CommentStore\getStore
static getStore()
Definition: CommentStore.php:125
WikiPage\$mDataLoaded
$mDataLoaded
Definition: WikiPage.php:55
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:47
DeferredUpdates\addCallableUpdate
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add a callable update.
Definition: DeferredUpdates.php:118
ParserOutput\hasDynamicContent
hasDynamicContent()
Check whether the cache TTL was lowered due to dynamic content.
Definition: ParserOutput.php:1233
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
WikiPage\getParserCache
getParserCache()
Definition: WikiPage.php:238
WikiPage\$mPreparedEdit
PreparedEdit $mPreparedEdit
Map of cache fields (text, parser output, ect) for a proposed/new edit.
Definition: WikiPage.php:61
WikiPage\pageDataFromId
pageDataFromId( $dbr, $id, $options=[])
Fetch a page record matching the requested ID.
Definition: WikiPage.php:451
WikiPage\getContentHandler
getContentHandler()
Returns the ContentHandler instance to be used to deal with the content of this WikiPage.
Definition: WikiPage.php:268
WikiPage\hasDifferencesOutsideMainSlot
static hasDifferencesOutsideMainSlot(Revision $a, Revision $b)
Helper method for checking whether two revisions have differences that go beyond the main slot.
Definition: WikiPage.php:1500
CommentStoreComment
CommentStoreComment represents a comment stored by CommentStore.
Definition: CommentStoreComment.php:29
WikiPage\$mTimestamp
string $mTimestamp
Timestamp of the current revision or empty string if not loaded.
Definition: WikiPage.php:86
Title\purgeExpiredRestrictions
static purgeExpiredRestrictions()
Purge expired restrictions from the page_restrictions table.
Definition: Title.php:3415
Revision\SlotRecord
Value object representing a content slot associated with a page revision.
Definition: SlotRecord.php:39
$wgRCWatchCategoryMembership
$wgRCWatchCategoryMembership
Treat category membership changes as a RecentChange.
Definition: DefaultSettings.php:6923
WikiPage\doDeleteUpdates
doDeleteUpdates( $id, Content $content=null, Revision $revision=null, User $user=null)
Do some database updates after deletion.
Definition: WikiPage.php:2977
User\isAllowed
isAllowed( $action='')
Internal mechanics of testing a permission.
Definition: User.php:3857
WikiPage\clear
clear()
Clear the object.
Definition: WikiPage.php:284
WikiPage\doUpdateRestrictions
doUpdateRestrictions(array $limit, array $expiry, &$cascade, $reason, User $user, $tags=null)
Update the article's restriction field, and leave a log entry.
Definition: WikiPage.php:2123
WikiPage\getRevisionStore
getRevisionStore()
Definition: WikiPage.php:224
$type
$type
Definition: testCompression.php:48
$wgActorTableSchemaMigrationStage
int $wgActorTableSchemaMigrationStage
Actor table schema migration stage.
Definition: DefaultSettings.php:9006