MediaWiki  master
PageUpdater.php
Go to the documentation of this file.
1 <?php
25 namespace MediaWiki\Storage;
26 
29 use Content;
30 use ContentHandler;
31 use DeferredUpdates;
32 use InvalidArgumentException;
33 use LogicException;
34 use ManualLogEntry;
49 use MWException;
50 use RecentChange;
51 use RuntimeException;
52 use Status;
53 use Title;
54 use TitleFormatter;
55 use User;
56 use Wikimedia\Assert\Assert;
61 use WikiPage;
62 
79 class PageUpdater {
80 
86  public const CONSTRUCTOR_OPTIONS = [
87  'ManualRevertSearchRadius',
88  'UseRCPatrol',
89  ];
90 
94  private $author;
95 
99  private $wikiPage;
100 
105 
109  private $loadBalancer;
110 
114  private $revisionStore;
115 
120 
125 
129  private $hookRunner;
130 
134  private $hookContainer;
135 
138 
141 
144 
150 
155 
159  private $usePageCreationLog = true;
160 
164  private $tags = [];
165 
169  private $slotsUpdate;
170 
174  private $status = null;
175 
180 
184  private $editResult = null;
185 
190 
194  private $flags = 0;
195 
197  private $softwareTags = [];
198 
215  public function __construct(
228  array $softwareTags
229  ) {
230  $serviceOptions->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
231  $this->serviceOptions = $serviceOptions;
232 
233  $this->author = $author;
234  $this->wikiPage = $wikiPage;
235  $this->derivedDataUpdater = $derivedDataUpdater;
236 
237  $this->loadBalancer = $loadBalancer;
238  $this->revisionStore = $revisionStore;
239  $this->slotRoleRegistry = $slotRoleRegistry;
240  $this->contentHandlerFactory = $contentHandlerFactory;
241  $this->hookContainer = $hookContainer;
242  $this->hookRunner = new HookRunner( $hookContainer );
243  $this->userEditTracker = $userEditTracker;
244  $this->userGroupManager = $userGroupManager;
245  $this->titleFormatter = $titleFormatter;
246 
247  $this->slotsUpdate = new RevisionSlotsUpdate();
248  $this->editResultBuilder = new EditResultBuilder(
251  new ServiceOptions(
253  [
254  'ManualRevertSearchRadius' =>
255  $serviceOptions->get( 'ManualRevertSearchRadius' )
256  ]
257  )
258  );
259  $this->softwareTags = $softwareTags;
260  }
261 
287  public function setFlags( int $flags ) {
288  $this->flags |= $flags;
289  return $this;
290  }
291 
301  public function prepareUpdate( int $flags = 0 ) {
302  $this->setFlags( $flags );
303 
304  // Load the data from the primary database if needed. Needed to check flags.
305  $this->grabParentRevision();
306  if ( !$this->derivedDataUpdater->isUpdatePrepared() ) {
307  // Avoid statsd noise and wasted cycles check the edit stash (T136678)
308  $useStashed = !( ( $this->flags & EDIT_INTERNAL ) || ( $this->flags & EDIT_FORCE_BOT ) );
309  // Prepare the update. This performs PST and generates the canonical ParserOutput.
310  $this->derivedDataUpdater->prepareContent(
311  $this->author,
312  $this->slotsUpdate,
313  $useStashed
314  );
315  }
316  }
317 
323  private static function toLegacyUser( UserIdentity $user ) {
324  return User::newFromIdentity( $user );
325  }
326 
336  $this->useAutomaticEditSummaries = $useAutomaticEditSummaries;
337  return $this;
338  }
339 
350  public function setRcPatrolStatus( $status ) {
351  $this->rcPatrolStatus = $status;
352  return $this;
353  }
354 
363  public function setUsePageCreationLog( $use ) {
364  $this->usePageCreationLog = $use;
365  return $this;
366  }
367 
368  private function getWikiId() {
369  return false; // TODO: get from RevisionStore!
370  }
371 
377  private function getDBConnectionRef( $mode ) {
378  return $this->loadBalancer->getConnectionRef( $mode, [], $this->getWikiId() );
379  }
380 
385  private function getPage(): PageIdentity {
386  return $this->wikiPage;
387  }
388 
392  private function getTitle() {
393  // NOTE: eventually, we won't get a WikiPage passed into the constructor any more
394  return $this->wikiPage->getTitle();
395  }
396 
400  private function getWikiPage() {
401  // NOTE: eventually, we won't get a WikiPage passed into the constructor any more
402  return $this->wikiPage;
403  }
404 
438  public function hasEditConflict( $expectedParentRevision ) {
439  $parent = $this->grabParentRevision();
440  $parentId = $parent ? $parent->getId() : 0;
441 
442  return $parentId !== $expectedParentRevision;
443  }
444 
472  public function grabParentRevision() {
473  return $this->derivedDataUpdater->grabCurrentRevision();
474  }
475 
483  public function setContent( $role, Content $content ) {
484  $this->ensureRoleAllowed( $role );
485 
486  $this->slotsUpdate->modifyContent( $role, $content );
487  return $this;
488  }
489 
496  public function setSlot( SlotRecord $slot ) {
497  $this->ensureRoleAllowed( $slot->getRole() );
498 
499  $this->slotsUpdate->modifySlot( $slot );
500  return $this;
501  }
502 
518  public function inheritSlot( SlotRecord $originalSlot ) {
519  // NOTE: slots can be inherited even if the role is not "allowed" on the title.
520  // NOTE: this slot is inherited from some other revision, but it's
521  // a "modified" slot for the RevisionSlotsUpdate and DerivedPageDataUpdater,
522  // since it's not implicitly inherited from the parent revision.
523  $inheritedSlot = SlotRecord::newInherited( $originalSlot );
524  $this->slotsUpdate->modifySlot( $inheritedSlot );
525  return $this;
526  }
527 
537  public function removeSlot( $role ) {
538  $this->ensureRoleNotRequired( $role );
539 
540  $this->slotsUpdate->removeSlot( $role );
541  }
542 
553  public function setOriginalRevisionId( $originalRevId ) {
554  $this->editResultBuilder->setOriginalRevision( $originalRevId );
555  return $this;
556  }
557 
570  public function markAsRevert(
571  int $revertMethod,
572  int $newestRevertedRevId,
573  int $revertAfterRevId = null
574  ) {
575  $this->editResultBuilder->markAsRevert(
576  $revertMethod, $newestRevertedRevId, $revertAfterRevId
577  );
578  return $this;
579  }
580 
588  public function getEditResult(): ?EditResult {
589  return $this->editResult;
590  }
591 
599  public function addTag( string $tag ) {
600  $this->tags[] = trim( $tag );
601  return $this;
602  }
603 
611  public function addTags( array $tags ) {
612  Assert::parameterElementType( 'string', $tags, '$tags' );
613  foreach ( $tags as $tag ) {
614  $this->addTag( $tag );
615  }
616  return $this;
617  }
618 
627  public function addSoftwareTag( string $tag ): self {
628  if ( in_array( $tag, $this->softwareTags ) ) {
629  $this->addTag( $tag );
630  }
631  return $this;
632  }
633 
639  public function getExplicitTags() {
640  return $this->tags;
641  }
642 
646  private function computeEffectiveTags() {
647  $tags = $this->tags;
648  $editResult = $this->getEditResult();
649 
650  foreach ( $this->slotsUpdate->getModifiedRoles() as $role ) {
651  $old_content = $this->getParentContent( $role );
652 
653  $handler = $this->getContentHandler( $role );
654  $content = $this->slotsUpdate->getModifiedSlot( $role )->getContent();
655 
656  // TODO: MCR: Do this for all slots. Also add tags for removing roles!
657  $tag = $handler->getChangeTag( $old_content, $content, $this->flags );
658  // If there is no applicable tag, null is returned, so we need to check
659  if ( $tag ) {
660  $tags[] = $tag;
661  }
662  }
663 
664  $tags = array_merge( $tags, $editResult->getRevertTags() );
665 
666  return array_unique( $tags );
667  }
668 
676  private function getParentContent( $role ) {
677  $parent = $this->grabParentRevision();
678 
679  if ( $parent && $parent->hasSlot( $role ) ) {
680  return $parent->getContent( $role, RevisionRecord::RAW );
681  }
682 
683  return null;
684  }
685 
690  private function getContentHandler( $role ) {
691  if ( $this->slotsUpdate->isModifiedSlot( $role ) ) {
692  $slot = $this->slotsUpdate->getModifiedSlot( $role );
693  } else {
694  $parent = $this->grabParentRevision();
695 
696  if ( $parent ) {
697  $slot = $parent->getSlot( $role, RevisionRecord::RAW );
698  } else {
699  throw new RevisionAccessException(
700  'No such slot: {role}',
701  [ 'role' => $role ]
702  );
703  }
704  }
705 
706  return $this->contentHandlerFactory->getContentHandler( $slot->getModel() );
707  }
708 
712  private function makeAutoSummary() {
713  if ( !$this->useAutomaticEditSummaries || ( $this->flags & EDIT_AUTOSUMMARY ) === 0 ) {
715  }
716 
717  // NOTE: this generates an auto-summary for SOME RANDOM changed slot!
718  // TODO: combine auto-summaries for multiple slots!
719  // XXX: this logic should not be in the storage layer!
720  $roles = $this->slotsUpdate->getModifiedRoles();
721  $role = reset( $roles );
722 
723  if ( $role === false ) {
725  }
726 
727  $handler = $this->getContentHandler( $role );
728  $content = $this->slotsUpdate->getModifiedSlot( $role )->getContent();
729  $old_content = $this->getParentContent( $role );
730  $summary = $handler->getAutosummary( $old_content, $content, $this->flags );
731 
732  return CommentStoreComment::newUnsavedComment( $summary );
733  }
734 
767  public function saveRevision( CommentStoreComment $summary, int $flags = 0 ) {
768  $this->setFlags( $flags );
769 
770  if ( $this->wasCommitted() ) {
771  throw new RuntimeException(
772  'saveRevision() or updateRevision() has already been called on this PageUpdater!'
773  );
774  }
775 
776  // Low-level sanity check
777  if ( $this->getPage()->getDBkey() === '' ) {
778  throw new RuntimeException( 'Something is trying to edit an article with an empty title' );
779  }
780 
781  // NOTE: slots can be inherited even if the role is not "allowed" on the title.
783  $this->checkAllRolesAllowed(
784  $this->slotsUpdate->getModifiedRoles(),
785  $status
786  );
787  $this->checkNoRolesRequired(
788  $this->slotsUpdate->getRemovedRoles(),
789  $status
790  );
791 
792  if ( !$status->isOK() ) {
793  return null;
794  }
795 
796  // Make sure the given content is allowed in the respective slots of this page
797  foreach ( $this->slotsUpdate->getModifiedRoles() as $role ) {
798  $slot = $this->slotsUpdate->getModifiedSlot( $role );
799  $roleHandler = $this->slotRoleRegistry->getRoleHandler( $role );
800 
801  if ( !$roleHandler->isAllowedModel( $slot->getModel(), $this->getPage() ) ) {
802  $contentHandler = $this->contentHandlerFactory
803  ->getContentHandler( $slot->getModel() );
804  $this->status = Status::newFatal( 'content-not-allowed-here',
805  ContentHandler::getLocalizedName( $contentHandler->getModelID() ),
806  $this->titleFormatter->getPrefixedText( $this->getPage() ),
807  wfMessage( $roleHandler->getNameMessageKey() )
808  // TODO: defer message lookup to caller
809  );
810  return null;
811  }
812  }
813 
814  // Load the data from the primary database if needed. Needed to check flags.
815  // NOTE: This grabs the parent revision as the CAS token, if grabParentRevision
816  // wasn't called yet. If the page is modified by another process before we are done with
817  // it, this method must fail (with status 'edit-conflict')!
818  // NOTE: The parent revision may be different from the edit's base revision.
819  $this->prepareUpdate();
820 
821  // Detect whether update or creation should be performed.
822  if ( !( $this->flags & EDIT_NEW ) && !( $this->flags & EDIT_UPDATE ) ) {
823  $this->flags |= ( $this->derivedDataUpdater->pageExisted() ) ? EDIT_UPDATE : EDIT_NEW;
824  }
825 
826  // Trigger pre-save hook (using provided edit summary)
827  $renderedRevision = $this->derivedDataUpdater->getRenderedRevision();
828  $hookStatus = Status::newGood( [] );
829  $allowedByHook = $this->hookRunner->onMultiContentSave(
830  $renderedRevision, $this->author, $summary, $this->flags, $hookStatus
831  );
832  if ( $allowedByHook && $this->hookContainer->isRegistered( 'PageContentSave' ) ) {
833  // Also run the legacy hook.
834  // NOTE: WikiPage should only be used for the legacy hook,
835  // and only if something uses the legacy hook.
836  $mainContent = $this->derivedDataUpdater->getSlots()->getContent( SlotRecord::MAIN );
837 
838  $legacyUser = self::toLegacyUser( $this->author );
839 
840  // Deprecated since 1.35.
841  $allowedByHook = $this->hookRunner->onPageContentSave(
842  $this->getWikiPage(), $legacyUser, $mainContent, $summary,
843  $this->flags & EDIT_MINOR, null, null, $this->flags, $hookStatus
844  );
845  }
846 
847  if ( !$allowedByHook ) {
848  // The hook has prevented this change from being saved.
849  if ( $hookStatus->isOK() ) {
850  // Hook returned false but didn't call fatal(); use generic message
851  $hookStatus->fatal( 'edit-hook-aborted' );
852  }
853 
854  $this->status = $hookStatus;
855  return null;
856  }
857 
858  // Provide autosummaries if one is not provided and autosummaries are enabled
859  // XXX: $summary == null seems logical, but the empty string may actually come from the user
860  // XXX: Move this logic out of the storage layer! It does not belong here! Use a callback?
861  if ( $summary->text === '' && $summary->data === null ) {
862  $summary = $this->makeAutoSummary();
863  }
864 
865  // Actually create the revision and create/update the page.
866  // Do NOT yet set $this->status!
867  if ( $this->flags & EDIT_UPDATE ) {
868  $status = $this->doModify( $summary );
869  } else {
870  $status = $this->doCreate( $summary );
871  }
872 
873  // Promote user to any groups they meet the criteria for
875  $this->userGroupManager->addUserToAutopromoteOnceGroups( $this->author, 'onEdit' );
876  // Also run 'onView' for backwards compatibility
877  $this->userGroupManager->addUserToAutopromoteOnceGroups( $this->author, 'onView' );
878  } );
879 
880  // NOTE: set $this->status only after all hooks have been called,
881  // so wasCommitted doesn't return true when called indirectly from a hook handler!
882  $this->status = $status;
883 
884  // TODO: replace bad status with Exceptions!
885  return ( $this->status && $this->status->isOK() )
886  ? $this->status->value['revision-record']
887  : null;
888  }
889 
901  public function updateRevision( int $revId = 0 ) {
902  if ( $this->wasCommitted() ) {
903  throw new RuntimeException(
904  'saveRevision() or updateRevision() has already been called on this PageUpdater!'
905  );
906  }
907 
908  // Low-level sanity check
909  if ( $this->getPage()->getDBkey() === '' ) {
910  throw new RuntimeException( 'Something is trying to edit an article with an empty title' );
911  }
912 
914  $this->checkAllRolesAllowed(
915  $this->slotsUpdate->getModifiedRoles(),
916  $status
917  );
918  $this->checkAllRolesDerived(
919  $this->slotsUpdate->getModifiedRoles(),
920  $status
921  );
922  $this->checkAllRolesDerived(
923  $this->slotsUpdate->getRemovedRoles(),
924  $status
925  );
926 
927  if ( $revId === 0 ) {
928  $revision = $this->grabParentRevision();
929  } else {
930  $revision = $this->revisionStore->getRevisionById( $revId, RevisionStore::READ_LATEST );
931  }
932  if ( $revision === null ) {
933  $status->fatal( 'edit-gone-missing' );
934  }
935 
936  if ( !$status->isOK() ) {
937  $this->status = $status;
938  return;
939  }
940 
941  // Make sure the given content is allowed in the respective slots of this page
942  foreach ( $this->slotsUpdate->getModifiedRoles() as $role ) {
943  $slot = $this->slotsUpdate->getModifiedSlot( $role );
944  $roleHandler = $this->slotRoleRegistry->getRoleHandler( $role );
945 
946  if ( !$roleHandler->isAllowedModel( $slot->getModel(), $this->getPage() ) ) {
947  $contentHandler = $this->contentHandlerFactory
948  ->getContentHandler( $slot->getModel() );
949  $this->status = Status::newFatal(
950  'content-not-allowed-here',
951  ContentHandler::getLocalizedName( $contentHandler->getModelID() ),
952  $this->titleFormatter->getPrefixedText( $this->getPage() ),
953  wfMessage( $roleHandler->getNameMessageKey() )
954  // TODO: defer message lookup to caller
955  );
956  return;
957  }
958  }
959 
960  // XXX: do we need PST?
961 
962  $this->flags |= EDIT_INTERNAL;
963  $this->status = $this->doUpdate( $revision );
964  }
965 
971  public function wasCommitted() {
972  return $this->status !== null;
973  }
974 
998  public function getStatus() {
999  return $this->status;
1000  }
1001 
1007  public function wasSuccessful() {
1008  return $this->status && $this->status->isOK();
1009  }
1010 
1016  public function isNew() {
1017  return $this->status && $this->status->isOK() && $this->status->value['new'];
1018  }
1019 
1027  public function isUnchanged() {
1028  return $this->status
1029  && $this->status->isOK()
1030  && $this->status->value['revision-record'] === null;
1031  }
1032 
1039  public function getNewRevision() {
1040  return ( $this->status && $this->status->isOK() )
1041  ? $this->status->value['revision-record']
1042  : null;
1043  }
1044 
1058  private function makeNewRevision(
1059  CommentStoreComment $comment,
1060  Status $status
1061  ) {
1062  $wikiPage = $this->getWikiPage();
1063  $title = $this->getTitle();
1064  $parent = $this->grabParentRevision();
1065 
1066  // XXX: we expect to get a MutableRevisionRecord here, but that's a bit brittle!
1067  // TODO: introduce something like an UnsavedRevisionFactory service instead!
1069  $rev = $this->derivedDataUpdater->getRevision();
1070  '@phan-var MutableRevisionRecord $rev';
1071 
1072  // Avoid fatal error when the Title's ID changed, T204793
1073  if (
1074  $rev->getPageId() !== null && $title->exists()
1075  && $rev->getPageId() !== $title->getArticleID()
1076  ) {
1077  $titlePageId = $title->getArticleID();
1078  $revPageId = $rev->getPageId();
1079  $masterPageId = $title->getArticleID( Title::READ_LATEST );
1080 
1081  if ( $revPageId === $masterPageId ) {
1082  wfWarn( __METHOD__ . ": Encountered stale Title object: old ID was $titlePageId, "
1083  . "continuing with new ID from primary DB, $masterPageId" );
1084  } else {
1085  throw new InvalidArgumentException(
1086  "Revision inherited page ID $revPageId from its parent, "
1087  . "but the provided Title object belongs to page ID $masterPageId"
1088  );
1089  }
1090  }
1091 
1092  $rev->setPageId( $title->getArticleID() );
1093 
1094  if ( $parent ) {
1095  $oldid = $parent->getId();
1096  $rev->setParentId( $oldid );
1097  } else {
1098  $oldid = 0;
1099  }
1100 
1101  $rev->setComment( $comment );
1102  $rev->setUser( $this->author );
1103  $rev->setMinorEdit( ( $this->flags & EDIT_MINOR ) > 0 );
1104 
1105  foreach ( $rev->getSlots()->getSlots() as $slot ) {
1106  $content = $slot->getContent();
1107 
1108  // XXX: We may push this up to the "edit controller" level, see T192777.
1109  // XXX: prepareSave() and isValid() could live in SlotRoleHandler
1110  // XXX: PrepareSave should not take a WikiPage!
1111  $legacyUser = self::toLegacyUser( $this->author );
1112  $prepStatus = $content->prepareSave( $wikiPage, $this->flags, $oldid, $legacyUser );
1113 
1114  // TODO: MCR: record which problem arose in which slot.
1115  $status->merge( $prepStatus );
1116  }
1117 
1118  $this->checkAllRequiredRoles(
1119  $rev->getSlotRoles(),
1120  $status
1121  );
1122 
1123  return $rev;
1124  }
1125 
1133  private function buildEditResult( RevisionRecord $revision, bool $isNew ) {
1134  $this->editResultBuilder->setRevisionRecord( $revision );
1135  $this->editResultBuilder->setIsNew( $isNew );
1136  $this->editResult = $this->editResultBuilder->buildEditResult();
1137  }
1138 
1150  private function doUpdate( RevisionRecord $revision ): Status {
1151  $currentRevision = $this->grabParentRevision();
1152  if ( !$currentRevision ) {
1153  // Article gone missing
1154  return Status::newFatal( 'edit-gone-missing' );
1155  }
1156 
1157  $dbw = $this->getDBConnectionRef( DB_PRIMARY );
1158  $dbw->startAtomic( __METHOD__ );
1159 
1160  $slots = $this->revisionStore->updateslotsOn( $revision, $this->slotsUpdate, $dbw );
1161 
1162  $dbw->endAtomic( __METHOD__ );
1163 
1164  // Return the slots and revision to the caller
1165  $newRevisionRecord = MutableRevisionRecord::newUpdatedRevisionRecord( $revision, $slots );
1167  'revision-record' => $newRevisionRecord,
1168  'slots' => $slots,
1169  ] );
1170 
1171  $isCurrent = $revision->getId( $this->getWikiId() ) ===
1172  $currentRevision->getId( $this->getWikiId() );
1173 
1174  if ( $isCurrent ) {
1175  // Update page_touched
1176  $this->getTitle()->invalidateCache( $newRevisionRecord->getTimestamp() );
1177 
1178  $this->buildEditResult( $newRevisionRecord, false );
1179 
1180  // Do secondary updates once the main changes have been committed...
1181  $wikiPage = $this->getWikiPage(); // TODO: use for legacy hooks only!
1183  $this->getAtomicSectionUpdate(
1184  $dbw,
1185  $wikiPage,
1186  $newRevisionRecord,
1187  $revision->getComment(),
1188  [ 'changed' => false, ]
1189  ),
1190  DeferredUpdates::PRESEND
1191  );
1192  }
1193 
1194  return $status;
1195  }
1196 
1203  private function doModify( CommentStoreComment $summary ) {
1204  $wikiPage = $this->getWikiPage(); // TODO: use for legacy hooks only!
1205 
1206  // Update article, but only if changed.
1208  [ 'new' => false, 'revision-record' => null ]
1209  );
1210 
1211  $oldRev = $this->grabParentRevision();
1212  $oldid = $oldRev ? $oldRev->getId() : 0;
1213 
1214  if ( !$oldRev ) {
1215  // Article gone missing
1216  return $status->fatal( 'edit-gone-missing' );
1217  }
1218 
1219  $newRevisionRecord = $this->makeNewRevision(
1220  $summary,
1221  $status
1222  );
1223 
1224  if ( !$status->isOK() ) {
1225  return $status;
1226  }
1227 
1228  $now = $newRevisionRecord->getTimestamp();
1229 
1230  // XXX: we may want a flag that allows a null revision to be forced!
1231  $changed = $this->derivedDataUpdater->isChange();
1232 
1233  // We build the EditResult before the $change if/else branch in order to pass
1234  // the correct $newRevisionRecord to EditResultBuilder. In case this is a null
1235  // edit, $newRevisionRecord will be later overridden to its parent revision, which
1236  // would confuse EditResultBuilder.
1237  if ( !$changed ) {
1238  // This is a null edit, ensure original revision ID is set properly
1239  $this->editResultBuilder->setOriginalRevision( $oldRev );
1240  }
1241  $this->buildEditResult( $newRevisionRecord, false );
1242 
1243  $dbw = $this->getDBConnectionRef( DB_PRIMARY );
1244 
1245  if ( $changed ) {
1246  $dbw->startAtomic( __METHOD__ );
1247 
1248  // Get the latest page_latest value while locking it.
1249  // Do a CAS style check to see if it's the same as when this method
1250  // started. If it changed then bail out before touching the DB.
1251  $latestNow = $wikiPage->lockAndGetLatest(); // TODO: move to storage service, pass DB
1252  if ( $latestNow != $oldid ) {
1253  // We don't need to roll back, since we did not modify the database yet.
1254  // XXX: Or do we want to rollback, any transaction started by calling
1255  // code will fail? If we want that, we should probably throw an exception.
1256  $dbw->endAtomic( __METHOD__ );
1257 
1258  // Page updated or deleted in the mean time
1259  return $status->fatal( 'edit-conflict' );
1260  }
1261 
1262  // At this point we are now comitted to returning an OK
1263  // status unless some DB query error or other exception comes up.
1264  // This way callers don't have to call rollback() if $status is bad
1265  // unless they actually try to catch exceptions (which is rare).
1266 
1267  // Save revision content and meta-data
1268  $newRevisionRecord = $this->revisionStore->insertRevisionOn( $newRevisionRecord, $dbw );
1269 
1270  // Update page_latest and friends to reflect the new revision
1271  // TODO: move to storage service
1272  $wasRedirect = $this->derivedDataUpdater->wasRedirect();
1273  if ( !$wikiPage->updateRevisionOn( $dbw, $newRevisionRecord, null, $wasRedirect ) ) {
1274  throw new PageUpdateException( "Failed to update page row to use new revision." );
1275  }
1276 
1277  $editResult = $this->getEditResult();
1278  $tags = $this->computeEffectiveTags();
1279  $this->hookRunner->onRevisionFromEditComplete(
1280  $wikiPage,
1281  $newRevisionRecord,
1283  $this->author,
1284  $tags
1285  );
1286 
1287  // Update recentchanges
1288  if ( !( $this->flags & EDIT_SUPPRESS_RC ) ) {
1289  // Add RC row to the DB
1291  $now,
1292  $this->getPage(),
1293  $newRevisionRecord->isMinor(),
1294  $this->author,
1295  $summary->text, // TODO: pass object when that becomes possible
1296  $oldid,
1297  $newRevisionRecord->getTimestamp(),
1298  ( $this->flags & EDIT_FORCE_BOT ) > 0,
1299  '',
1300  $oldRev->getSize(),
1301  $newRevisionRecord->getSize(),
1302  $newRevisionRecord->getId(),
1303  $this->rcPatrolStatus,
1304  $tags,
1305  $editResult
1306  );
1307  }
1308 
1309  $this->userEditTracker->incrementUserEditCount( $this->author );
1310 
1311  $dbw->endAtomic( __METHOD__ );
1312 
1313  // Return the new revision to the caller
1314  $status->value['revision-record'] = $newRevisionRecord;
1315  } else {
1316  // T34948: revision ID must be set to page {{REVISIONID}} and
1317  // related variables correctly. Likewise for {{REVISIONUSER}} (T135261).
1318  // Since we don't insert a new revision into the database, the least
1319  // error-prone way is to reuse given old revision.
1320  $newRevisionRecord = $oldRev;
1321 
1322  $status->warning( 'edit-no-change' );
1323  // Update page_touched as updateRevisionOn() was not called.
1324  // Other cache updates are managed in WikiPage::onArticleEdit()
1325  // via WikiPage::doEditUpdates().
1326  $this->getTitle()->invalidateCache( $now );
1327  }
1328 
1329  // Do secondary updates once the main changes have been committed...
1330  // NOTE: the updates have to be processed before sending the response to the client
1331  // (DeferredUpdates::PRESEND), otherwise the client may already be following the
1332  // HTTP redirect to the standard view before derived data has been created - most
1333  // importantly, before the parser cache has been updated. This would cause the
1334  // content to be parsed a second time, or may cause stale content to be shown.
1336  $this->getAtomicSectionUpdate(
1337  $dbw,
1338  $wikiPage,
1339  $newRevisionRecord,
1340  $summary,
1341  [ 'changed' => $changed, ]
1342  ),
1343  DeferredUpdates::PRESEND
1344  );
1345 
1346  return $status;
1347  }
1348 
1356  private function doCreate( CommentStoreComment $summary ) {
1357  $wikiPage = $this->getWikiPage(); // TODO: use for legacy hooks only!
1358 
1359  if ( !$this->derivedDataUpdater->getSlots()->hasSlot( SlotRecord::MAIN ) ) {
1360  throw new PageUpdateException( 'Must provide a main slot when creating a page!' );
1361  }
1362 
1364  [ 'new' => true, 'revision-record' => null ]
1365  );
1366 
1367  $newRevisionRecord = $this->makeNewRevision(
1368  $summary,
1369  $status
1370  );
1371 
1372  if ( !$status->isOK() ) {
1373  return $status;
1374  }
1375 
1376  $this->buildEditResult( $newRevisionRecord, true );
1377  $now = $newRevisionRecord->getTimestamp();
1378 
1379  $dbw = $this->getDBConnectionRef( DB_PRIMARY );
1380  $dbw->startAtomic( __METHOD__ );
1381 
1382  // Add the page record unless one already exists for the title
1383  // TODO: move to storage service
1384  $newid = $wikiPage->insertOn( $dbw );
1385  if ( $newid === false ) {
1386  $dbw->endAtomic( __METHOD__ );
1387  return $status->fatal( 'edit-already-exists' );
1388  }
1389 
1390  // At this point we are now comitted to returning an OK
1391  // status unless some DB query error or other exception comes up.
1392  // This way callers don't have to call rollback() if $status is bad
1393  // unless they actually try to catch exceptions (which is rare).
1394  $newRevisionRecord->setPageId( $newid );
1395 
1396  // Save the revision text...
1397  $newRevisionRecord = $this->revisionStore->insertRevisionOn( $newRevisionRecord, $dbw );
1398 
1399  // Update the page record with revision data
1400  // TODO: move to storage service
1401  if ( !$wikiPage->updateRevisionOn( $dbw, $newRevisionRecord, 0 ) ) {
1402  throw new PageUpdateException( "Failed to update page row to use new revision." );
1403  }
1404 
1405  $tags = $this->computeEffectiveTags();
1406  $this->hookRunner->onRevisionFromEditComplete(
1407  $wikiPage, $newRevisionRecord, false, $this->author, $tags
1408  );
1409 
1410  // Update recentchanges
1411  if ( !( $this->flags & EDIT_SUPPRESS_RC ) ) {
1412  // Add RC row to the DB
1414  $now,
1415  $this->getPage(),
1416  $newRevisionRecord->isMinor(),
1417  $this->author,
1418  $summary->text, // TODO: pass object when that becomes possible
1419  ( $this->flags & EDIT_FORCE_BOT ) > 0,
1420  '',
1421  $newRevisionRecord->getSize(),
1422  $newRevisionRecord->getId(),
1423  $this->rcPatrolStatus,
1424  $tags
1425  );
1426  }
1427 
1428  $this->userEditTracker->incrementUserEditCount( $this->author );
1429 
1430  if ( $this->usePageCreationLog ) {
1431  // Log the page creation
1432  // @TODO: Do we want a 'recreate' action?
1433  $logEntry = new ManualLogEntry( 'create', 'create' );
1434  $logEntry->setPerformer( $this->author );
1435  $logEntry->setTarget( $this->getPage() );
1436  $logEntry->setComment( $summary->text );
1437  $logEntry->setTimestamp( $now );
1438  $logEntry->setAssociatedRevId( $newRevisionRecord->getId() );
1439  $logEntry->insert();
1440  // Note that we don't publish page creation events to recentchanges
1441  // (i.e. $logEntry->publish()) since this would create duplicate entries,
1442  // one for the edit and one for the page creation.
1443  }
1444 
1445  $dbw->endAtomic( __METHOD__ );
1446 
1447  // Return the new revision to the caller
1448  $status->value['revision-record'] = $newRevisionRecord;
1449 
1450  // Do secondary updates once the main changes have been committed...
1452  $this->getAtomicSectionUpdate(
1453  $dbw,
1454  $wikiPage,
1455  $newRevisionRecord,
1456  $summary,
1457  [ 'created' => true ]
1458  ),
1459  DeferredUpdates::PRESEND
1460  );
1461 
1462  return $status;
1463  }
1464 
1465  private function getAtomicSectionUpdate(
1466  IDatabase $dbw,
1468  RevisionRecord $newRevisionRecord,
1469  CommentStoreComment $summary,
1470  array $hints = []
1471  ) {
1472  return new AtomicSectionUpdate(
1473  $dbw,
1474  __METHOD__,
1475  function () use (
1476  $wikiPage, $newRevisionRecord,
1477  $summary, $hints
1478  ) {
1479  // set debug data
1480  $hints['causeAction'] = 'edit-page';
1481  $hints['causeAgent'] = $this->author->getName();
1482 
1483  $editResult = $this->getEditResult();
1484  $hints['editResult'] = $editResult;
1485 
1486  if ( $editResult->isRevert() ) {
1487  // Should the reverted tag update be scheduled right away?
1488  // The revert is approved if either patrolling is disabled or the
1489  // edit is patrolled or autopatrolled.
1490  $approved = !$this->serviceOptions->get( 'UseRCPatrol' ) ||
1491  $this->rcPatrolStatus === RecentChange::PRC_PATROLLED ||
1492  $this->rcPatrolStatus === RecentChange::PRC_AUTOPATROLLED;
1493 
1494  // Allow extensions to override the patrolling subsystem.
1495  $this->hookRunner->onBeforeRevertedTagUpdate(
1496  $wikiPage,
1497  $this->author,
1498  $summary,
1499  $this->flags,
1500  $newRevisionRecord,
1501  $editResult,
1502  $approved
1503  );
1504  $hints['approved'] = $approved;
1505  }
1506 
1507  // Update links tables, site stats, etc.
1508  $this->derivedDataUpdater->prepareUpdate( $newRevisionRecord, $hints );
1509  $this->derivedDataUpdater->doUpdates();
1510 
1511  $created = $hints['created'] ?? false;
1512  $this->flags |= ( $created ? EDIT_NEW : EDIT_UPDATE );
1513 
1514  // PageSaveComplete replaced old PageContentInsertComplete and
1515  // PageContentSaveComplete hooks since 1.35
1516  $this->hookRunner->onPageSaveComplete(
1517  $wikiPage,
1518  $this->author,
1519  $summary->text,
1520  $this->flags,
1521  $newRevisionRecord,
1522  $editResult
1523  );
1524  }
1525  );
1526  }
1527 
1531  private function getRequiredSlotRoles() {
1532  return $this->slotRoleRegistry->getRequiredRoles( $this->getPage() );
1533  }
1534 
1538  private function getAllowedSlotRoles() {
1539  return $this->slotRoleRegistry->getAllowedRoles( $this->getPage() );
1540  }
1541 
1542  private function ensureRoleAllowed( $role ) {
1543  $allowedRoles = $this->getAllowedSlotRoles();
1544  if ( !in_array( $role, $allowedRoles ) ) {
1545  throw new PageUpdateException( "Slot role `$role` is not allowed." );
1546  }
1547  }
1548 
1549  private function ensureRoleNotRequired( $role ) {
1550  $requiredRoles = $this->getRequiredSlotRoles();
1551  if ( in_array( $role, $requiredRoles ) ) {
1552  throw new PageUpdateException( "Slot role `$role` is required." );
1553  }
1554  }
1555 
1560  private function checkAllRolesAllowed( array $roles, Status $status ) {
1561  $allowedRoles = $this->getAllowedSlotRoles();
1562 
1563  $forbidden = array_diff( $roles, $allowedRoles );
1564  if ( !empty( $forbidden ) ) {
1565  $status->error(
1566  'edit-slots-cannot-add',
1567  count( $forbidden ),
1568  implode( ', ', $forbidden )
1569  );
1570  }
1571  }
1572 
1577  private function checkAllRolesDerived( array $roles, Status $status ) {
1578  $notDerived = array_filter(
1579  $roles,
1580  function ( $role ) {
1581  return !$this->slotRoleRegistry->getRoleHandler( $role )->isDerived();
1582  }
1583  );
1584  if ( $notDerived ) {
1585  $status->error(
1586  'edit-slots-not-derived',
1587  count( $notDerived ),
1588  implode( ', ', $notDerived )
1589  );
1590  }
1591  }
1592 
1597  private function checkNoRolesRequired( array $roles, Status $status ) {
1598  $requiredRoles = $this->getRequiredSlotRoles();
1599 
1600  $needed = array_diff( $roles, $requiredRoles );
1601  if ( !empty( $needed ) ) {
1602  $status->error(
1603  'edit-slots-cannot-remove',
1604  count( $needed ),
1605  implode( ', ', $needed )
1606  );
1607  }
1608  }
1609 
1614  private function checkAllRequiredRoles( array $roles, Status $status ) {
1615  $requiredRoles = $this->getRequiredSlotRoles();
1616 
1617  $missing = array_diff( $requiredRoles, $roles );
1618  if ( !empty( $missing ) ) {
1619  $status->error(
1620  'edit-slots-missing',
1621  count( $missing ),
1622  implode( ', ', $missing )
1623  );
1624  }
1625  }
1626 
1627 }
MediaWiki\Revision\RevisionRecord\RAW
const RAW
Definition: RevisionRecord.php:64
Page\PageIdentity
Interface for objects (potentially) representing an editable wiki page.
Definition: PageIdentity.php:64
MediaWiki\Storage\PageUpdater\markAsRevert
markAsRevert(int $revertMethod, int $newestRevertedRevId, int $revertAfterRevId=null)
Marks this edit as a revert and applies relevant information.
Definition: PageUpdater.php:570
MediaWiki\Storage\PageUpdater\addTags
addTags(array $tags)
Sets tags to apply to this update.
Definition: PageUpdater.php:611
ContentHandler
A content handler knows how do deal with a specific type of content on a wiki page.
Definition: ContentHandler.php:60
EDIT_AUTOSUMMARY
const EDIT_AUTOSUMMARY
Definition: Defines.php:131
CommentStoreComment\newUnsavedComment
static newUnsavedComment( $comment, array $data=null)
Create a new, unsaved CommentStoreComment.
Definition: CommentStoreComment.php:67
MediaWiki\Revision\RevisionAccessException
Exception representing a failure to look up a revision.
Definition: RevisionAccessException.php:37
MediaWiki\Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:47
MediaWiki\Storage\PageUpdater\$tags
array $tags
Definition: PageUpdater.php:164
StatusValue\newFatal
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:70
MediaWiki\Storage\PageUpdater\computeEffectiveTags
computeEffectiveTags()
Definition: PageUpdater.php:646
RecentChange\notifyEdit
static notifyEdit( $timestamp, $page, $minor, $user, $comment, $oldId, $lastTimestamp, $bot, $ip='', $oldSize=0, $newSize=0, $newId=0, $patrol=0, $tags=[], EditResult $editResult=null)
Makes an entry in the database corresponding to an edit.
Definition: RecentChange.php:727
MediaWiki\Storage\PageUpdater\$usePageCreationLog
bool $usePageCreationLog
whether to create a log entry for new page creations.
Definition: PageUpdater.php:159
MediaWiki\Storage\PageUpdater\CONSTRUCTOR_OPTIONS
const CONSTRUCTOR_OPTIONS
Options that have to be present in the ServiceOptions object passed to the constructor.
Definition: PageUpdater.php:86
MediaWiki\Storage\PageUpdater\saveRevision
saveRevision(CommentStoreComment $summary, int $flags=0)
Change an existing article or create a new article.
Definition: PageUpdater.php:767
MediaWiki\Storage\PageUpdater\checkNoRolesRequired
checkNoRolesRequired(array $roles, Status $status)
Definition: PageUpdater.php:1597
MediaWiki\Storage\PageUpdater\$flags
int $flags
Definition: PageUpdater.php:194
StatusValue\error
error( $message,... $parameters)
Add an error, do not set fatal flag This can be used for non-fatal errors.
Definition: StatusValue.php:250
MediaWiki\Storage\PageUpdater\inheritSlot
inheritSlot(SlotRecord $originalSlot)
Explicitly inherit a slot from some earlier revision.
Definition: PageUpdater.php:518
MediaWiki\Revision\RevisionStore
Service for looking up page revisions.
Definition: RevisionStore.php:88
MediaWiki\Storage\PageUpdater\doCreate
doCreate(CommentStoreComment $summary)
Definition: PageUpdater.php:1356
MediaWiki\Storage\PageUpdater\setOriginalRevisionId
setOriginalRevisionId( $originalRevId)
Sets the ID of an earlier revision that is being repeated or restored by this update.
Definition: PageUpdater.php:553
MediaWiki\Storage\PageUpdater\getWikiId
getWikiId()
Definition: PageUpdater.php:368
RecentChange
Utility class for creating new RC entries.
Definition: RecentChange.php:80
MediaWiki\Storage\PageUpdater\updateRevision
updateRevision(int $revId=0)
Updates derived slots of an existing article.
Definition: PageUpdater.php:901
MediaWiki\Storage\PageUpdater\getExplicitTags
getExplicitTags()
Returns the list of tags set using the addTag() method.
Definition: PageUpdater.php:639
StatusValue\warning
warning( $message,... $parameters)
Add a new warning.
Definition: StatusValue.php:234
DeferredUpdates\addUpdate
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the pending update queue for execution at the appropriate time.
Definition: DeferredUpdates.php:119
MediaWiki\Storage\PageUpdater\getEditResult
getEditResult()
Returns the EditResult associated with this PageUpdater.
Definition: PageUpdater.php:588
WikiPage
Class representing a MediaWiki article and history.
Definition: WikiPage.php:60
MediaWiki\Storage\PageUpdater\$userGroupManager
UserGroupManager $userGroupManager
Definition: PageUpdater.php:140
StatusValue\fatal
fatal( $message,... $parameters)
Add an error and set OK to false, indicating that the operation as a whole was fatal.
Definition: StatusValue.php:266
MediaWiki\Storage\PageUpdater\setUsePageCreationLog
setUsePageCreationLog( $use)
Whether to create a log entry for new page creations.
Definition: PageUpdater.php:363
MediaWiki\Storage\PageUpdater\makeAutoSummary
makeAutoSummary()
Definition: PageUpdater.php:712
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1186
MediaWiki\Storage\PageUpdater\getParentContent
getParentContent( $role)
Returns the content of the given slot of the parent revision, with no audience checks applied.
Definition: PageUpdater.php:676
MediaWiki\Storage\PageUpdater\$wikiPage
WikiPage $wikiPage
Definition: PageUpdater.php:99
MediaWiki\Storage\PageUpdater\$userEditTracker
UserEditTracker $userEditTracker
Definition: PageUpdater.php:137
User\newFromIdentity
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:683
MediaWiki\Storage\PageUpdater\$loadBalancer
ILoadBalancer $loadBalancer
Definition: PageUpdater.php:109
MediaWiki\Storage\PageUpdater\wasCommitted
wasCommitted()
Whether saveRevision() has been called on this instance.
Definition: PageUpdater.php:971
MediaWiki\Storage\EditResultBuilder\CONSTRUCTOR_OPTIONS
const CONSTRUCTOR_OPTIONS
Definition: EditResultBuilder.php:40
MediaWiki\Storage\PageUpdater\$editResultBuilder
EditResultBuilder $editResultBuilder
Definition: PageUpdater.php:179
MediaWiki\User\UserIdentity
Interface for objects representing user identity.
Definition: UserIdentity.php:39
Wikimedia\Rdbms\IDatabase
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
MediaWiki\Storage\PageUpdater\ensureRoleAllowed
ensureRoleAllowed( $role)
Definition: PageUpdater.php:1542
Status
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:44
MediaWiki\Revision\SlotRecord\MAIN
const MAIN
Definition: SlotRecord.php:43
MediaWiki\Storage\PageUpdater\makeNewRevision
makeNewRevision(CommentStoreComment $comment, Status $status)
Constructs a MutableRevisionRecord based on the Content prepared by the DerivedPageDataUpdater.
Definition: PageUpdater.php:1058
MediaWiki\User\UserGroupManager
Managers user groups.
Definition: UserGroupManager.php:52
MediaWiki\Storage\PageUpdater\$revisionStore
RevisionStore $revisionStore
Definition: PageUpdater.php:114
MediaWiki\Storage\EditResult\getOriginalRevisionId
getOriginalRevisionId()
Returns the ID of an earlier revision that is being repeated or restored.
Definition: EditResult.php:188
MediaWiki\Storage\PageUpdater\wasSuccessful
wasSuccessful()
Whether saveRevision() completed successfully.
Definition: PageUpdater.php:1007
MWException
MediaWiki exception.
Definition: MWException.php:29
WikiPage\updateRevisionOn
updateRevisionOn( $dbw, RevisionRecord $revision, $lastRevision=null, $lastRevIsRedirect=null)
Update the page record to point to a newly saved revision.
Definition: WikiPage.php:1407
MediaWiki\Storage\PageUpdater\doUpdate
doUpdate(RevisionRecord $revision)
Update derived slots in an existing revision.
Definition: PageUpdater.php:1150
MediaWiki\Storage\PageUpdater\setContent
setContent( $role, Content $content)
Set the new content for the given slot role.
Definition: PageUpdater.php:483
MediaWiki\Config\ServiceOptions
A class for passing options to services.
Definition: ServiceOptions.php:27
MediaWiki\Storage\PageUpdater\getTitle
getTitle()
Definition: PageUpdater.php:392
StatusValue\isOK
isOK()
Returns whether the operation completed.
Definition: StatusValue.php:131
WikiPage\insertOn
insertOn( $dbw, $pageId=null)
Insert a new empty page record for this article.
Definition: WikiPage.php:1362
MediaWiki\Storage\PageUpdater\setFlags
setFlags(int $flags)
Sets any flags to use when performing the update.
Definition: PageUpdater.php:287
DeferredUpdates
Class for managing the deferral of updates within the scope of a PHP script invocation.
Definition: DeferredUpdates.php:82
MediaWiki\Storage\PageUpdater\getNewRevision
getNewRevision()
The new revision created by saveRevision(), or null if saveRevision() has not yet been called,...
Definition: PageUpdater.php:1039
MediaWiki\Storage\PageUpdater\getWikiPage
getWikiPage()
Definition: PageUpdater.php:400
StatusValue\merge
merge( $other, $overwriteValue=false)
Merge another status object into this one.
Definition: StatusValue.php:278
EDIT_NEW
const EDIT_NEW
Definition: Defines.php:125
$title
$title
Definition: testCompression.php:38
MediaWiki\Storage\PageUpdater\removeSlot
removeSlot( $role)
Removes the slot with the given role.
Definition: PageUpdater.php:537
MediaWiki\Revision\RevisionRecord\getComment
getComment( $audience=self::FOR_PUBLIC, Authority $performer=null)
Fetch revision comment, if it's available to the specified audience.
Definition: RevisionRecord.php:413
MediaWiki\Storage\EditResultBuilder
Builder class for the EditResult object.
Definition: EditResultBuilder.php:38
MediaWiki\Storage\PageUpdater\getPage
getPage()
Get the page we're currently updating.
Definition: PageUpdater.php:385
MediaWiki\Storage\PageUpdater\$serviceOptions
ServiceOptions $serviceOptions
Definition: PageUpdater.php:189
MediaWiki\Storage\PageUpdater\$titleFormatter
TitleFormatter $titleFormatter
Definition: PageUpdater.php:143
MediaWiki\Storage\PageUpdater\getRequiredSlotRoles
getRequiredSlotRoles()
Definition: PageUpdater.php:1531
MediaWiki\Storage\EditResult
Object for storing information about the effects of an edit.
Definition: EditResult.php:38
WikiPage\lockAndGetLatest
lockAndGetLatest()
Lock the page row for this title+id and return page_latest (or 0)
Definition: WikiPage.php:2728
MediaWiki\Storage\PageUpdater\checkAllRolesDerived
checkAllRolesDerived(array $roles, Status $status)
Definition: PageUpdater.php:1577
MediaWiki\Storage\PageUpdater\$hookContainer
HookContainer $hookContainer
Definition: PageUpdater.php:134
MediaWiki\Storage\PageUpdater\checkAllRolesAllowed
checkAllRolesAllowed(array $roles, Status $status)
Definition: PageUpdater.php:1560
RecentChange\PRC_PATROLLED
const PRC_PATROLLED
Definition: RecentChange.php:92
MediaWiki\Storage\PageUpdater\getAllowedSlotRoles
getAllowedSlotRoles()
Definition: PageUpdater.php:1538
AtomicSectionUpdate
Deferrable Update for closure/callback updates via IDatabase::doAtomicSection()
Definition: AtomicSectionUpdate.php:9
MediaWiki\Storage\RevisionSlotsUpdate
Value object representing a modification of revision slots.
Definition: RevisionSlotsUpdate.php:36
MediaWiki\Storage\PageUpdater\$softwareTags
string[] $softwareTags
Definition: PageUpdater.php:197
$content
$content
Definition: router.php:76
ContentHandler\getLocalizedName
static getLocalizedName( $name, Language $lang=null)
Returns the localized name for a given content model.
Definition: ContentHandler.php:307
MediaWiki\Content\IContentHandlerFactory
Definition: IContentHandlerFactory.php:10
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:82
MediaWiki\Revision\MutableRevisionRecord
Definition: MutableRevisionRecord.php:44
MediaWiki\Storage\PageUpdater\addSoftwareTag
addSoftwareTag(string $tag)
Sets software tag to this update.
Definition: PageUpdater.php:627
MediaWiki\Storage\PageUpdater\$contentHandlerFactory
IContentHandlerFactory $contentHandlerFactory
Definition: PageUpdater.php:124
DB_PRIMARY
const DB_PRIMARY
Definition: defines.php:27
MediaWiki\Storage\PageUpdater
Controller-like object for creating and updating pages by creating new revisions.
Definition: PageUpdater.php:79
MediaWiki\Storage\PageUpdater\getDBConnectionRef
getDBConnectionRef( $mode)
Definition: PageUpdater.php:377
MediaWiki\Storage\PageUpdater\$useAutomaticEditSummaries
bool $useAutomaticEditSummaries
see $wgUseAutomaticEditSummaries
Definition: PageUpdater.php:149
MediaWiki\Storage\PageUpdater\hasEditConflict
hasEditConflict( $expectedParentRevision)
Checks whether this update conflicts with another update performed between the client loading data to...
Definition: PageUpdater.php:438
EDIT_SUPPRESS_RC
const EDIT_SUPPRESS_RC
Definition: Defines.php:128
RecentChange\PRC_AUTOPATROLLED
const PRC_AUTOPATROLLED
Definition: RecentChange.php:93
MediaWiki\Storage\PageUpdater\$hookRunner
HookRunner $hookRunner
Definition: PageUpdater.php:129
MediaWiki\Storage
Definition: BlobAccessException.php:23
Wikimedia\Rdbms\DBUnexpectedError
@newable
Definition: DBUnexpectedError.php:29
MediaWiki\Storage\PageUpdater\$author
UserIdentity $author
Definition: PageUpdater.php:94
EDIT_UPDATE
const EDIT_UPDATE
Definition: Defines.php:126
Content
Base interface for content objects.
Definition: Content.php:35
MediaWiki\Storage\PageUpdater\$status
Status null $status
Definition: PageUpdater.php:174
MediaWiki\Revision\MutableRevisionRecord\newUpdatedRevisionRecord
static newUpdatedRevisionRecord(RevisionRecord $revision, array $slots)
Returns a MutableRevisionRecord which is an updated version of $revision with $slots added.
Definition: MutableRevisionRecord.php:77
Wikimedia\Rdbms\DBConnRef
Helper class used for automatically marking an IDatabase connection as reusable (once it no longer ma...
Definition: DBConnRef.php:29
MediaWiki\Storage\PageUpdater\$slotsUpdate
RevisionSlotsUpdate $slotsUpdate
Definition: PageUpdater.php:169
Title
Represents a title within MediaWiki.
Definition: Title.php:47
MediaWiki\Revision\RevisionRecord\getId
getId( $wikiId=self::LOCAL)
Get revision ID.
Definition: RevisionRecord.php:279
MediaWiki\User\UserEditTracker
Track info about user edit counts and timings.
Definition: UserEditTracker.php:21
MediaWiki\Storage\PageUpdater\grabParentRevision
grabParentRevision()
Returns the revision that was the page's current revision when grabParentRevision() was first called.
Definition: PageUpdater.php:472
MediaWiki\Storage\PageUpdater\addTag
addTag(string $tag)
Sets a tag to apply to this update.
Definition: PageUpdater.php:599
RecentChange\notifyNew
static notifyNew( $timestamp, $page, $minor, $user, $comment, $bot, $ip='', $size=0, $newId=0, $patrol=0, $tags=[])
Makes an entry in the database corresponding to page creation.
Definition: RecentChange.php:808
MediaWiki\Storage\PageUpdater\getStatus
getStatus()
The Status object indicating whether saveRevision() was successful, or null if saveRevision() was not...
Definition: PageUpdater.php:998
TitleFormatter
A title formatter service for MediaWiki.
Definition: TitleFormatter.php:35
MediaWiki\Storage\PageUpdater\setRcPatrolStatus
setRcPatrolStatus( $status)
Sets the "patrolled" status of the edit.
Definition: PageUpdater.php:350
MediaWiki\Storage\PageUpdater\$rcPatrolStatus
int $rcPatrolStatus
the RC patrol status the new revision should be marked with.
Definition: PageUpdater.php:154
RecentChange\PRC_UNPATROLLED
const PRC_UNPATROLLED
Definition: RecentChange.php:91
MediaWiki\Storage\EditResult\isRevert
isRevert()
Whether the edit was a revert, not necessarily exact.
Definition: EditResult.php:218
MediaWiki\Storage\PageUpdater\prepareUpdate
prepareUpdate(int $flags=0)
Prepare the update.
Definition: PageUpdater.php:301
MediaWiki\Storage\PageUpdater\ensureRoleNotRequired
ensureRoleNotRequired( $role)
Definition: PageUpdater.php:1549
MediaWiki\Storage\PageUpdater\getContentHandler
getContentHandler( $role)
Definition: PageUpdater.php:690
ManualLogEntry
Class for creating new log entries and inserting them into the database.
Definition: ManualLogEntry.php:44
MediaWiki\Storage\PageUpdater\buildEditResult
buildEditResult(RevisionRecord $revision, bool $isNew)
Builds the EditResult for this update.
Definition: PageUpdater.php:1133
MediaWiki\Storage\PageUpdater\doModify
doModify(CommentStoreComment $summary)
Definition: PageUpdater.php:1203
MediaWiki\Storage\PageUpdater\$derivedDataUpdater
DerivedPageDataUpdater $derivedDataUpdater
Definition: PageUpdater.php:104
MediaWiki\Storage\PageUpdater\checkAllRequiredRoles
checkAllRequiredRoles(array $roles, Status $status)
Definition: PageUpdater.php:1614
MediaWiki\Revision\SlotRecord\getRole
getRole()
Returns the role of the slot.
Definition: SlotRecord.php:507
MediaWiki\Revision\SlotRoleRegistry
A registry service for SlotRoleHandlers, used to define which slot roles are available on which page.
Definition: SlotRoleRegistry.php:48
MediaWiki\Config\ServiceOptions\get
get( $key)
Definition: ServiceOptions.php:93
MediaWiki\HookContainer\HookContainer
HookContainer class.
Definition: HookContainer.php:45
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:1043
MediaWiki\HookContainer\HookRunner
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:556
MediaWiki\Revision\SlotRecord\newInherited
static newInherited(SlotRecord $slot)
Constructs a new SlotRecord for a new revision, inheriting the content of the given SlotRecord of a p...
Definition: SlotRecord.php:129
MediaWiki\Storage\PageUpdater\toLegacyUser
static toLegacyUser(UserIdentity $user)
Definition: PageUpdater.php:323
EDIT_FORCE_BOT
const EDIT_FORCE_BOT
Definition: Defines.php:129
MediaWiki\Storage\PageUpdater\__construct
__construct(UserIdentity $author, WikiPage $wikiPage, DerivedPageDataUpdater $derivedDataUpdater, ILoadBalancer $loadBalancer, RevisionStore $revisionStore, SlotRoleRegistry $slotRoleRegistry, IContentHandlerFactory $contentHandlerFactory, HookContainer $hookContainer, UserEditTracker $userEditTracker, UserGroupManager $userGroupManager, TitleFormatter $titleFormatter, ServiceOptions $serviceOptions, array $softwareTags)
Definition: PageUpdater.php:215
EDIT_MINOR
const EDIT_MINOR
Definition: Defines.php:127
MediaWiki\Storage\PageUpdateException
Exception representing a failure to update a page entry.
Definition: PageUpdateException.php:33
MediaWiki\Storage\DerivedPageDataUpdater
A handle for managing updates for derived page data on edit, import, purge, etc.
Definition: DerivedPageDataUpdater.php:105
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:68
DeferredUpdates\addCallableUpdate
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add an update to the pending update queue that invokes the specified callback when run.
Definition: DeferredUpdates.php:145
MediaWiki\Storage\EditResult\getRevertTags
getRevertTags()
Returns an array of revert-related tags that were applied automatically to this edit.
Definition: EditResult.php:261
MediaWiki\Storage\PageUpdater\$slotRoleRegistry
SlotRoleRegistry $slotRoleRegistry
Definition: PageUpdater.php:119
CommentStoreComment
Value object for a comment stored by CommentStore.
Definition: CommentStoreComment.php:30
MediaWiki\Storage\PageUpdater\setUseAutomaticEditSummaries
setUseAutomaticEditSummaries( $useAutomaticEditSummaries)
Can be used to enable or disable automatic summaries that are applied to certain kinds of changes,...
Definition: PageUpdater.php:335
MediaWiki\Storage\PageUpdater\$editResult
EditResult null $editResult
Definition: PageUpdater.php:184
MediaWiki\Storage\PageUpdater\isNew
isNew()
Whether saveRevision() was called and created a new page.
Definition: PageUpdater.php:1016
Wikimedia\Rdbms\ILoadBalancer
Database cluster connection, tracking, load balancing, and transaction manager interface.
Definition: ILoadBalancer.php:81
MediaWiki\Storage\PageUpdater\isUnchanged
isUnchanged()
Whether saveRevision() did not create a revision because the content didn't change (null-edit).
Definition: PageUpdater.php:1027
MediaWiki\Storage\PageUpdater\setSlot
setSlot(SlotRecord $slot)
Set the new slot for the given slot role.
Definition: PageUpdater.php:496
MediaWiki\Revision\SlotRecord
Value object representing a content slot associated with a page revision.
Definition: SlotRecord.php:40
MediaWiki\Storage\PageUpdater\getAtomicSectionUpdate
getAtomicSectionUpdate(IDatabase $dbw, WikiPage $wikiPage, RevisionRecord $newRevisionRecord, CommentStoreComment $summary, array $hints=[])
Definition: PageUpdater.php:1465
MediaWiki\Config\ServiceOptions\assertRequiredOptions
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys,...
Definition: ServiceOptions.php:71
EDIT_INTERNAL
const EDIT_INTERNAL
Definition: Defines.php:132