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 Revision;
52 use RuntimeException;
53 use Status;
54 use Title;
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 $performer;
95 
99  private $wikiPage;
100 
105 
109  private $loadBalancer;
110 
114  private $revisionStore;
115 
120 
125 
129  private $hookRunner;
130 
134  private $hookContainer;
135 
141 
146 
150  private $usePageCreationLog = true;
151 
155  private $ajaxEditStash = true;
156 
160  private $tags = [];
161 
165  private $slotsUpdate;
166 
170  private $status = null;
171 
176 
180  private $editResult = null;
181 
185  private $softwareTags;
186 
191 
205  public function __construct(
215  array $softwareTags
216  ) {
217  $serviceOptions->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
218  $this->serviceOptions = $serviceOptions;
219 
220  $this->performer = $performer;
221  $this->wikiPage = $wikiPage;
222  $this->derivedDataUpdater = $derivedDataUpdater;
223 
224  $this->loadBalancer = $loadBalancer;
225  $this->revisionStore = $revisionStore;
226  $this->slotRoleRegistry = $slotRoleRegistry;
227  $this->contentHandlerFactory = $contentHandlerFactory;
228  $this->hookContainer = $hookContainer;
229  $this->hookRunner = new HookRunner( $hookContainer );
230  $this->softwareTags = $softwareTags;
231 
232  $this->slotsUpdate = new RevisionSlotsUpdate();
233  $this->editResultBuilder = new EditResultBuilder(
237  new ServiceOptions(
239  [
240  'ManualRevertSearchRadius' =>
241  $serviceOptions->get( 'ManualRevertSearchRadius' )
242  ]
243  )
244  );
245  }
246 
252  private static function toLegacyUser( UserIdentity $user ) {
253  return User::newFromIdentity( $user );
254  }
255 
264  $this->useAutomaticEditSummaries = $useAutomaticEditSummaries;
265  }
266 
276  public function setRcPatrolStatus( $status ) {
277  $this->rcPatrolStatus = $status;
278  }
279 
287  public function setUsePageCreationLog( $use ) {
288  $this->usePageCreationLog = $use;
289  }
290 
295  public function setAjaxEditStash( $ajaxEditStash ) {
296  $this->ajaxEditStash = $ajaxEditStash;
297  }
298 
299  private function getWikiId() {
300  return false; // TODO: get from RevisionStore!
301  }
302 
308  private function getDBConnectionRef( $mode ) {
309  return $this->loadBalancer->getConnectionRef( $mode, [], $this->getWikiId() );
310  }
311 
315  private function getLinkTarget() {
316  // NOTE: eventually, we won't get a WikiPage passed into the constructor any more
317  return $this->wikiPage->getTitle();
318  }
319 
323  private function getTitle() {
324  // NOTE: eventually, we won't get a WikiPage passed into the constructor any more
325  return $this->wikiPage->getTitle();
326  }
327 
331  private function getWikiPage() {
332  // NOTE: eventually, we won't get a WikiPage passed into the constructor any more
333  return $this->wikiPage;
334  }
335 
369  public function hasEditConflict( $expectedParentRevision ) {
370  $parent = $this->grabParentRevision();
371  $parentId = $parent ? $parent->getId() : 0;
372 
373  return $parentId !== $expectedParentRevision;
374  }
375 
403  public function grabParentRevision() {
404  return $this->derivedDataUpdater->grabCurrentRevision();
405  }
406 
413  private function checkFlags( $flags ) {
414  if ( !( $flags & EDIT_NEW ) && !( $flags & EDIT_UPDATE ) ) {
415  $flags |= ( $this->derivedDataUpdater->pageExisted() ) ? EDIT_UPDATE : EDIT_NEW;
416  }
417 
418  return $flags;
419  }
420 
427  public function setContent( $role, Content $content ) {
428  $this->ensureRoleAllowed( $role );
429 
430  $this->slotsUpdate->modifyContent( $role, $content );
431  }
432 
438  public function setSlot( SlotRecord $slot ) {
439  $this->ensureRoleAllowed( $slot->getRole() );
440 
441  $this->slotsUpdate->modifySlot( $slot );
442  }
443 
458  public function inheritSlot( SlotRecord $originalSlot ) {
459  // NOTE: slots can be inherited even if the role is not "allowed" on the title.
460  // NOTE: this slot is inherited from some other revision, but it's
461  // a "modified" slot for the RevisionSlotsUpdate and DerivedPageDataUpdater,
462  // since it's not implicitly inherited from the parent revision.
463  $inheritedSlot = SlotRecord::newInherited( $originalSlot );
464  $this->slotsUpdate->modifySlot( $inheritedSlot );
465  }
466 
476  public function removeSlot( $role ) {
477  $this->ensureRoleNotRequired( $role );
478 
479  $this->slotsUpdate->removeSlot( $role );
480  }
481 
493  public function setOriginalRevisionId( $originalRevId ) {
494  $this->editResultBuilder->setOriginalRevisionId( $originalRevId );
495  }
496 
510  public function markAsRevert(
511  int $revertMethod,
512  int $oldestRevertedRevId,
513  int $newestRevertedRevId = 0
514  ) {
515  $this->editResultBuilder->markAsRevert(
516  $revertMethod, $oldestRevertedRevId, $newestRevertedRevId
517  );
518  }
519 
527  public function getEditResult() : ?EditResult {
528  return $this->editResult;
529  }
530 
537  public function addTag( $tag ) {
538  Assert::parameterType( 'string', $tag, '$tag' );
539  $this->tags[] = trim( $tag );
540  }
541 
548  public function addTags( array $tags ) {
549  Assert::parameterElementType( 'string', $tags, '$tags' );
550  foreach ( $tags as $tag ) {
551  $this->addTag( $tag );
552  }
553  }
554 
560  public function getExplicitTags() {
561  return $this->tags;
562  }
563 
568  private function computeEffectiveTags( $flags ) {
569  $tags = $this->tags;
570  $editResult = $this->getEditResult();
571 
572  foreach ( $this->slotsUpdate->getModifiedRoles() as $role ) {
573  $old_content = $this->getParentContent( $role );
574 
575  $handler = $this->getContentHandler( $role );
576  $content = $this->slotsUpdate->getModifiedSlot( $role )->getContent();
577 
578  // TODO: MCR: Do this for all slots. Also add tags for removing roles!
579  $tag = $handler->getChangeTag( $old_content, $content, $flags );
580  // If there is no applicable tag, null is returned, so we need to check
581  if ( $tag ) {
582  $tags[] = $tag;
583  }
584  }
585 
586  $tags = array_merge( $tags, $editResult->getRevertTags() );
587 
588  return array_unique( $tags );
589  }
590 
598  private function getParentContent( $role ) {
599  $parent = $this->grabParentRevision();
600 
601  if ( $parent && $parent->hasSlot( $role ) ) {
602  return $parent->getContent( $role, RevisionRecord::RAW );
603  }
604 
605  return null;
606  }
607 
612  private function getContentHandler( $role ) {
613  if ( $this->slotsUpdate->isModifiedSlot( $role ) ) {
614  $slot = $this->slotsUpdate->getModifiedSlot( $role );
615  } else {
616  $parent = $this->grabParentRevision();
617 
618  if ( $parent ) {
619  $slot = $parent->getSlot( $role, RevisionRecord::RAW );
620  } else {
621  throw new RevisionAccessException( 'No such slot: ' . $role );
622  }
623  }
624 
625  return $this->contentHandlerFactory->getContentHandler( $slot->getModel() );
626  }
627 
633  private function makeAutoSummary( $flags ) {
634  if ( !$this->useAutomaticEditSummaries || ( $flags & EDIT_AUTOSUMMARY ) === 0 ) {
636  }
637 
638  // NOTE: this generates an auto-summary for SOME RANDOM changed slot!
639  // TODO: combine auto-summaries for multiple slots!
640  // XXX: this logic should not be in the storage layer!
641  $roles = $this->slotsUpdate->getModifiedRoles();
642  $role = reset( $roles );
643 
644  if ( $role === false ) {
646  }
647 
648  $handler = $this->getContentHandler( $role );
649  $content = $this->slotsUpdate->getModifiedSlot( $role )->getContent();
650  $old_content = $this->getParentContent( $role );
651  $summary = $handler->getAutosummary( $old_content, $content, $flags );
652 
653  return CommentStoreComment::newUnsavedComment( $summary );
654  }
655 
701  public function saveRevision( CommentStoreComment $summary, $flags = 0 ) {
702  // Defend against mistakes caused by differences with the
703  // signature of WikiPage::doEditContent.
704  Assert::parameterType( 'integer', $flags, '$flags' );
705 
706  if ( $this->wasCommitted() ) {
707  throw new RuntimeException(
708  'saveRevision() or updateRevision() has already been called on this PageUpdater!'
709  );
710  }
711 
712  // Low-level sanity check
713  if ( $this->getLinkTarget()->getText() === '' ) {
714  throw new RuntimeException( 'Something is trying to edit an article with an empty title' );
715  }
716 
717  // NOTE: slots can be inherited even if the role is not "allowed" on the title.
719  $this->checkAllRolesAllowed(
720  $this->slotsUpdate->getModifiedRoles(),
721  $status
722  );
723  $this->checkNoRolesRequired(
724  $this->slotsUpdate->getRemovedRoles(),
725  $status
726  );
727 
728  if ( !$status->isOK() ) {
729  return null;
730  }
731 
732  // Make sure the given content is allowed in the respective slots of this page
733  foreach ( $this->slotsUpdate->getModifiedRoles() as $role ) {
734  $slot = $this->slotsUpdate->getModifiedSlot( $role );
735  $roleHandler = $this->slotRoleRegistry->getRoleHandler( $role );
736 
737  if ( !$roleHandler->isAllowedModel( $slot->getModel(), $this->getTitle() ) ) {
738  $contentHandler = $this->contentHandlerFactory
739  ->getContentHandler( $slot->getModel() );
740  $this->status = Status::newFatal( 'content-not-allowed-here',
741  ContentHandler::getLocalizedName( $contentHandler->getModelID() ),
742  $this->getTitle()->getPrefixedText(),
743  wfMessage( $roleHandler->getNameMessageKey() )
744  // TODO: defer message lookup to caller
745  );
746  return null;
747  }
748  }
749 
750  // Load the data from the master database if needed. Needed to check flags.
751  // NOTE: This grabs the parent revision as the CAS token, if grabParentRevision
752  // wasn't called yet. If the page is modified by another process before we are done with
753  // it, this method must fail (with status 'edit-conflict')!
754  // NOTE: The parent revision may be different from $this->originalRevisionId.
755  $this->grabParentRevision();
756  $flags = $this->checkFlags( $flags );
757 
758  // Avoid statsd noise and wasted cycles check the edit stash (T136678)
759  if ( ( $flags & EDIT_INTERNAL ) || ( $flags & EDIT_FORCE_BOT ) ) {
760  $useStashed = false;
761  } else {
762  $useStashed = $this->ajaxEditStash;
763  }
764 
765  $user = $this->performer->getUser();
766  $legacyUser = self::toLegacyUser( $user );
767 
768  // Prepare the update. This performs PST and generates the canonical ParserOutput.
769  $this->derivedDataUpdater->prepareContent(
770  $user,
771  $this->slotsUpdate,
772  $useStashed
773  );
774 
775  // Trigger pre-save hook (using provided edit summary)
776  $renderedRevision = $this->derivedDataUpdater->getRenderedRevision();
777  $hookStatus = Status::newGood( [] );
778  $allowedByHook = $this->hookRunner->onMultiContentSave(
779  $renderedRevision, $user, $summary, $flags, $hookStatus
780  );
781  if ( $allowedByHook && $this->hookContainer->isRegistered( 'PageContentSave' ) ) {
782  // Also run the legacy hook.
783  // NOTE: WikiPage should only be used for the legacy hook,
784  // and only if something uses the legacy hook.
785  $mainContent = $this->derivedDataUpdater->getSlots()->getContent( SlotRecord::MAIN );
786 
787  // Deprecated since 1.35.
788  $allowedByHook = $this->hookRunner->onPageContentSave(
789  $this->getWikiPage(), $legacyUser, $mainContent, $summary,
790  $flags & EDIT_MINOR, null, null, $flags, $hookStatus
791  );
792  }
793 
794  if ( !$allowedByHook ) {
795  // The hook has prevented this change from being saved.
796  if ( $hookStatus->isOK() ) {
797  // Hook returned false but didn't call fatal(); use generic message
798  $hookStatus->fatal( 'edit-hook-aborted' );
799  }
800 
801  $this->status = $hookStatus;
802  return null;
803  }
804 
805  // Provide autosummaries if one is not provided and autosummaries are enabled
806  // XXX: $summary == null seems logical, but the empty string may actually come from the user
807  // XXX: Move this logic out of the storage layer! It does not belong here! Use a callback?
808  if ( $summary->text === '' && $summary->data === null ) {
809  $summary = $this->makeAutoSummary( $flags );
810  }
811 
812  // Actually create the revision and create/update the page.
813  // Do NOT yet set $this->status!
814  if ( $flags & EDIT_UPDATE ) {
815  $status = $this->doModify( $summary, $user, $flags );
816  } else {
817  $status = $this->doCreate( $summary, $user, $flags );
818  }
819 
820  // Promote user to any groups they meet the criteria for
821  DeferredUpdates::addCallableUpdate( static function () use ( $legacyUser ) {
822  $legacyUser->addAutopromoteOnceGroups( 'onEdit' );
823  $legacyUser->addAutopromoteOnceGroups( 'onView' ); // b/c
824  } );
825 
826  // NOTE: set $this->status only after all hooks have been called,
827  // so wasCommitted doesn't return true when called indirectly from a hook handler!
828  $this->status = $status;
829 
830  // TODO: replace bad status with Exceptions!
831  return ( $this->status && $this->status->isOK() )
832  ? $this->status->value['revision-record']
833  : null;
834  }
835 
847  public function updateRevision( int $revId = 0 ) {
848  if ( $this->wasCommitted() ) {
849  throw new RuntimeException(
850  'saveRevision() or updateRevision() has already been called on this PageUpdater!'
851  );
852  }
853 
854  // Low-level sanity check
855  if ( $this->getLinkTarget()->getText() === '' ) {
856  throw new RuntimeException( 'Something is trying to edit an article with an empty title' );
857  }
858 
860  $this->checkAllRolesAllowed(
861  $this->slotsUpdate->getModifiedRoles(),
862  $status
863  );
864  $this->checkAllRolesDerived(
865  $this->slotsUpdate->getModifiedRoles(),
866  $status
867  );
868  $this->checkAllRolesDerived(
869  $this->slotsUpdate->getRemovedRoles(),
870  $status
871  );
872 
873  if ( $revId === 0 ) {
874  $revision = $this->grabParentRevision();
875  } else {
876  $revision = $this->revisionStore->getRevisionById( $revId, RevisionStore::READ_LATEST );
877  }
878  if ( $revision === null ) {
879  $status->fatal( 'edit-gone-missing' );
880  }
881 
882  if ( !$status->isOK() ) {
883  $this->status = $status;
884  return;
885  }
886 
887  // Make sure the given content is allowed in the respective slots of this page
888  foreach ( $this->slotsUpdate->getModifiedRoles() as $role ) {
889  $slot = $this->slotsUpdate->getModifiedSlot( $role );
890  $roleHandler = $this->slotRoleRegistry->getRoleHandler( $role );
891 
892  if ( !$roleHandler->isAllowedModel( $slot->getModel(), $this->getTitle() ) ) {
893  $contentHandler = $this->contentHandlerFactory
894  ->getContentHandler( $slot->getModel() );
895  $this->status = Status::newFatal(
896  'content-not-allowed-here',
897  ContentHandler::getLocalizedName( $contentHandler->getModelID() ),
898  $this->getTitle()->getPrefixedText(),
899  wfMessage( $roleHandler->getNameMessageKey() )
900  // TODO: defer message lookup to caller
901  );
902  return;
903  }
904  }
905 
906  // do we need PST?
907 
908  $this->status = $this->doUpdate( $this->performer->getUser(), $revision );
909  }
910 
916  public function wasCommitted() {
917  return $this->status !== null;
918  }
919 
943  public function getStatus() {
944  return $this->status;
945  }
946 
952  public function wasSuccessful() {
953  return $this->status && $this->status->isOK();
954  }
955 
961  public function isNew() {
962  return $this->status && $this->status->isOK() && $this->status->value['new'];
963  }
964 
972  public function isUnchanged() {
973  return $this->status
974  && $this->status->isOK()
975  && $this->status->value['revision-record'] === null;
976  }
977 
984  public function getNewRevision() {
985  return ( $this->status && $this->status->isOK() )
986  ? $this->status->value['revision-record']
987  : null;
988  }
989 
1005  private function makeNewRevision(
1006  CommentStoreComment $comment,
1007  UserIdentity $user,
1008  $flags,
1009  Status $status
1010  ) {
1011  $wikiPage = $this->getWikiPage();
1012  $title = $this->getTitle();
1013  $parent = $this->grabParentRevision();
1014 
1015  // XXX: we expect to get a MutableRevisionRecord here, but that's a bit brittle!
1016  // TODO: introduce something like an UnsavedRevisionFactory service instead!
1018  $rev = $this->derivedDataUpdater->getRevision();
1019  '@phan-var MutableRevisionRecord $rev';
1020 
1021  // Avoid fatal error when the Title's ID changed, T204793
1022  if (
1023  $rev->getPageId() !== null && $title->exists()
1024  && $rev->getPageId() !== $title->getArticleID()
1025  ) {
1026  $titlePageId = $title->getArticleID();
1027  $revPageId = $rev->getPageId();
1028  $masterPageId = $title->getArticleID( Title::READ_LATEST );
1029 
1030  if ( $revPageId === $masterPageId ) {
1031  wfWarn( __METHOD__ . ": Encountered stale Title object: old ID was $titlePageId, "
1032  . "continuing with new ID from master, $masterPageId" );
1033  } else {
1034  throw new InvalidArgumentException(
1035  "Revision inherited page ID $revPageId from its parent, "
1036  . "but the provided Title object belongs to page ID $masterPageId"
1037  );
1038  }
1039  }
1040 
1041  $rev->setPageId( $title->getArticleID() );
1042 
1043  if ( $parent ) {
1044  $oldid = $parent->getId();
1045  $rev->setParentId( $oldid );
1046  } else {
1047  $oldid = 0;
1048  }
1049 
1050  $rev->setComment( $comment );
1051  $rev->setUser( $user );
1052  $rev->setMinorEdit( ( $flags & EDIT_MINOR ) > 0 );
1053 
1054  foreach ( $rev->getSlots()->getSlots() as $slot ) {
1055  $content = $slot->getContent();
1056 
1057  // XXX: We may push this up to the "edit controller" level, see T192777.
1058  // XXX: prepareSave() and isValid() could live in SlotRoleHandler
1059  // XXX: PrepareSave should not take a WikiPage!
1060  $legacyUser = self::toLegacyUser( $user );
1061  $prepStatus = $content->prepareSave( $wikiPage, $flags, $oldid, $legacyUser );
1062 
1063  // TODO: MCR: record which problem arose in which slot.
1064  $status->merge( $prepStatus );
1065  }
1066 
1067  $this->checkAllRequiredRoles(
1068  $rev->getSlotRoles(),
1069  $status
1070  );
1071 
1072  return $rev;
1073  }
1074 
1082  private function buildEditResult( RevisionRecord $revision, bool $isNew ) {
1083  $this->editResultBuilder->setRevisionRecord( $revision );
1084  $this->editResultBuilder->setIsNew( $isNew );
1085  $this->editResult = $this->editResultBuilder->buildEditResult();
1086  }
1087 
1100  private function doUpdate( UserIdentity $user, RevisionRecord $revision ) : Status {
1101  $currentRevision = $this->grabParentRevision();
1102  if ( !$currentRevision ) {
1103  // Article gone missing
1104  return Status::newFatal( 'edit-gone-missing' );
1105  }
1106 
1107  $dbw = $this->getDBConnectionRef( DB_MASTER );
1108  $dbw->startAtomic( __METHOD__ );
1109 
1110  $slots = $this->revisionStore->updateslotsOn( $revision, $this->slotsUpdate, $dbw );
1111 
1112  $dbw->endAtomic( __METHOD__ );
1113 
1114  // Return the slots and revision to the caller
1115  $newRevisionRecord = MutableRevisionRecord::newUpdatedRevisionRecord( $revision, $slots );
1117  'revision-record' => $newRevisionRecord,
1118  'slots' => $slots,
1119  ] );
1120 
1121  $isCurrent = $revision->getId( $this->getWikiId() ) ===
1122  $currentRevision->getId( $this->getWikiId() );
1123 
1124  if ( $isCurrent ) {
1125  // Update page_touched
1126  $this->getTitle()->invalidateCache( $newRevisionRecord->getTimestamp() );
1127 
1128  $this->buildEditResult( $newRevisionRecord, false );
1129 
1130  // Do secondary updates once the main changes have been committed...
1131  $wikiPage = $this->getWikiPage(); // TODO: use for legacy hooks only!
1133  $this->getAtomicSectionUpdate(
1134  $dbw,
1135  $wikiPage,
1136  $newRevisionRecord,
1137  $user,
1138  $revision->getComment(),
1139  EDIT_INTERNAL,
1140  $status,
1141  [ 'changed' => false, ]
1142  ),
1143  DeferredUpdates::PRESEND
1144  );
1145  }
1146 
1147  return $status;
1148  }
1149 
1158  private function doModify( CommentStoreComment $summary, UserIdentity $user, $flags ) {
1159  $wikiPage = $this->getWikiPage(); // TODO: use for legacy hooks only!
1160 
1161  // Update article, but only if changed.
1164  [ 'new' => false, 'revision' => null, 'revision-record' => null ],
1165  [ 'revision' => '1.35' ],
1166  __METHOD__ . ' status'
1167  )
1168  );
1169 
1170  $oldRev = $this->grabParentRevision();
1171  $oldid = $oldRev ? $oldRev->getId() : 0;
1172 
1173  if ( !$oldRev ) {
1174  // Article gone missing
1175  $status->fatal( 'edit-gone-missing' );
1176 
1177  return $status;
1178  }
1179 
1180  $newRevisionRecord = $this->makeNewRevision(
1181  $summary,
1182  $user,
1183  $flags,
1184  $status
1185  );
1186 
1187  if ( !$status->isOK() ) {
1188  return $status;
1189  }
1190 
1191  $now = $newRevisionRecord->getTimestamp();
1192 
1193  // XXX: we may want a flag that allows a null revision to be forced!
1194  $changed = $this->derivedDataUpdater->isChange();
1195 
1196  // We build the EditResult before the $change if/else branch in order to pass
1197  // the correct $newRevisionRecord to EditResultBuilder. In case this is a null
1198  // edit, $newRevisionRecord will be later overridden to its parent revision, which
1199  // would confuse EditResultBuilder.
1200  if ( !$changed ) {
1201  // This is a null edit, ensure original revision ID is set properly
1202  $this->editResultBuilder->setOriginalRevisionId( $oldid );
1203  }
1204  $this->buildEditResult( $newRevisionRecord, false );
1205 
1206  $legacyUser = self::toLegacyUser( $user );
1207 
1208  $dbw = $this->getDBConnectionRef( DB_MASTER );
1209 
1210  if ( $changed ) {
1211  $dbw->startAtomic( __METHOD__ );
1212 
1213  // Get the latest page_latest value while locking it.
1214  // Do a CAS style check to see if it's the same as when this method
1215  // started. If it changed then bail out before touching the DB.
1216  $latestNow = $wikiPage->lockAndGetLatest(); // TODO: move to storage service, pass DB
1217  if ( $latestNow != $oldid ) {
1218  // We don't need to roll back, since we did not modify the database yet.
1219  // XXX: Or do we want to rollback, any transaction started by calling
1220  // code will fail? If we want that, we should probably throw an exception.
1221  $dbw->endAtomic( __METHOD__ );
1222  // Page updated or deleted in the mean time
1223  $status->fatal( 'edit-conflict' );
1224 
1225  return $status;
1226  }
1227 
1228  // At this point we are now comitted to returning an OK
1229  // status unless some DB query error or other exception comes up.
1230  // This way callers don't have to call rollback() if $status is bad
1231  // unless they actually try to catch exceptions (which is rare).
1232 
1233  // Save revision content and meta-data
1234  $newRevisionRecord = $this->revisionStore->insertRevisionOn( $newRevisionRecord, $dbw );
1235 
1236  // Update page_latest and friends to reflect the new revision
1237  // TODO: move to storage service
1238  $wasRedirect = $this->derivedDataUpdater->wasRedirect();
1239  if ( !$wikiPage->updateRevisionOn( $dbw, $newRevisionRecord, null, $wasRedirect ) ) {
1240  throw new PageUpdateException( "Failed to update page row to use new revision." );
1241  }
1242 
1243  $editResult = $this->getEditResult();
1244  $tags = $this->computeEffectiveTags( $flags );
1245  $this->hookRunner->onRevisionFromEditComplete(
1246  $wikiPage, $newRevisionRecord, $editResult->getOriginalRevisionId(), $user, $tags
1247  );
1248 
1249  // Hook is hard deprecated since 1.35
1250  if ( $this->hookContainer->isRegistered( 'NewRevisionFromEditComplete' ) ) {
1251  // Only create Revision object if needed
1252  $newLegacyRevision = new Revision( $newRevisionRecord );
1253  $this->hookRunner->onNewRevisionFromEditComplete(
1254  $wikiPage,
1255  $newLegacyRevision,
1257  $legacyUser,
1258  $tags
1259  );
1260  }
1261 
1262  // Update recentchanges
1263  if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
1264  // Add RC row to the DB
1266  $now,
1267  $this->getTitle(),
1268  $newRevisionRecord->isMinor(),
1269  $legacyUser,
1270  $summary->text, // TODO: pass object when that becomes possible
1271  $oldid,
1272  $newRevisionRecord->getTimestamp(),
1273  ( $flags & EDIT_FORCE_BOT ) > 0,
1274  '',
1275  $oldRev->getSize(),
1276  $newRevisionRecord->getSize(),
1277  $newRevisionRecord->getId(),
1278  $this->rcPatrolStatus,
1279  $tags,
1280  $editResult
1281  );
1282  }
1283 
1284  $legacyUser->incEditCount();
1285 
1286  $dbw->endAtomic( __METHOD__ );
1287 
1288  // Return the new revision to the caller
1289  $status->value['revision-record'] = $newRevisionRecord;
1290 
1291  // Deprecated via DeprecatablePropertyArray
1292  $status->value['revision'] = static function () use ( $newRevisionRecord ) {
1293  return new Revision( $newRevisionRecord );
1294  };
1295  } else {
1296  // T34948: revision ID must be set to page {{REVISIONID}} and
1297  // related variables correctly. Likewise for {{REVISIONUSER}} (T135261).
1298  // Since we don't insert a new revision into the database, the least
1299  // error-prone way is to reuse given old revision.
1300  $newRevisionRecord = $oldRev;
1301 
1302  $status->warning( 'edit-no-change' );
1303  // Update page_touched as updateRevisionOn() was not called.
1304  // Other cache updates are managed in WikiPage::onArticleEdit()
1305  // via WikiPage::doEditUpdates().
1306  $this->getTitle()->invalidateCache( $now );
1307  }
1308 
1309  // Do secondary updates once the main changes have been committed...
1310  // NOTE: the updates have to be processed before sending the response to the client
1311  // (DeferredUpdates::PRESEND), otherwise the client may already be following the
1312  // HTTP redirect to the standard view before derived data has been created - most
1313  // importantly, before the parser cache has been updated. This would cause the
1314  // content to be parsed a second time, or may cause stale content to be shown.
1316  $this->getAtomicSectionUpdate(
1317  $dbw,
1318  $wikiPage,
1319  $newRevisionRecord,
1320  $user,
1321  $summary,
1322  $flags,
1323  $status,
1324  [ 'changed' => $changed, ]
1325  ),
1326  DeferredUpdates::PRESEND
1327  );
1328 
1329  return $status;
1330  }
1331 
1341  private function doCreate( CommentStoreComment $summary, UserIdentity $user, $flags ) {
1342  $wikiPage = $this->getWikiPage(); // TODO: use for legacy hooks only!
1343 
1344  if ( !$this->derivedDataUpdater->getSlots()->hasSlot( SlotRecord::MAIN ) ) {
1345  throw new PageUpdateException( 'Must provide a main slot when creating a page!' );
1346  }
1347 
1350  [ 'new' => true, 'revision' => null, 'revision-record' => null ],
1351  [ 'revision' => '1.35' ],
1352  __METHOD__ . ' status'
1353  )
1354  );
1355 
1356  $newRevisionRecord = $this->makeNewRevision(
1357  $summary,
1358  $user,
1359  $flags,
1360  $status
1361  );
1362 
1363  if ( !$status->isOK() ) {
1364  return $status;
1365  }
1366 
1367  $this->buildEditResult( $newRevisionRecord, true );
1368  $now = $newRevisionRecord->getTimestamp();
1369 
1370  $dbw = $this->getDBConnectionRef( DB_MASTER );
1371  $dbw->startAtomic( __METHOD__ );
1372 
1373  // Add the page record unless one already exists for the title
1374  // TODO: move to storage service
1375  $newid = $wikiPage->insertOn( $dbw );
1376  if ( $newid === false ) {
1377  $dbw->endAtomic( __METHOD__ );
1378  $status->fatal( 'edit-already-exists' );
1379 
1380  return $status;
1381  }
1382 
1383  // At this point we are now comitted to returning an OK
1384  // status unless some DB query error or other exception comes up.
1385  // This way callers don't have to call rollback() if $status is bad
1386  // unless they actually try to catch exceptions (which is rare).
1387  $newRevisionRecord->setPageId( $newid );
1388 
1389  // Save the revision text...
1390  $newRevisionRecord = $this->revisionStore->insertRevisionOn( $newRevisionRecord, $dbw );
1391 
1392  // Update the page record with revision data
1393  // TODO: move to storage service
1394  if ( !$wikiPage->updateRevisionOn( $dbw, $newRevisionRecord, 0 ) ) {
1395  throw new PageUpdateException( "Failed to update page row to use new revision." );
1396  }
1397 
1398  $tags = $this->computeEffectiveTags( $flags );
1399  $this->hookRunner->onRevisionFromEditComplete(
1400  $wikiPage, $newRevisionRecord, false, $user, $tags
1401  );
1402 
1403  $legacyUser = self::toLegacyUser( $user );
1404 
1405  // Hook is deprecated since 1.35
1406  if ( $this->hookContainer->isRegistered( 'NewRevisionFromEditComplete' ) ) {
1407  // ONly create Revision object if needed
1408  $newLegacyRevision = new Revision( $newRevisionRecord );
1409  $this->hookRunner->onNewRevisionFromEditComplete(
1410  $wikiPage,
1411  $newLegacyRevision,
1412  false,
1413  $legacyUser,
1414  $tags
1415  );
1416  }
1417 
1418  // Update recentchanges
1419  if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
1420  // Add RC row to the DB
1422  $now,
1423  $this->getTitle(),
1424  $newRevisionRecord->isMinor(),
1425  $legacyUser,
1426  $summary->text, // TODO: pass object when that becomes possible
1427  ( $flags & EDIT_FORCE_BOT ) > 0,
1428  '',
1429  $newRevisionRecord->getSize(),
1430  $newRevisionRecord->getId(),
1431  $this->rcPatrolStatus,
1432  $tags
1433  );
1434  }
1435 
1436  $legacyUser->incEditCount();
1437 
1438  if ( $this->usePageCreationLog ) {
1439  // Log the page creation
1440  // @TODO: Do we want a 'recreate' action?
1441  $logEntry = new ManualLogEntry( 'create', 'create' );
1442  $logEntry->setPerformer( $user );
1443  $logEntry->setTarget( $this->getTitle() );
1444  $logEntry->setComment( $summary->text );
1445  $logEntry->setTimestamp( $now );
1446  $logEntry->setAssociatedRevId( $newRevisionRecord->getId() );
1447  $logEntry->insert();
1448  // Note that we don't publish page creation events to recentchanges
1449  // (i.e. $logEntry->publish()) since this would create duplicate entries,
1450  // one for the edit and one for the page creation.
1451  }
1452 
1453  $dbw->endAtomic( __METHOD__ );
1454 
1455  // Return the new revision to the caller
1456  $status->value['revision-record'] = $newRevisionRecord;
1457 
1458  // Deprecated via DeprecatablePropertyArray
1459  $status->value['revision'] = static function () use ( $newRevisionRecord ) {
1460  return new Revision( $newRevisionRecord );
1461  };
1462 
1463  // Do secondary updates once the main changes have been committed...
1465  $this->getAtomicSectionUpdate(
1466  $dbw,
1467  $wikiPage,
1468  $newRevisionRecord,
1469  $user,
1470  $summary,
1471  $flags,
1472  $status,
1473  [ 'created' => true ]
1474  ),
1475  DeferredUpdates::PRESEND
1476  );
1477 
1478  return $status;
1479  }
1480 
1481  private function getAtomicSectionUpdate(
1482  IDatabase $dbw,
1484  RevisionRecord $newRevisionRecord,
1485  UserIdentity $user,
1486  CommentStoreComment $summary,
1487  $flags,
1488  Status $status,
1489  $hints = []
1490  ) {
1491  return new AtomicSectionUpdate(
1492  $dbw,
1493  __METHOD__,
1494  function () use (
1495  $wikiPage, $newRevisionRecord, $user,
1496  $summary, $flags, $status, $hints
1497  ) {
1498  // set debug data
1499  $hints['causeAction'] = 'edit-page';
1500  $hints['causeAgent'] = $user->getName();
1501 
1502  $editResult = $this->getEditResult();
1503  $hints['editResult'] = $editResult;
1504 
1505  if ( $editResult->isRevert() ) {
1506  // Should the reverted tag update be scheduled right away?
1507  // The revert is approved if either patrolling is disabled or the
1508  // edit is patrolled or autopatrolled.
1509  $approved = !$this->serviceOptions->get( 'UseRCPatrol' ) ||
1510  $this->rcPatrolStatus === RecentChange::PRC_PATROLLED ||
1511  $this->rcPatrolStatus === RecentChange::PRC_AUTOPATROLLED;
1512 
1513  // Allow extensions to override the patrolling subsystem.
1514  $this->hookRunner->onBeforeRevertedTagUpdate(
1515  $wikiPage,
1516  $user,
1517  $summary,
1518  $flags,
1519  $newRevisionRecord,
1520  $editResult,
1521  $approved
1522  );
1523  $hints['approved'] = $approved;
1524  }
1525 
1526  // Update links tables, site stats, etc.
1527  $this->derivedDataUpdater->prepareUpdate( $newRevisionRecord, $hints );
1528  $this->derivedDataUpdater->doUpdates();
1529 
1530  $created = $hints['created'] ?? false;
1531  $flags |= ( $created ? EDIT_NEW : EDIT_UPDATE );
1532 
1533  // PageSaveComplete replaces the other two since 1.35
1534  $this->hookRunner->onPageSaveComplete(
1535  $wikiPage,
1536  $user,
1537  $summary->text,
1538  $flags,
1539  $newRevisionRecord,
1540  $editResult
1541  );
1542 
1543  // Both hooks are hard deprecated since 1.35
1544  if ( !$this->hookContainer->isRegistered( 'PageContentInsertComplete' )
1545  && !$this->hookContainer->isRegistered( 'PageContentSaveComplete' )
1546  ) {
1547  // Don't go on to create a Revision unless its needed
1548  return;
1549  }
1550 
1551  $legacyUser = self::toLegacyUser( $user );
1552 
1553  $mainContent = $newRevisionRecord->getContent( SlotRecord::MAIN, RevisionRecord::RAW );
1554  $newLegacyRevision = new Revision( $newRevisionRecord );
1555  if ( $created ) {
1556  // Trigger post-create hook
1557  $this->hookRunner->onPageContentInsertComplete( $wikiPage, $legacyUser,
1558  $mainContent, $summary->text, $flags & EDIT_MINOR,
1559  null, null, $flags, $newLegacyRevision );
1560  }
1561 
1562  // Trigger post-save hook
1563  $this->hookRunner->onPageContentSaveComplete( $wikiPage, $legacyUser, $mainContent,
1564  $summary->text, $flags & EDIT_MINOR, null,
1565  null, $flags, $newLegacyRevision, $status,
1567  }
1568  );
1569  }
1570 
1574  private function getRequiredSlotRoles() {
1575  return $this->slotRoleRegistry->getRequiredRoles( $this->getTitle() );
1576  }
1577 
1581  private function getAllowedSlotRoles() {
1582  return $this->slotRoleRegistry->getAllowedRoles( $this->getTitle() );
1583  }
1584 
1585  private function ensureRoleAllowed( $role ) {
1586  $allowedRoles = $this->getAllowedSlotRoles();
1587  if ( !in_array( $role, $allowedRoles ) ) {
1588  throw new PageUpdateException( "Slot role `$role` is not allowed." );
1589  }
1590  }
1591 
1592  private function ensureRoleNotRequired( $role ) {
1593  $requiredRoles = $this->getRequiredSlotRoles();
1594  if ( in_array( $role, $requiredRoles ) ) {
1595  throw new PageUpdateException( "Slot role `$role` is required." );
1596  }
1597  }
1598 
1603  private function checkAllRolesAllowed( array $roles, Status $status ) {
1604  $allowedRoles = $this->getAllowedSlotRoles();
1605 
1606  $forbidden = array_diff( $roles, $allowedRoles );
1607  if ( !empty( $forbidden ) ) {
1608  $status->error(
1609  'edit-slots-cannot-add',
1610  count( $forbidden ),
1611  implode( ', ', $forbidden )
1612  );
1613  }
1614  }
1615 
1620  private function checkAllRolesDerived( array $roles, Status $status ) {
1621  $notDerived = array_filter(
1622  $roles,
1623  function ( $role ) {
1624  return !$this->slotRoleRegistry->getRoleHandler( $role )->isDerived();
1625  }
1626  );
1627  if ( $notDerived ) {
1628  $status->error(
1629  'edit-slots-not-derived',
1630  count( $notDerived ),
1631  implode( ', ', $notDerived )
1632  );
1633  }
1634  }
1635 
1640  private function checkNoRolesRequired( array $roles, Status $status ) {
1641  $requiredRoles = $this->getRequiredSlotRoles();
1642 
1643  $needed = array_diff( $roles, $requiredRoles );
1644  if ( !empty( $needed ) ) {
1645  $status->error(
1646  'edit-slots-cannot-remove',
1647  count( $needed ),
1648  implode( ', ', $needed )
1649  );
1650  }
1651  }
1652 
1657  private function checkAllRequiredRoles( array $roles, Status $status ) {
1658  $requiredRoles = $this->getRequiredSlotRoles();
1659 
1660  $missing = array_diff( $requiredRoles, $roles );
1661  if ( !empty( $missing ) ) {
1662  $status->error(
1663  'edit-slots-missing',
1664  count( $missing ),
1665  implode( ', ', $missing )
1666  );
1667  }
1668  }
1669 
1670 }
MediaWiki\Storage\PageUpdater\addTags
addTags(array $tags)
Sets tags to apply to this update.
Definition: PageUpdater.php:548
ContentHandler
A content handler knows how do deal with a specific type of content on a wiki page.
Definition: ContentHandler.php:59
EDIT_AUTOSUMMARY
const EDIT_AUTOSUMMARY
Definition: Defines.php:142
CommentStoreComment\newUnsavedComment
static newUnsavedComment( $comment, array $data=null)
Create a new, unsaved CommentStoreComment.
Definition: CommentStoreComment.php:67
Revision\RevisionAccessException
Exception representing a failure to look up a revision.
Definition: RevisionAccessException.php:34
MediaWiki\Storage\PageUpdater\saveRevision
saveRevision(CommentStoreComment $summary, $flags=0)
Change an existing article or create a new article.
Definition: PageUpdater.php:701
Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:47
MediaWiki\Storage\PageUpdater\$tags
array $tags
Definition: PageUpdater.php:160
StatusValue\newFatal
static newFatal( $message,... $parameters)
Factory function for fatal errors.
Definition: StatusValue.php:70
MediaWiki\Storage\PageUpdater\setAjaxEditStash
setAjaxEditStash( $ajaxEditStash)
Definition: PageUpdater.php:295
MediaWiki\Storage\PageUpdater\$usePageCreationLog
bool $usePageCreationLog
whether to create a log entry for new page creations.
Definition: PageUpdater.php:150
WikiPage\updateRevisionOn
updateRevisionOn( $dbw, $revision, $lastRevision=null, $lastRevIsRedirect=null)
Update the page record to point to a newly saved revision.
Definition: WikiPage.php:1435
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:123
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\checkNoRolesRequired
checkNoRolesRequired(array $roles, Status $status)
Definition: PageUpdater.php:1640
StatusValue\error
error( $message,... $parameters)
Add an error, do not set fatal flag This can be used for non-fatal errors.
Definition: StatusValue.php:242
MediaWiki\Storage\PageUpdater\inheritSlot
inheritSlot(SlotRecord $originalSlot)
Explicitly inherit a slot from some earlier revision.
Definition: PageUpdater.php:458
Revision\RevisionStore
Service for looking up page revisions.
Definition: RevisionStore.php:89
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:493
MediaWiki\Storage\PageUpdater\getWikiId
getWikiId()
Definition: PageUpdater.php:299
RecentChange
Utility class for creating new RC entries.
Definition: RecentChange.php:76
MediaWiki\Storage\EditResult\getUndidRevId
getUndidRevId()
If the edit was an undo, returns the oldest revision that was undone.
Definition: EditResult.php:173
MediaWiki\Storage\PageUpdater\updateRevision
updateRevision(int $revId=0)
Updates derived slots of an existing article.
Definition: PageUpdater.php:847
MediaWiki\Storage\PageUpdater\getExplicitTags
getExplicitTags()
Returns the list of tags set using the addTag() method.
Definition: PageUpdater.php:560
MediaWiki\Storage\PageUpdater\getAtomicSectionUpdate
getAtomicSectionUpdate(IDatabase $dbw, WikiPage $wikiPage, RevisionRecord $newRevisionRecord, UserIdentity $user, CommentStoreComment $summary, $flags, Status $status, $hints=[])
Definition: PageUpdater.php:1481
StatusValue\warning
warning( $message,... $parameters)
Add a new warning.
Definition: StatusValue.php:227
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:527
WikiPage
Class representing a MediaWiki article and history.
Definition: WikiPage.php:61
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:257
MediaWiki\Storage\PageUpdater\setUsePageCreationLog
setUsePageCreationLog( $use)
Whether to create a log entry for new page creations.
Definition: PageUpdater.php:287
wfMessage
wfMessage( $key,... $params)
This is the function for getting translated interface messages.
Definition: GlobalFunctions.php:1231
MediaWiki\Storage\PageUpdater\$ajaxEditStash
bool $ajaxEditStash
see $wgAjaxEditStash
Definition: PageUpdater.php:155
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:598
MediaWiki\Storage\PageUpdater\$wikiPage
WikiPage $wikiPage
Definition: PageUpdater.php:99
MediaWiki\Storage\PageUpdater\__construct
__construct(Authority $performer, WikiPage $wikiPage, DerivedPageDataUpdater $derivedDataUpdater, ILoadBalancer $loadBalancer, RevisionStore $revisionStore, SlotRoleRegistry $slotRoleRegistry, IContentHandlerFactory $contentHandlerFactory, HookContainer $hookContainer, ServiceOptions $serviceOptions, array $softwareTags)
Definition: PageUpdater.php:205
User\newFromIdentity
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:663
MediaWiki\Storage\PageUpdater\doCreate
doCreate(CommentStoreComment $summary, UserIdentity $user, $flags)
Definition: PageUpdater.php:1341
MediaWiki\Storage\PageUpdater\$loadBalancer
ILoadBalancer $loadBalancer
Definition: PageUpdater.php:109
RecentChange\notifyEdit
static notifyEdit( $timestamp, $title, $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:661
MediaWiki\Storage\PageUpdater\wasCommitted
wasCommitted()
Whether saveRevision() has been called on this instance.
Definition: PageUpdater.php:916
MediaWiki\Storage\EditResultBuilder\CONSTRUCTOR_OPTIONS
const CONSTRUCTOR_OPTIONS
Definition: EditResultBuilder.php:41
MediaWiki\Storage\PageUpdater\$editResultBuilder
EditResultBuilder $editResultBuilder
Definition: PageUpdater.php:175
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:1585
Status
Generic operation result class Has warning/error list, boolean status and arbitrary value.
Definition: Status.php:44
Revision
Definition: Revision.php:40
MediaWiki\Storage\PageUpdater\makeAutoSummary
makeAutoSummary( $flags)
Definition: PageUpdater.php:633
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:191
MediaWiki\Storage\PageUpdater\checkFlags
checkFlags( $flags)
Check flags and add EDIT_NEW or EDIT_UPDATE to them as needed.
Definition: PageUpdater.php:413
MediaWiki\Storage\PageUpdater\wasSuccessful
wasSuccessful()
Whether saveRevision() completed successfully.
Definition: PageUpdater.php:952
MediaWiki\Storage\PageUpdater\doUpdate
doUpdate(UserIdentity $user, RevisionRecord $revision)
Update derived slots in an existing revision.
Definition: PageUpdater.php:1100
MWException
MediaWiki exception.
Definition: MWException.php:29
MediaWiki\Storage\PageUpdater\setContent
setContent( $role, Content $content)
Set the new content for the given slot role.
Definition: PageUpdater.php:427
MediaWiki\Config\ServiceOptions
A class for passing options to services.
Definition: ServiceOptions.php:27
MediaWiki\Storage\PageUpdater\markAsRevert
markAsRevert(int $revertMethod, int $oldestRevertedRevId, int $newestRevertedRevId=0)
Marks this edit as a revert and applies relevant information.
Definition: PageUpdater.php:510
MediaWiki\Storage\PageUpdater\getTitle
getTitle()
Definition: PageUpdater.php:323
Revision\SlotRecord\getRole
getRole()
Returns the role of the slot.
Definition: SlotRecord.php:506
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:1388
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:984
MediaWiki\Storage\PageUpdater\computeEffectiveTags
computeEffectiveTags( $flags)
Definition: PageUpdater.php:568
MediaWiki\Storage\PageUpdater\getWikiPage
getWikiPage()
Definition: PageUpdater.php:331
StatusValue\merge
merge( $other, $overwriteValue=false)
Merge another status object into this one.
Definition: StatusValue.php:268
EDIT_NEW
const EDIT_NEW
Definition: Defines.php:136
Revision\RevisionRecord\RAW
const RAW
Definition: RevisionRecord.php:64
MediaWiki\User\UserIdentity\getName
getName()
$title
$title
Definition: testCompression.php:38
MediaWiki\Storage\PageUpdater\removeSlot
removeSlot( $role)
Removes the slot with the given role.
Definition: PageUpdater.php:476
MediaWiki\Storage\EditResultBuilder
Builder class for the EditResult object.
Definition: EditResultBuilder.php:39
DB_MASTER
const DB_MASTER
Definition: defines.php:26
MediaWiki\Storage\PageUpdater\$serviceOptions
ServiceOptions $serviceOptions
Definition: PageUpdater.php:190
MediaWiki\Storage\PageUpdater\getRequiredSlotRoles
getRequiredSlotRoles()
Definition: PageUpdater.php:1574
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:3196
MediaWiki\Storage\PageUpdater\checkAllRolesDerived
checkAllRolesDerived(array $roles, Status $status)
Definition: PageUpdater.php:1620
MediaWiki\Storage\PageUpdater\$hookContainer
HookContainer $hookContainer
Definition: PageUpdater.php:134
MediaWiki\Permissions\Authority
Definition: Authority.php:30
MediaWiki\Storage\PageUpdater\checkAllRolesAllowed
checkAllRolesAllowed(array $roles, Status $status)
Definition: PageUpdater.php:1603
MediaWiki\Storage\PageUpdater\addTag
addTag( $tag)
Sets a tag to apply to this update.
Definition: PageUpdater.php:537
RecentChange\PRC_PATROLLED
const PRC_PATROLLED
Definition: RecentChange.php:86
MediaWiki\Storage\PageUpdater\getAllowedSlotRoles
getAllowedSlotRoles()
Definition: PageUpdater.php:1581
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
currently enabled software change tags
Definition: PageUpdater.php:185
MediaWiki\Storage\PageUpdater\doModify
doModify(CommentStoreComment $summary, UserIdentity $user, $flags)
Definition: PageUpdater.php:1158
$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:299
MediaWiki\Content\IContentHandlerFactory
Definition: IContentHandlerFactory.php:10
StatusValue\newGood
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:82
Revision\MutableRevisionRecord
Definition: MutableRevisionRecord.php:45
MediaWiki\Storage\PageUpdater\$contentHandlerFactory
IContentHandlerFactory $contentHandlerFactory
Definition: PageUpdater.php:124
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:308
Revision\RevisionRecord\getId
getId( $wikiId=self::LOCAL)
Get revision ID.
Definition: RevisionRecord.php:295
MediaWiki\Storage\PageUpdater\$useAutomaticEditSummaries
bool $useAutomaticEditSummaries
see $wgUseAutomaticEditSummaries
Definition: PageUpdater.php:140
MediaWiki\Storage\PageUpdater\hasEditConflict
hasEditConflict( $expectedParentRevision)
Checks whether this update conflicts with another update performed between the client loading data to...
Definition: PageUpdater.php:369
EDIT_SUPPRESS_RC
const EDIT_SUPPRESS_RC
Definition: Defines.php:139
RecentChange\PRC_AUTOPATROLLED
const PRC_AUTOPATROLLED
Definition: RecentChange.php:87
MediaWiki\Storage\PageUpdater\$hookRunner
HookRunner $hookRunner
Definition: PageUpdater.php:129
MediaWiki\Storage
Definition: BlobAccessException.php:23
Revision\SlotRecord\MAIN
const MAIN
Definition: SlotRecord.php:43
Wikimedia\Rdbms\DBUnexpectedError
@newable
Definition: DBUnexpectedError.php:29
MediaWiki\Storage\PageUpdater\$performer
Authority $performer
Definition: PageUpdater.php:94
EDIT_UPDATE
const EDIT_UPDATE
Definition: Defines.php:137
Content
Base interface for content objects.
Definition: Content.php:35
MediaWiki\Storage\PageUpdater\$status
Status null $status
Definition: PageUpdater.php:170
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:165
Title
Represents a title within MediaWiki.
Definition: Title.php:46
MediaWiki\Storage\PageUpdater\makeNewRevision
makeNewRevision(CommentStoreComment $comment, UserIdentity $user, $flags, Status $status)
Constructs a MutableRevisionRecord based on the Content prepared by the DerivedPageDataUpdater.
Definition: PageUpdater.php:1005
MediaWiki\Storage\PageUpdater\grabParentRevision
grabParentRevision()
Returns the revision that was the page's current revision when grabParentRevision() was first called.
Definition: PageUpdater.php:403
RecentChange\notifyNew
static notifyNew( $timestamp, $title, $minor, $user, $comment, $bot, $ip='', $size=0, $newId=0, $patrol=0, $tags=[])
Makes an entry in the database corresponding to page creation Note: the title object must be loaded w...
Definition: RecentChange.php:735
MediaWiki\Storage\PageUpdater\getStatus
getStatus()
The Status object indicating whether saveRevision() was successful, or null if saveRevision() was not...
Definition: PageUpdater.php:943
MediaWiki\Storage\PageUpdater\setRcPatrolStatus
setRcPatrolStatus( $status)
Sets the "patrolled" status of the edit.
Definition: PageUpdater.php:276
MediaWiki\Storage\PageUpdater\$rcPatrolStatus
int $rcPatrolStatus
the RC patrol status the new revision should be marked with.
Definition: PageUpdater.php:145
RecentChange\PRC_UNPATROLLED
const PRC_UNPATROLLED
Definition: RecentChange.php:85
MediaWiki\Storage\EditResult\isRevert
isRevert()
Whether the edit was a revert, not necessarily exact.
Definition: EditResult.php:221
MediaWiki\Storage\PageUpdater\ensureRoleNotRequired
ensureRoleNotRequired( $role)
Definition: PageUpdater.php:1592
MediaWiki\Storage\PageUpdater\getContentHandler
getContentHandler( $role)
Definition: PageUpdater.php:612
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:429
ManualLogEntry
Class for creating new log entries and inserting them into the database.
Definition: ManualLogEntry.php:43
MediaWiki\Storage\PageUpdater\buildEditResult
buildEditResult(RevisionRecord $revision, bool $isNew)
Builds the EditResult for this update.
Definition: PageUpdater.php:1082
MediaWiki\Storage\PageUpdater\$derivedDataUpdater
DerivedPageDataUpdater $derivedDataUpdater
Definition: PageUpdater.php:104
MediaWiki\Storage\PageUpdater\checkAllRequiredRoles
checkAllRequiredRoles(array $roles, Status $status)
Definition: PageUpdater.php:1657
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:88
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:1081
MediaWiki\HookContainer\HookRunner
This class provides an implementation of the core hook interfaces, forwarding hook calls to HookConta...
Definition: HookRunner.php:576
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:78
MediaWiki\Linker\LinkTarget
Definition: LinkTarget.php:26
MediaWiki\Storage\PageUpdater\toLegacyUser
static toLegacyUser(UserIdentity $user)
Definition: PageUpdater.php:252
EDIT_FORCE_BOT
const EDIT_FORCE_BOT
Definition: Defines.php:140
EDIT_MINOR
const EDIT_MINOR
Definition: Defines.php:138
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:104
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:66
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:264
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:263
MediaWiki\Storage\PageUpdater\$editResult
EditResult null $editResult
Definition: PageUpdater.php:180
MediaWiki\Debug\DeprecatablePropertyArray
ArrayAccess implementation that supports deprecating access to certain properties.
Definition: DeprecatablePropertyArray.php:16
MediaWiki\Storage\PageUpdater\isNew
isNew()
Whether saveRevision() was called and created a new page.
Definition: PageUpdater.php:961
Wikimedia\Rdbms\ILoadBalancer
Database cluster connection, tracking, load balancing, and transaction manager interface.
Definition: ILoadBalancer.php:81
Revision\RevisionRecord\getContent
getContent( $role, $audience=self::FOR_PUBLIC, Authority $performer=null)
Returns the Content of the given slot of this revision.
Definition: RevisionRecord.php:172
MediaWiki\Storage\PageUpdater\isUnchanged
isUnchanged()
Whether saveRevision() did not create a revision because the content didn't change (null-edit).
Definition: PageUpdater.php:972
MediaWiki\Storage\PageUpdater\setSlot
setSlot(SlotRecord $slot)
Set the new slot for the given slot role.
Definition: PageUpdater.php:438
Revision\SlotRecord
Value object representing a content slot associated with a page revision.
Definition: SlotRecord.php:40
MediaWiki\Config\ServiceOptions\assertRequiredOptions
assertRequiredOptions(array $expectedKeys)
Assert that the list of options provided in this instance exactly match $expectedKeys,...
Definition: ServiceOptions.php:66
MediaWiki\Storage\PageUpdater\getLinkTarget
getLinkTarget()
Definition: PageUpdater.php:315
EDIT_INTERNAL
const EDIT_INTERNAL
Definition: Defines.php:143