MediaWiki  master
PageUpdater.php
Go to the documentation of this file.
1 <?php
25 namespace MediaWiki\Storage;
26 
28 use ChangeTags;
30 use Content;
31 use ContentHandler;
32 use DeferredUpdates;
33 use InvalidArgumentException;
34 use LogicException;
35 use ManualLogEntry;
51 use MWException;
52 use RecentChange;
53 use RuntimeException;
54 use Status;
55 use Title;
56 use TitleFormatter;
57 use User;
58 use Wikimedia\Assert\Assert;
63 use WikiPage;
64 
81 class PageUpdater {
82 
88  public const CONSTRUCTOR_OPTIONS = [
89  'ManualRevertSearchRadius',
90  'UseRCPatrol',
91  ];
92 
96  private $author;
97 
101  private $wikiPage;
102 
107 
111  private $loadBalancer;
112 
116  private $revisionStore;
117 
122 
127 
131  private $hookRunner;
132 
136  private $hookContainer;
137 
140 
143 
146 
152 
157 
161  private $usePageCreationLog = true;
162 
166  private $tags = [];
167 
171  private $slotsUpdate;
172 
176  private $status = null;
177 
182 
186  private $editResult = null;
187 
192 
196  private $flags = 0;
197 
199  private $softwareTags = [];
200 
217  public function __construct(
230  array $softwareTags
231  ) {
232  $serviceOptions->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
233  $this->serviceOptions = $serviceOptions;
234 
235  $this->author = $author;
236  $this->wikiPage = $wikiPage;
237  $this->derivedDataUpdater = $derivedDataUpdater;
238 
239  $this->loadBalancer = $loadBalancer;
240  $this->revisionStore = $revisionStore;
241  $this->slotRoleRegistry = $slotRoleRegistry;
242  $this->contentHandlerFactory = $contentHandlerFactory;
243  $this->hookContainer = $hookContainer;
244  $this->hookRunner = new HookRunner( $hookContainer );
245  $this->userEditTracker = $userEditTracker;
246  $this->userGroupManager = $userGroupManager;
247  $this->titleFormatter = $titleFormatter;
248 
249  $this->slotsUpdate = new RevisionSlotsUpdate();
250  $this->editResultBuilder = new EditResultBuilder(
253  new ServiceOptions(
255  [
256  'ManualRevertSearchRadius' =>
257  $serviceOptions->get( 'ManualRevertSearchRadius' )
258  ]
259  )
260  );
261  $this->softwareTags = $softwareTags;
262  }
263 
289  public function setFlags( int $flags ) {
290  $this->flags |= $flags;
291  return $this;
292  }
293 
304  public function prepareUpdate( int $flags = 0 ): PreparedUpdate {
305  $this->setFlags( $flags );
306 
307  // Load the data from the primary database if needed. Needed to check flags.
308  $this->grabParentRevision();
309  if ( !$this->derivedDataUpdater->isUpdatePrepared() ) {
310  // Avoid statsd noise and wasted cycles check the edit stash (T136678)
311  $useStashed = !( ( $this->flags & EDIT_INTERNAL ) || ( $this->flags & EDIT_FORCE_BOT ) );
312  // Prepare the update. This performs PST and generates the canonical ParserOutput.
313  $this->derivedDataUpdater->prepareContent(
314  $this->author,
315  $this->slotsUpdate,
316  $useStashed
317  );
318  }
319 
321  }
322 
328  private static function toLegacyUser( UserIdentity $user ) {
329  return User::newFromIdentity( $user );
330  }
331 
341  $this->useAutomaticEditSummaries = $useAutomaticEditSummaries;
342  return $this;
343  }
344 
355  public function setRcPatrolStatus( $status ) {
356  $this->rcPatrolStatus = $status;
357  return $this;
358  }
359 
368  public function setUsePageCreationLog( $use ) {
369  $this->usePageCreationLog = $use;
370  return $this;
371  }
372 
373  private function getWikiId() {
374  return false; // TODO: get from RevisionStore!
375  }
376 
382  private function getDBConnectionRef( $mode ) {
383  return $this->loadBalancer->getConnectionRef( $mode, [], $this->getWikiId() );
384  }
385 
390  public function getPage(): PageIdentity {
391  return $this->wikiPage;
392  }
393 
397  private function getTitle() {
398  // NOTE: eventually, we won't get a WikiPage passed into the constructor any more
399  return $this->wikiPage->getTitle();
400  }
401 
405  private function getWikiPage() {
406  // NOTE: eventually, we won't get a WikiPage passed into the constructor any more
407  return $this->wikiPage;
408  }
409 
443  public function hasEditConflict( $expectedParentRevision ) {
444  $parent = $this->grabParentRevision();
445  $parentId = $parent ? $parent->getId() : 0;
446 
447  return $parentId !== $expectedParentRevision;
448  }
449 
477  public function grabParentRevision() {
478  return $this->derivedDataUpdater->grabCurrentRevision();
479  }
480 
488  public function setContent( $role, Content $content ) {
489  $this->ensureRoleAllowed( $role );
490 
491  $this->slotsUpdate->modifyContent( $role, $content );
492  return $this;
493  }
494 
501  public function setSlot( SlotRecord $slot ) {
502  $this->ensureRoleAllowed( $slot->getRole() );
503 
504  $this->slotsUpdate->modifySlot( $slot );
505  return $this;
506  }
507 
523  public function inheritSlot( SlotRecord $originalSlot ) {
524  // NOTE: slots can be inherited even if the role is not "allowed" on the title.
525  // NOTE: this slot is inherited from some other revision, but it's
526  // a "modified" slot for the RevisionSlotsUpdate and DerivedPageDataUpdater,
527  // since it's not implicitly inherited from the parent revision.
528  $inheritedSlot = SlotRecord::newInherited( $originalSlot );
529  $this->slotsUpdate->modifySlot( $inheritedSlot );
530  return $this;
531  }
532 
542  public function removeSlot( $role ) {
543  $this->ensureRoleNotRequired( $role );
544 
545  $this->slotsUpdate->removeSlot( $role );
546  }
547 
558  public function setOriginalRevisionId( $originalRevId ) {
559  $this->editResultBuilder->setOriginalRevision( $originalRevId );
560  return $this;
561  }
562 
575  public function markAsRevert(
576  int $revertMethod,
577  int $newestRevertedRevId,
578  int $revertAfterRevId = null
579  ) {
580  $this->editResultBuilder->markAsRevert(
581  $revertMethod, $newestRevertedRevId, $revertAfterRevId
582  );
583  return $this;
584  }
585 
593  public function getEditResult(): ?EditResult {
594  return $this->editResult;
595  }
596 
604  public function addTag( string $tag ) {
605  $this->tags[] = trim( $tag );
606  return $this;
607  }
608 
616  public function addTags( array $tags ) {
617  Assert::parameterElementType( 'string', $tags, '$tags' );
618  foreach ( $tags as $tag ) {
619  $this->addTag( $tag );
620  }
621  return $this;
622  }
623 
632  public function addSoftwareTag( string $tag ): self {
633  if ( in_array( $tag, $this->softwareTags ) ) {
634  $this->addTag( $tag );
635  }
636  return $this;
637  }
638 
644  public function getExplicitTags() {
645  return $this->tags;
646  }
647 
651  private function computeEffectiveTags() {
652  $tags = $this->tags;
653  $editResult = $this->getEditResult();
654 
655  foreach ( $this->slotsUpdate->getModifiedRoles() as $role ) {
656  $old_content = $this->getParentContent( $role );
657 
658  $handler = $this->getContentHandler( $role );
659  $content = $this->slotsUpdate->getModifiedSlot( $role )->getContent();
660 
661  // TODO: MCR: Do this for all slots. Also add tags for removing roles!
662  $tag = $handler->getChangeTag( $old_content, $content, $this->flags );
663  // If there is no applicable tag, null is returned, so we need to check
664  if ( $tag ) {
665  $tags[] = $tag;
666  }
667  }
668 
669  $tags = array_merge( $tags, $editResult->getRevertTags() );
670 
671  return array_unique( $tags );
672  }
673 
681  private function getParentContent( $role ) {
682  $parent = $this->grabParentRevision();
683 
684  if ( $parent && $parent->hasSlot( $role ) ) {
685  return $parent->getContent( $role, RevisionRecord::RAW );
686  }
687 
688  return null;
689  }
690 
695  private function getContentHandler( $role ) {
696  if ( $this->slotsUpdate->isModifiedSlot( $role ) ) {
697  $slot = $this->slotsUpdate->getModifiedSlot( $role );
698  } else {
699  $parent = $this->grabParentRevision();
700 
701  if ( $parent ) {
702  $slot = $parent->getSlot( $role, RevisionRecord::RAW );
703  } else {
704  throw new RevisionAccessException(
705  'No such slot: {role}',
706  [ 'role' => $role ]
707  );
708  }
709  }
710 
711  return $this->contentHandlerFactory->getContentHandler( $slot->getModel() );
712  }
713 
717  private function makeAutoSummary() {
718  if ( !$this->useAutomaticEditSummaries || ( $this->flags & EDIT_AUTOSUMMARY ) === 0 ) {
720  }
721 
722  // NOTE: this generates an auto-summary for SOME RANDOM changed slot!
723  // TODO: combine auto-summaries for multiple slots!
724  // XXX: this logic should not be in the storage layer!
725  $roles = $this->slotsUpdate->getModifiedRoles();
726  $role = reset( $roles );
727 
728  if ( $role === false ) {
730  }
731 
732  $handler = $this->getContentHandler( $role );
733  $content = $this->slotsUpdate->getModifiedSlot( $role )->getContent();
734  $old_content = $this->getParentContent( $role );
735  $summary = $handler->getAutosummary( $old_content, $content, $this->flags );
736 
737  return CommentStoreComment::newUnsavedComment( $summary );
738  }
739 
772  public function saveRevision( CommentStoreComment $summary, int $flags = 0 ) {
773  $this->setFlags( $flags );
774 
775  if ( $this->wasCommitted() ) {
776  throw new RuntimeException(
777  'saveRevision() or updateRevision() has already been called on this PageUpdater!'
778  );
779  }
780 
781  // Low-level check
782  if ( $this->getPage()->getDBkey() === '' ) {
783  throw new RuntimeException( 'Something is trying to edit an article with an empty title' );
784  }
785 
786  // NOTE: slots can be inherited even if the role is not "allowed" on the title.
788  $this->checkAllRolesAllowed(
789  $this->slotsUpdate->getModifiedRoles(),
790  $status
791  );
792  $this->checkNoRolesRequired(
793  $this->slotsUpdate->getRemovedRoles(),
794  $status
795  );
796 
797  if ( !$status->isOK() ) {
798  return null;
799  }
800 
801  // Make sure the given content is allowed in the respective slots of this page
802  foreach ( $this->slotsUpdate->getModifiedRoles() as $role ) {
803  $slot = $this->slotsUpdate->getModifiedSlot( $role );
804  $roleHandler = $this->slotRoleRegistry->getRoleHandler( $role );
805 
806  if ( !$roleHandler->isAllowedModel( $slot->getModel(), $this->getPage() ) ) {
807  $contentHandler = $this->contentHandlerFactory
808  ->getContentHandler( $slot->getModel() );
809  $this->status = Status::newFatal( 'content-not-allowed-here',
810  ContentHandler::getLocalizedName( $contentHandler->getModelID() ),
811  $this->titleFormatter->getPrefixedText( $this->getPage() ),
812  wfMessage( $roleHandler->getNameMessageKey() )
813  // TODO: defer message lookup to caller
814  );
815  return null;
816  }
817  }
818 
819  // Load the data from the primary database if needed. Needed to check flags.
820  // NOTE: This grabs the parent revision as the CAS token, if grabParentRevision
821  // wasn't called yet. If the page is modified by another process before we are done with
822  // it, this method must fail (with status 'edit-conflict')!
823  // NOTE: The parent revision may be different from the edit's base revision.
824  $this->prepareUpdate();
825 
826  // Detect whether update or creation should be performed.
827  if ( !( $this->flags & EDIT_NEW ) && !( $this->flags & EDIT_UPDATE ) ) {
828  $this->flags |= ( $this->derivedDataUpdater->pageExisted() ) ? EDIT_UPDATE : EDIT_NEW;
829  }
830 
831  // Trigger pre-save hook (using provided edit summary)
832  $renderedRevision = $this->derivedDataUpdater->getRenderedRevision();
833  $hookStatus = Status::newGood( [] );
834  $allowedByHook = $this->hookRunner->onMultiContentSave(
835  $renderedRevision, $this->author, $summary, $this->flags, $hookStatus
836  );
837  if ( $allowedByHook && $this->hookContainer->isRegistered( 'PageContentSave' ) ) {
838  // Also run the legacy hook.
839  // NOTE: WikiPage should only be used for the legacy hook,
840  // and only if something uses the legacy hook.
841  $mainContent = $this->derivedDataUpdater->getSlots()->getContent( SlotRecord::MAIN );
842 
843  $legacyUser = self::toLegacyUser( $this->author );
844 
845  // Deprecated since 1.35.
846  $allowedByHook = $this->hookRunner->onPageContentSave(
847  $this->getWikiPage(), $legacyUser, $mainContent, $summary,
848  $this->flags & EDIT_MINOR, null, null, $this->flags, $hookStatus
849  );
850  }
851 
852  if ( !$allowedByHook ) {
853  // The hook has prevented this change from being saved.
854  if ( $hookStatus->isOK() ) {
855  // Hook returned false but didn't call fatal(); use generic message
856  $hookStatus->fatal( 'edit-hook-aborted' );
857  }
858 
859  $this->status = $hookStatus;
860  return null;
861  }
862 
863  // Provide autosummaries if one is not provided and autosummaries are enabled
864  // XXX: $summary == null seems logical, but the empty string may actually come from the user
865  // XXX: Move this logic out of the storage layer! It does not belong here! Use a callback?
866  if ( $summary->text === '' && $summary->data === null ) {
867  $summary = $this->makeAutoSummary();
868  }
869 
870  // Actually create the revision and create/update the page.
871  // Do NOT yet set $this->status!
872  if ( $this->flags & EDIT_UPDATE ) {
873  $status = $this->doModify( $summary );
874  } else {
875  $status = $this->doCreate( $summary );
876  }
877 
878  // Promote user to any groups they meet the criteria for
880  $this->userGroupManager->addUserToAutopromoteOnceGroups( $this->author, 'onEdit' );
881  // Also run 'onView' for backwards compatibility
882  $this->userGroupManager->addUserToAutopromoteOnceGroups( $this->author, 'onView' );
883  } );
884 
885  // NOTE: set $this->status only after all hooks have been called,
886  // so wasCommitted doesn't return true when called indirectly from a hook handler!
887  $this->status = $status;
888 
889  // TODO: replace bad status with Exceptions!
890  return ( $this->status && $this->status->isOK() )
891  ? $this->status->value['revision-record']
892  : null;
893  }
894 
906  public function updateRevision( int $revId = 0 ) {
907  if ( $this->wasCommitted() ) {
908  throw new RuntimeException(
909  'saveRevision() or updateRevision() has already been called on this PageUpdater!'
910  );
911  }
912 
913  // Low-level check
914  if ( $this->getPage()->getDBkey() === '' ) {
915  throw new RuntimeException( 'Something is trying to edit an article with an empty title' );
916  }
917 
919  $this->checkAllRolesAllowed(
920  $this->slotsUpdate->getModifiedRoles(),
921  $status
922  );
923  $this->checkAllRolesDerived(
924  $this->slotsUpdate->getModifiedRoles(),
925  $status
926  );
927  $this->checkAllRolesDerived(
928  $this->slotsUpdate->getRemovedRoles(),
929  $status
930  );
931 
932  if ( $revId === 0 ) {
933  $revision = $this->grabParentRevision();
934  } else {
935  $revision = $this->revisionStore->getRevisionById( $revId, RevisionStore::READ_LATEST );
936  }
937  if ( $revision === null ) {
938  $status->fatal( 'edit-gone-missing' );
939  }
940 
941  if ( !$status->isOK() ) {
942  $this->status = $status;
943  return;
944  }
945 
946  // Make sure the given content is allowed in the respective slots of this page
947  foreach ( $this->slotsUpdate->getModifiedRoles() as $role ) {
948  $slot = $this->slotsUpdate->getModifiedSlot( $role );
949  $roleHandler = $this->slotRoleRegistry->getRoleHandler( $role );
950 
951  if ( !$roleHandler->isAllowedModel( $slot->getModel(), $this->getPage() ) ) {
952  $contentHandler = $this->contentHandlerFactory
953  ->getContentHandler( $slot->getModel() );
954  $this->status = Status::newFatal(
955  'content-not-allowed-here',
956  ContentHandler::getLocalizedName( $contentHandler->getModelID() ),
957  $this->titleFormatter->getPrefixedText( $this->getPage() ),
958  wfMessage( $roleHandler->getNameMessageKey() )
959  // TODO: defer message lookup to caller
960  );
961  return;
962  }
963  }
964 
965  // XXX: do we need PST?
966 
967  $this->flags |= EDIT_INTERNAL;
968  $this->status = $this->doUpdate( $revision );
969  }
970 
976  public function wasCommitted() {
977  return $this->status !== null;
978  }
979 
1003  public function getStatus() {
1004  return $this->status;
1005  }
1006 
1012  public function wasSuccessful() {
1013  return $this->status && $this->status->isOK();
1014  }
1015 
1021  public function isNew() {
1022  return $this->status && $this->status->isOK() && $this->status->value['new'];
1023  }
1024 
1032  public function isUnchanged() {
1033  return $this->status
1034  && $this->status->isOK()
1035  && $this->status->value['revision-record'] === null;
1036  }
1037 
1044  public function getNewRevision() {
1045  return ( $this->status && $this->status->isOK() )
1046  ? $this->status->value['revision-record']
1047  : null;
1048  }
1049 
1063  private function makeNewRevision(
1064  CommentStoreComment $comment,
1065  Status $status
1066  ) {
1067  $wikiPage = $this->getWikiPage();
1068  $title = $this->getTitle();
1069  $parent = $this->grabParentRevision();
1070 
1071  // XXX: we expect to get a MutableRevisionRecord here, but that's a bit brittle!
1072  // TODO: introduce something like an UnsavedRevisionFactory service instead!
1074  $rev = $this->derivedDataUpdater->getRevision();
1075  '@phan-var MutableRevisionRecord $rev';
1076 
1077  // Avoid fatal error when the Title's ID changed, T204793
1078  if (
1079  $rev->getPageId() !== null && $title->exists()
1080  && $rev->getPageId() !== $title->getArticleID()
1081  ) {
1082  $titlePageId = $title->getArticleID();
1083  $revPageId = $rev->getPageId();
1084  $masterPageId = $title->getArticleID( Title::READ_LATEST );
1085 
1086  if ( $revPageId === $masterPageId ) {
1087  wfWarn( __METHOD__ . ": Encountered stale Title object: old ID was $titlePageId, "
1088  . "continuing with new ID from primary DB, $masterPageId" );
1089  } else {
1090  throw new InvalidArgumentException(
1091  "Revision inherited page ID $revPageId from its parent, "
1092  . "but the provided Title object belongs to page ID $masterPageId"
1093  );
1094  }
1095  }
1096 
1097  $rev->setPageId( $title->getArticleID() );
1098 
1099  if ( $parent ) {
1100  $oldid = $parent->getId();
1101  $rev->setParentId( $oldid );
1102  } else {
1103  $oldid = 0;
1104  }
1105 
1106  $rev->setComment( $comment );
1107  $rev->setUser( $this->author );
1108  $rev->setMinorEdit( ( $this->flags & EDIT_MINOR ) > 0 );
1109 
1110  foreach ( $rev->getSlots()->getSlots() as $slot ) {
1111  $content = $slot->getContent();
1112 
1113  // XXX: We may push this up to the "edit controller" level, see T192777.
1114  $contentHandler = $this->contentHandlerFactory->getContentHandler( $content->getModel() );
1115  $validationParams = new ValidationParams( $wikiPage, $this->flags, $oldid );
1116  $prepStatus = $contentHandler->validateSave( $content, $validationParams );
1117 
1118  // TODO: MCR: record which problem arose in which slot.
1119  $status->merge( $prepStatus );
1120  }
1121 
1122  $this->checkAllRequiredRoles(
1123  $rev->getSlotRoles(),
1124  $status
1125  );
1126 
1127  return $rev;
1128  }
1129 
1137  private function buildEditResult( RevisionRecord $revision, bool $isNew ) {
1138  $this->editResultBuilder->setRevisionRecord( $revision );
1139  $this->editResultBuilder->setIsNew( $isNew );
1140  $this->editResult = $this->editResultBuilder->buildEditResult();
1141  }
1142 
1154  private function doUpdate( RevisionRecord $revision ): Status {
1155  $currentRevision = $this->grabParentRevision();
1156  if ( !$currentRevision ) {
1157  // Article gone missing
1158  return Status::newFatal( 'edit-gone-missing' );
1159  }
1160 
1161  $dbw = $this->getDBConnectionRef( DB_PRIMARY );
1162  $dbw->startAtomic( __METHOD__ );
1163 
1164  $slots = $this->revisionStore->updateslotsOn( $revision, $this->slotsUpdate, $dbw );
1165 
1166  $dbw->endAtomic( __METHOD__ );
1167 
1168  // Return the slots and revision to the caller
1169  $newRevisionRecord = MutableRevisionRecord::newUpdatedRevisionRecord( $revision, $slots );
1171  'revision-record' => $newRevisionRecord,
1172  'slots' => $slots,
1173  ] );
1174 
1175  $isCurrent = $revision->getId( $this->getWikiId() ) ===
1176  $currentRevision->getId( $this->getWikiId() );
1177 
1178  if ( $isCurrent ) {
1179  // Update page_touched
1180  $this->getTitle()->invalidateCache( $newRevisionRecord->getTimestamp() );
1181 
1182  $this->buildEditResult( $newRevisionRecord, false );
1183 
1184  // Do secondary updates once the main changes have been committed...
1185  $wikiPage = $this->getWikiPage(); // TODO: use for legacy hooks only!
1187  $this->getAtomicSectionUpdate(
1188  $dbw,
1189  $wikiPage,
1190  $newRevisionRecord,
1191  $revision->getComment(),
1192  [ 'changed' => false, ]
1193  ),
1194  DeferredUpdates::PRESEND
1195  );
1196  }
1197 
1198  return $status;
1199  }
1200 
1207  private function doModify( CommentStoreComment $summary ) {
1208  $wikiPage = $this->getWikiPage(); // TODO: use for legacy hooks only!
1209 
1210  // Update article, but only if changed.
1212  [ 'new' => false, 'revision-record' => null ]
1213  );
1214 
1215  $oldRev = $this->grabParentRevision();
1216  $oldid = $oldRev ? $oldRev->getId() : 0;
1217 
1218  if ( !$oldRev ) {
1219  // Article gone missing
1220  return $status->fatal( 'edit-gone-missing' );
1221  }
1222 
1223  $newRevisionRecord = $this->makeNewRevision(
1224  $summary,
1225  $status
1226  );
1227 
1228  if ( !$status->isOK() ) {
1229  return $status;
1230  }
1231 
1232  $now = $newRevisionRecord->getTimestamp();
1233 
1234  // XXX: we may want a flag that allows a null revision to be forced!
1235  $changed = $this->derivedDataUpdater->isChange();
1236 
1237  // We build the EditResult before the $change if/else branch in order to pass
1238  // the correct $newRevisionRecord to EditResultBuilder. In case this is a null
1239  // edit, $newRevisionRecord will be later overridden to its parent revision, which
1240  // would confuse EditResultBuilder.
1241  if ( !$changed ) {
1242  // This is a null edit, ensure original revision ID is set properly
1243  $this->editResultBuilder->setOriginalRevision( $oldRev );
1244  }
1245  $this->buildEditResult( $newRevisionRecord, false );
1246 
1247  $dbw = $this->getDBConnectionRef( DB_PRIMARY );
1248 
1249  if ( $changed ) {
1250  $dbw->startAtomic( __METHOD__ );
1251 
1252  // Get the latest page_latest value while locking it.
1253  // Do a CAS style check to see if it's the same as when this method
1254  // started. If it changed then bail out before touching the DB.
1255  $latestNow = $wikiPage->lockAndGetLatest(); // TODO: move to storage service, pass DB
1256  if ( $latestNow != $oldid ) {
1257  // We don't need to roll back, since we did not modify the database yet.
1258  // XXX: Or do we want to rollback, any transaction started by calling
1259  // code will fail? If we want that, we should probably throw an exception.
1260  $dbw->endAtomic( __METHOD__ );
1261 
1262  // Page updated or deleted in the mean time
1263  return $status->fatal( 'edit-conflict' );
1264  }
1265 
1266  // At this point we are now committed to returning an OK
1267  // status unless some DB query error or other exception comes up.
1268  // This way callers don't have to call rollback() if $status is bad
1269  // unless they actually try to catch exceptions (which is rare).
1270 
1271  // Save revision content and meta-data
1272  $newRevisionRecord = $this->revisionStore->insertRevisionOn( $newRevisionRecord, $dbw );
1273 
1274  // Update page_latest and friends to reflect the new revision
1275  // TODO: move to storage service
1276  $wasRedirect = $this->derivedDataUpdater->wasRedirect();
1277  if ( !$wikiPage->updateRevisionOn( $dbw, $newRevisionRecord, null, $wasRedirect ) ) {
1278  throw new PageUpdateException( "Failed to update page row to use new revision." );
1279  }
1280 
1281  $editResult = $this->getEditResult();
1282  $tags = $this->computeEffectiveTags();
1283  $this->hookRunner->onRevisionFromEditComplete(
1284  $wikiPage,
1285  $newRevisionRecord,
1287  $this->author,
1288  $tags
1289  );
1290 
1291  // Update recentchanges
1292  if ( !( $this->flags & EDIT_SUPPRESS_RC ) ) {
1293  // Add RC row to the DB
1294  // @phan-suppress-next-line SecurityCheck-DoubleEscaped
1296  $now,
1297  $this->getPage(),
1298  $newRevisionRecord->isMinor(),
1299  $this->author,
1300  $summary->text, // TODO: pass object when that becomes possible
1301  $oldid,
1302  $newRevisionRecord->getTimestamp(),
1303  ( $this->flags & EDIT_FORCE_BOT ) > 0,
1304  '',
1305  $oldRev->getSize(),
1306  $newRevisionRecord->getSize(),
1307  $newRevisionRecord->getId(),
1308  $this->rcPatrolStatus,
1309  $tags,
1310  $editResult
1311  );
1312  } else {
1313  ChangeTags::addTags( $tags, null, $newRevisionRecord->getId(), null );
1314  }
1315 
1316  $this->userEditTracker->incrementUserEditCount( $this->author );
1317 
1318  $dbw->endAtomic( __METHOD__ );
1319 
1320  // Return the new revision to the caller
1321  $status->value['revision-record'] = $newRevisionRecord;
1322  } else {
1323  // T34948: revision ID must be set to page {{REVISIONID}} and
1324  // related variables correctly. Likewise for {{REVISIONUSER}} (T135261).
1325  // Since we don't insert a new revision into the database, the least
1326  // error-prone way is to reuse given old revision.
1327  $newRevisionRecord = $oldRev;
1328 
1329  $status->warning( 'edit-no-change' );
1330  // Update page_touched as updateRevisionOn() was not called.
1331  // Other cache updates are managed in WikiPage::onArticleEdit()
1332  // via WikiPage::doEditUpdates().
1333  $this->getTitle()->invalidateCache( $now );
1334  }
1335 
1336  // Do secondary updates once the main changes have been committed...
1337  // NOTE: the updates have to be processed before sending the response to the client
1338  // (DeferredUpdates::PRESEND), otherwise the client may already be following the
1339  // HTTP redirect to the standard view before derived data has been created - most
1340  // importantly, before the parser cache has been updated. This would cause the
1341  // content to be parsed a second time, or may cause stale content to be shown.
1343  $this->getAtomicSectionUpdate(
1344  $dbw,
1345  $wikiPage,
1346  $newRevisionRecord,
1347  $summary,
1348  [ 'changed' => $changed, ]
1349  ),
1350  DeferredUpdates::PRESEND
1351  );
1352 
1353  return $status;
1354  }
1355 
1363  private function doCreate( CommentStoreComment $summary ) {
1364  $wikiPage = $this->getWikiPage(); // TODO: use for legacy hooks only!
1365 
1366  if ( !$this->derivedDataUpdater->getSlots()->hasSlot( SlotRecord::MAIN ) ) {
1367  throw new PageUpdateException( 'Must provide a main slot when creating a page!' );
1368  }
1369 
1371  [ 'new' => true, 'revision-record' => null ]
1372  );
1373 
1374  $newRevisionRecord = $this->makeNewRevision(
1375  $summary,
1376  $status
1377  );
1378 
1379  if ( !$status->isOK() ) {
1380  return $status;
1381  }
1382 
1383  $this->buildEditResult( $newRevisionRecord, true );
1384  $now = $newRevisionRecord->getTimestamp();
1385 
1386  $dbw = $this->getDBConnectionRef( DB_PRIMARY );
1387  $dbw->startAtomic( __METHOD__ );
1388 
1389  // Add the page record unless one already exists for the title
1390  // TODO: move to storage service
1391  $newid = $wikiPage->insertOn( $dbw );
1392  if ( $newid === false ) {
1393  $dbw->endAtomic( __METHOD__ );
1394  return $status->fatal( 'edit-already-exists' );
1395  }
1396 
1397  // At this point we are now committed to returning an OK
1398  // status unless some DB query error or other exception comes up.
1399  // This way callers don't have to call rollback() if $status is bad
1400  // unless they actually try to catch exceptions (which is rare).
1401  $newRevisionRecord->setPageId( $newid );
1402 
1403  // Save the revision text...
1404  $newRevisionRecord = $this->revisionStore->insertRevisionOn( $newRevisionRecord, $dbw );
1405 
1406  // Update the page record with revision data
1407  // TODO: move to storage service
1408  if ( !$wikiPage->updateRevisionOn( $dbw, $newRevisionRecord, 0 ) ) {
1409  throw new PageUpdateException( "Failed to update page row to use new revision." );
1410  }
1411 
1412  $tags = $this->computeEffectiveTags();
1413  $this->hookRunner->onRevisionFromEditComplete(
1414  $wikiPage, $newRevisionRecord, false, $this->author, $tags
1415  );
1416 
1417  // Update recentchanges
1418  if ( !( $this->flags & EDIT_SUPPRESS_RC ) ) {
1419  // Add RC row to the DB
1420  // @phan-suppress-next-line SecurityCheck-DoubleEscaped
1422  $now,
1423  $this->getPage(),
1424  $newRevisionRecord->isMinor(),
1425  $this->author,
1426  $summary->text, // TODO: pass object when that becomes possible
1427  ( $this->flags & EDIT_FORCE_BOT ) > 0,
1428  '',
1429  $newRevisionRecord->getSize(),
1430  $newRevisionRecord->getId(),
1431  $this->rcPatrolStatus,
1432  $tags
1433  );
1434  } else {
1435  ChangeTags::addTags( $tags, null, $newRevisionRecord->getId(), null );
1436  }
1437 
1438  $this->userEditTracker->incrementUserEditCount( $this->author );
1439 
1440  if ( $this->usePageCreationLog ) {
1441  // Log the page creation
1442  // @TODO: Do we want a 'recreate' action?
1443  $logEntry = new ManualLogEntry( 'create', 'create' );
1444  $logEntry->setPerformer( $this->author );
1445  $logEntry->setTarget( $this->getPage() );
1446  $logEntry->setComment( $summary->text );
1447  $logEntry->setTimestamp( $now );
1448  $logEntry->setAssociatedRevId( $newRevisionRecord->getId() );
1449  $logEntry->insert();
1450  // Note that we don't publish page creation events to recentchanges
1451  // (i.e. $logEntry->publish()) since this would create duplicate entries,
1452  // one for the edit and one for the page creation.
1453  }
1454 
1455  $dbw->endAtomic( __METHOD__ );
1456 
1457  // Return the new revision to the caller
1458  $status->value['revision-record'] = $newRevisionRecord;
1459 
1460  // Do secondary updates once the main changes have been committed...
1462  $this->getAtomicSectionUpdate(
1463  $dbw,
1464  $wikiPage,
1465  $newRevisionRecord,
1466  $summary,
1467  [ 'created' => true ]
1468  ),
1469  DeferredUpdates::PRESEND
1470  );
1471 
1472  return $status;
1473  }
1474 
1475  private function getAtomicSectionUpdate(
1476  IDatabase $dbw,
1478  RevisionRecord $newRevisionRecord,
1479  CommentStoreComment $summary,
1480  array $hints = []
1481  ) {
1482  return new AtomicSectionUpdate(
1483  $dbw,
1484  __METHOD__,
1485  function () use (
1486  $wikiPage, $newRevisionRecord,
1487  $summary, $hints
1488  ) {
1489  // set debug data
1490  $hints['causeAction'] = 'edit-page';
1491  $hints['causeAgent'] = $this->author->getName();
1492 
1493  $editResult = $this->getEditResult();
1494  $hints['editResult'] = $editResult;
1495 
1496  if ( $editResult->isRevert() ) {
1497  // Should the reverted tag update be scheduled right away?
1498  // The revert is approved if either patrolling is disabled or the
1499  // edit is patrolled or autopatrolled.
1500  $approved = !$this->serviceOptions->get( 'UseRCPatrol' ) ||
1501  $this->rcPatrolStatus === RecentChange::PRC_PATROLLED ||
1502  $this->rcPatrolStatus === RecentChange::PRC_AUTOPATROLLED;
1503 
1504  // Allow extensions to override the patrolling subsystem.
1505  $this->hookRunner->onBeforeRevertedTagUpdate(
1506  $wikiPage,
1507  $this->author,
1508  $summary,
1509  $this->flags,
1510  $newRevisionRecord,
1511  $editResult,
1512  $approved
1513  );
1514  $hints['approved'] = $approved;
1515  }
1516 
1517  // Update links tables, site stats, etc.
1518  $this->derivedDataUpdater->prepareUpdate( $newRevisionRecord, $hints );
1519  $this->derivedDataUpdater->doUpdates();
1520 
1521  $created = $hints['created'] ?? false;
1522  $this->flags |= ( $created ? EDIT_NEW : EDIT_UPDATE );
1523 
1524  // PageSaveComplete replaced old PageContentInsertComplete and
1525  // PageContentSaveComplete hooks since 1.35
1526  $this->hookRunner->onPageSaveComplete(
1527  $wikiPage,
1528  $this->author,
1529  $summary->text,
1530  $this->flags,
1531  $newRevisionRecord,
1532  $editResult
1533  );
1534  }
1535  );
1536  }
1537 
1541  private function getRequiredSlotRoles() {
1542  return $this->slotRoleRegistry->getRequiredRoles( $this->getPage() );
1543  }
1544 
1548  private function getAllowedSlotRoles() {
1549  return $this->slotRoleRegistry->getAllowedRoles( $this->getPage() );
1550  }
1551 
1552  private function ensureRoleAllowed( $role ) {
1553  $allowedRoles = $this->getAllowedSlotRoles();
1554  if ( !in_array( $role, $allowedRoles ) ) {
1555  throw new PageUpdateException( "Slot role `$role` is not allowed." );
1556  }
1557  }
1558 
1559  private function ensureRoleNotRequired( $role ) {
1560  $requiredRoles = $this->getRequiredSlotRoles();
1561  if ( in_array( $role, $requiredRoles ) ) {
1562  throw new PageUpdateException( "Slot role `$role` is required." );
1563  }
1564  }
1565 
1570  private function checkAllRolesAllowed( array $roles, Status $status ) {
1571  $allowedRoles = $this->getAllowedSlotRoles();
1572 
1573  $forbidden = array_diff( $roles, $allowedRoles );
1574  if ( !empty( $forbidden ) ) {
1575  $status->error(
1576  'edit-slots-cannot-add',
1577  count( $forbidden ),
1578  implode( ', ', $forbidden )
1579  );
1580  }
1581  }
1582 
1587  private function checkAllRolesDerived( array $roles, Status $status ) {
1588  $notDerived = array_filter(
1589  $roles,
1590  function ( $role ) {
1591  return !$this->slotRoleRegistry->getRoleHandler( $role )->isDerived();
1592  }
1593  );
1594  if ( $notDerived ) {
1595  $status->error(
1596  'edit-slots-not-derived',
1597  count( $notDerived ),
1598  implode( ', ', $notDerived )
1599  );
1600  }
1601  }
1602 
1607  private function checkNoRolesRequired( array $roles, Status $status ) {
1608  $requiredRoles = $this->getRequiredSlotRoles();
1609 
1610  $needed = array_diff( $roles, $requiredRoles );
1611  if ( !empty( $needed ) ) {
1612  $status->error(
1613  'edit-slots-cannot-remove',
1614  count( $needed ),
1615  implode( ', ', $needed )
1616  );
1617  }
1618  }
1619 
1624  private function checkAllRequiredRoles( array $roles, Status $status ) {
1625  $requiredRoles = $this->getRequiredSlotRoles();
1626 
1627  $missing = array_diff( $requiredRoles, $roles );
1628  if ( !empty( $missing ) ) {
1629  $status->error(
1630  'edit-slots-missing',
1631  count( $missing ),
1632  implode( ', ', $missing )
1633  );
1634  }
1635  }
1636 
1637 }
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:575
MediaWiki\Storage\PageUpdater\addTags
addTags(array $tags)
Sets tags to apply to this update.
Definition: PageUpdater.php:616
ContentHandler
A content handler knows how do deal with a specific type of content on a wiki page.
Definition: ContentHandler.php:62
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:166
StatusValue\newFatal
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:70
MediaWiki\Storage\PageUpdater\computeEffectiveTags
computeEffectiveTags()
Definition: PageUpdater.php:651
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:731
MediaWiki\Storage\PageUpdater\$usePageCreationLog
bool $usePageCreationLog
whether to create a log entry for new page creations.
Definition: PageUpdater.php:161
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:88
MediaWiki\Storage\PageUpdater\saveRevision
saveRevision(CommentStoreComment $summary, int $flags=0)
Change an existing article or create a new article.
Definition: PageUpdater.php:772
MediaWiki\Storage\PageUpdater\checkNoRolesRequired
checkNoRolesRequired(array $roles, Status $status)
Definition: PageUpdater.php:1607
MediaWiki\Storage\PageUpdater\$flags
int $flags
Definition: PageUpdater.php:196
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:523
MediaWiki\Revision\RevisionStore
Service for looking up page revisions.
Definition: RevisionStore.php:89
MediaWiki\Storage\PageUpdater\doCreate
doCreate(CommentStoreComment $summary)
Definition: PageUpdater.php:1363
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:558
MediaWiki\Storage\PageUpdater\getWikiId
getWikiId()
Definition: PageUpdater.php:373
RecentChange
Utility class for creating new RC entries.
Definition: RecentChange.php:81
MediaWiki\Storage\PageUpdater\updateRevision
updateRevision(int $revId=0)
Updates derived slots of an existing article.
Definition: PageUpdater.php:906
MediaWiki\Storage\PageUpdater\getExplicitTags
getExplicitTags()
Returns the list of tags set using the addTag() method.
Definition: PageUpdater.php:644
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:125
MediaWiki\Storage\PageUpdater\getEditResult
getEditResult()
Returns the EditResult associated with this PageUpdater.
Definition: PageUpdater.php:593
WikiPage
Class representing a MediaWiki article and history.
Definition: WikiPage.php:63
MediaWiki\Storage\PageUpdater\$userGroupManager
UserGroupManager $userGroupManager
Definition: PageUpdater.php:142
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:368
MediaWiki\Storage\PageUpdater\makeAutoSummary
makeAutoSummary()
Definition: PageUpdater.php:717
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1167
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:681
MediaWiki\Storage\PageUpdater\$wikiPage
WikiPage $wikiPage
Definition: PageUpdater.php:101
MediaWiki\Storage\PageUpdater\$userEditTracker
UserEditTracker $userEditTracker
Definition: PageUpdater.php:139
User\newFromIdentity
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:672
MediaWiki\Content\ValidationParams
Definition: ValidationParams.php:10
MediaWiki\Storage\PageUpdater\$loadBalancer
ILoadBalancer $loadBalancer
Definition: PageUpdater.php:111
MediaWiki\Storage\PageUpdater\wasCommitted
wasCommitted()
Whether saveRevision() has been called on this instance.
Definition: PageUpdater.php:976
MediaWiki\Storage\EditResultBuilder\CONSTRUCTOR_OPTIONS
const CONSTRUCTOR_OPTIONS
Definition: EditResultBuilder.php:40
MediaWiki\Storage\PageUpdater\$editResultBuilder
EditResultBuilder $editResultBuilder
Definition: PageUpdater.php:181
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:1552
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:1063
MediaWiki\User\UserGroupManager
Managers user groups.
Definition: UserGroupManager.php:52
MediaWiki\Storage\PageUpdater\$revisionStore
RevisionStore $revisionStore
Definition: PageUpdater.php:116
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:1012
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:1422
MediaWiki\Storage\PageUpdater\doUpdate
doUpdate(RevisionRecord $revision)
Update derived slots in an existing revision.
Definition: PageUpdater.php:1154
MediaWiki\Storage\PageUpdater\setContent
setContent( $role, Content $content)
Set the new content for the given slot role.
Definition: PageUpdater.php:488
MediaWiki\Config\ServiceOptions
A class for passing options to services.
Definition: ServiceOptions.php:27
MediaWiki\Storage\PageUpdater\getTitle
getTitle()
Definition: PageUpdater.php:397
ChangeTags
Definition: ChangeTags.php:32
MediaWiki\Storage\PreparedUpdate
An object representing a page update during an edit.
Definition: PreparedUpdate.php:22
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:1377
MediaWiki\Storage\PageUpdater\setFlags
setFlags(int $flags)
Sets any flags to use when performing the update.
Definition: PageUpdater.php:289
DeferredUpdates
Class for managing the deferral of updates within the scope of a PHP script invocation.
Definition: DeferredUpdates.php:83
MediaWiki\Storage\PageUpdater\getNewRevision
getNewRevision()
The new revision created by saveRevision(), or null if saveRevision() has not yet been called,...
Definition: PageUpdater.php:1044
MediaWiki\Storage\PageUpdater\getWikiPage
getWikiPage()
Definition: PageUpdater.php:405
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:542
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:390
MediaWiki\Storage\PageUpdater\$serviceOptions
ServiceOptions $serviceOptions
Definition: PageUpdater.php:191
MediaWiki\Storage\PageUpdater\$titleFormatter
TitleFormatter $titleFormatter
Definition: PageUpdater.php:145
MediaWiki\Storage\PageUpdater\getRequiredSlotRoles
getRequiredSlotRoles()
Definition: PageUpdater.php:1541
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:2779
MediaWiki\Storage\PageUpdater\checkAllRolesDerived
checkAllRolesDerived(array $roles, Status $status)
Definition: PageUpdater.php:1587
MediaWiki\Storage\PageUpdater\$hookContainer
HookContainer $hookContainer
Definition: PageUpdater.php:136
MediaWiki\Storage\PageUpdater\checkAllRolesAllowed
checkAllRolesAllowed(array $roles, Status $status)
Definition: PageUpdater.php:1570
RecentChange\PRC_PATROLLED
const PRC_PATROLLED
Definition: RecentChange.php:93
MediaWiki\Storage\PageUpdater\getAllowedSlotRoles
getAllowedSlotRoles()
Definition: PageUpdater.php:1548
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:199
$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:310
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:632
MediaWiki\Storage\PageUpdater\$contentHandlerFactory
IContentHandlerFactory $contentHandlerFactory
Definition: PageUpdater.php:126
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:81
MediaWiki\Storage\PageUpdater\getDBConnectionRef
getDBConnectionRef( $mode)
Definition: PageUpdater.php:382
MediaWiki\Storage\PageUpdater\$useAutomaticEditSummaries
bool $useAutomaticEditSummaries
see $wgUseAutomaticEditSummaries
Definition: PageUpdater.php:151
MediaWiki\Storage\PageUpdater\hasEditConflict
hasEditConflict( $expectedParentRevision)
Checks whether this update conflicts with another update performed between the client loading data to...
Definition: PageUpdater.php:443
EDIT_SUPPRESS_RC
const EDIT_SUPPRESS_RC
Definition: Defines.php:128
RecentChange\PRC_AUTOPATROLLED
const PRC_AUTOPATROLLED
Definition: RecentChange.php:94
MediaWiki\Storage\PageUpdater\$hookRunner
HookRunner $hookRunner
Definition: PageUpdater.php:131
MediaWiki\Storage
Definition: BlobAccessException.php:23
Wikimedia\Rdbms\DBUnexpectedError
Definition: DBUnexpectedError.php:29
MediaWiki\Storage\PageUpdater\$author
UserIdentity $author
Definition: PageUpdater.php:96
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:176
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:30
MediaWiki\Storage\PageUpdater\$slotsUpdate
RevisionSlotsUpdate $slotsUpdate
Definition: PageUpdater.php:171
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:477
MediaWiki\Storage\PageUpdater\addTag
addTag(string $tag)
Sets a tag to apply to this update.
Definition: PageUpdater.php:604
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:812
MediaWiki\Storage\PageUpdater\getStatus
getStatus()
The Status object indicating whether saveRevision() was successful, or null if saveRevision() was not...
Definition: PageUpdater.php:1003
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:355
MediaWiki\Storage\PageUpdater\$rcPatrolStatus
int $rcPatrolStatus
the RC patrol status the new revision should be marked with.
Definition: PageUpdater.php:156
RecentChange\PRC_UNPATROLLED
const PRC_UNPATROLLED
Definition: RecentChange.php:92
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:304
MediaWiki\Storage\PageUpdater\ensureRoleNotRequired
ensureRoleNotRequired( $role)
Definition: PageUpdater.php:1559
MediaWiki\Storage\PageUpdater\getContentHandler
getContentHandler( $role)
Definition: PageUpdater.php:695
ManualLogEntry
Class for creating new log entries and inserting them into the database.
Definition: ManualLogEntry.php:45
MediaWiki\Storage\PageUpdater\buildEditResult
buildEditResult(RevisionRecord $revision, bool $isNew)
Builds the EditResult for this update.
Definition: PageUpdater.php:1137
MediaWiki\Storage\PageUpdater\doModify
doModify(CommentStoreComment $summary)
Definition: PageUpdater.php:1207
MediaWiki\Storage\PageUpdater\$derivedDataUpdater
DerivedPageDataUpdater $derivedDataUpdater
Definition: PageUpdater.php:106
MediaWiki\Storage\PageUpdater\checkAllRequiredRoles
checkAllRequiredRoles(array $roles, Status $status)
Definition: PageUpdater.php:1624
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:557
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:328
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:217
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:106
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:67
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:151
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:121
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:340
MediaWiki\Storage\PageUpdater\$editResult
EditResult null $editResult
Definition: PageUpdater.php:186
MediaWiki\Storage\PageUpdater\isNew
isNew()
Whether saveRevision() was called and created a new page.
Definition: PageUpdater.php:1021
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:1032
MediaWiki\Storage\PageUpdater\setSlot
setSlot(SlotRecord $slot)
Set the new slot for the given slot role.
Definition: PageUpdater.php:501
ChangeTags\addTags
static addTags( $tags, $rc_id=null, $rev_id=null, $log_id=null, $params=null, RecentChange $rc=null)
Add tags to a change given its rc_id, rev_id and/or log_id.
Definition: ChangeTags.php:333
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:1475
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