MediaWiki  master
PageUpdater.php
Go to the documentation of this file.
1 <?php
25 namespace MediaWiki\Storage;
26 
30 use Content;
33 use Hooks;
47 use Status;
48 use Title;
49 use User;
56 
72 class PageUpdater {
73 
77  private $user;
78 
82  private $wikiPage;
83 
88 
92  private $loadBalancer;
93 
97  private $revisionStore;
98 
103 
109 
114 
118  private $usePageCreationLog = true;
119 
123  private $ajaxEditStash = true;
124 
128  private $originalRevId = false;
129 
133  private $tags = [];
134 
138  private $undidRevId = 0;
139 
143  private $slotsUpdate;
144 
148  private $status = null;
149 
158  public function __construct(
159  User $user,
165  ) {
166  $this->user = $user;
167  $this->wikiPage = $wikiPage;
168  $this->derivedDataUpdater = $derivedDataUpdater;
169 
170  $this->loadBalancer = $loadBalancer;
171  $this->revisionStore = $revisionStore;
172  $this->slotRoleRegistry = $slotRoleRegistry;
173 
174  $this->slotsUpdate = new RevisionSlotsUpdate();
175  }
176 
185  $this->useAutomaticEditSummaries = $useAutomaticEditSummaries;
186  }
187 
197  public function setRcPatrolStatus( $status ) {
198  $this->rcPatrolStatus = $status;
199  }
200 
208  public function setUsePageCreationLog( $use ) {
209  $this->usePageCreationLog = $use;
210  }
211 
216  public function setAjaxEditStash( $ajaxEditStash ) {
217  $this->ajaxEditStash = $ajaxEditStash;
218  }
219 
220  private function getWikiId() {
221  return false; // TODO: get from RevisionStore!
222  }
223 
229  private function getDBConnectionRef( $mode ) {
230  return $this->loadBalancer->getConnectionRef( $mode, [], $this->getWikiId() );
231  }
232 
236  private function getLinkTarget() {
237  // NOTE: eventually, we won't get a WikiPage passed into the constructor any more
238  return $this->wikiPage->getTitle();
239  }
240 
244  private function getTitle() {
245  // NOTE: eventually, we won't get a WikiPage passed into the constructor any more
246  return $this->wikiPage->getTitle();
247  }
248 
252  private function getWikiPage() {
253  // NOTE: eventually, we won't get a WikiPage passed into the constructor any more
254  return $this->wikiPage;
255  }
256 
290  public function hasEditConflict( $expectedParentRevision ) {
291  $parent = $this->grabParentRevision();
292  $parentId = $parent ? $parent->getId() : 0;
293 
294  return $parentId !== $expectedParentRevision;
295  }
296 
324  public function grabParentRevision() {
325  return $this->derivedDataUpdater->grabCurrentRevision();
326  }
327 
334  private function checkFlags( $flags ) {
335  if ( !( $flags & EDIT_NEW ) && !( $flags & EDIT_UPDATE ) ) {
336  $flags |= ( $this->derivedDataUpdater->pageExisted() ) ? EDIT_UPDATE : EDIT_NEW;
337  }
338 
339  return $flags;
340  }
341 
348  public function setContent( $role, Content $content ) {
349  $this->ensureRoleAllowed( $role );
350 
351  $this->slotsUpdate->modifyContent( $role, $content );
352  }
353 
359  public function setSlot( SlotRecord $slot ) {
360  $this->ensureRoleAllowed( $slot->getRole() );
361 
362  $this->slotsUpdate->modifySlot( $slot );
363  }
364 
379  public function inheritSlot( SlotRecord $originalSlot ) {
380  // NOTE: slots can be inherited even if the role is not "allowed" on the title.
381  // NOTE: this slot is inherited from some other revision, but it's
382  // a "modified" slot for the RevisionSlotsUpdate and DerivedPageDataUpdater,
383  // since it's not implicitly inherited from the parent revision.
384  $inheritedSlot = SlotRecord::newInherited( $originalSlot );
385  $this->slotsUpdate->modifySlot( $inheritedSlot );
386  }
387 
397  public function removeSlot( $role ) {
398  $this->ensureRoleNotRequired( $role );
399 
400  $this->slotsUpdate->removeSlot( $role );
401  }
402 
409  public function getOriginalRevisionId() {
410  return $this->originalRevId;
411  }
412 
425  Assert::parameterType( 'integer|boolean', $originalRevId, '$originalRevId' );
426  $this->originalRevId = $originalRevId;
427  }
428 
435  public function getUndidRevisionId() {
436  return $this->undidRevId;
437  }
438 
446  public function setUndidRevisionId( $undidRevId ) {
447  Assert::parameterType( 'integer', $undidRevId, '$undidRevId' );
448  $this->undidRevId = $undidRevId;
449  }
450 
457  public function addTag( $tag ) {
458  Assert::parameterType( 'string', $tag, '$tag' );
459  $this->tags[] = trim( $tag );
460  }
461 
468  public function addTags( array $tags ) {
469  Assert::parameterElementType( 'string', $tags, '$tags' );
470  foreach ( $tags as $tag ) {
471  $this->addTag( $tag );
472  }
473  }
474 
480  public function getExplicitTags() {
481  return $this->tags;
482  }
483 
488  private function computeEffectiveTags( $flags ) {
489  $tags = $this->tags;
490 
491  foreach ( $this->slotsUpdate->getModifiedRoles() as $role ) {
492  $old_content = $this->getParentContent( $role );
493 
494  $handler = $this->getContentHandler( $role );
495  $content = $this->slotsUpdate->getModifiedSlot( $role )->getContent();
496 
497  // TODO: MCR: Do this for all slots. Also add tags for removing roles!
498  $tag = $handler->getChangeTag( $old_content, $content, $flags );
499  // If there is no applicable tag, null is returned, so we need to check
500  if ( $tag ) {
501  $tags[] = $tag;
502  }
503  }
504 
505  // Check for undo tag
506  if ( $this->undidRevId !== 0 && in_array( 'mw-undo', ChangeTags::getSoftwareTags() ) ) {
507  $tags[] = 'mw-undo';
508  }
509 
510  return array_unique( $tags );
511  }
512 
520  private function getParentContent( $role ) {
521  $parent = $this->grabParentRevision();
522 
523  if ( $parent && $parent->hasSlot( $role ) ) {
524  return $parent->getContent( $role, RevisionRecord::RAW );
525  }
526 
527  return null;
528  }
529 
534  private function getContentHandler( $role ) {
535  // TODO: inject something like a ContentHandlerRegistry
536  if ( $this->slotsUpdate->isModifiedSlot( $role ) ) {
537  $slot = $this->slotsUpdate->getModifiedSlot( $role );
538  } else {
539  $parent = $this->grabParentRevision();
540 
541  if ( $parent ) {
542  $slot = $parent->getSlot( $role, RevisionRecord::RAW );
543  } else {
544  throw new RevisionAccessException( 'No such slot: ' . $role );
545  }
546  }
547 
548  return ContentHandler::getForModelID( $slot->getModel() );
549  }
550 
556  private function makeAutoSummary( $flags ) {
557  if ( !$this->useAutomaticEditSummaries || ( $flags & EDIT_AUTOSUMMARY ) === 0 ) {
559  }
560 
561  // NOTE: this generates an auto-summary for SOME RANDOM changed slot!
562  // TODO: combine auto-summaries for multiple slots!
563  // XXX: this logic should not be in the storage layer!
564  $roles = $this->slotsUpdate->getModifiedRoles();
565  $role = reset( $roles );
566 
567  if ( $role === false ) {
569  }
570 
571  $handler = $this->getContentHandler( $role );
572  $content = $this->slotsUpdate->getModifiedSlot( $role )->getContent();
573  $old_content = $this->getParentContent( $role );
574  $summary = $handler->getAutosummary( $old_content, $content, $flags );
575 
576  return CommentStoreComment::newUnsavedComment( $summary );
577  }
578 
624  public function saveRevision( CommentStoreComment $summary, $flags = 0 ) {
625  // Defend against mistakes caused by differences with the
626  // signature of WikiPage::doEditContent.
627  Assert::parameterType( 'integer', $flags, '$flags' );
628 
629  if ( $this->wasCommitted() ) {
630  throw new RuntimeException( 'saveRevision() has already been called on this PageUpdater!' );
631  }
632 
633  // Low-level sanity check
634  if ( $this->getLinkTarget()->getText() === '' ) {
635  throw new RuntimeException( 'Something is trying to edit an article with an empty title' );
636  }
637 
638  // NOTE: slots can be inherited even if the role is not "allowed" on the title.
640  $this->checkAllRolesAllowed(
641  $this->slotsUpdate->getModifiedRoles(),
642  $status
643  );
644  $this->checkNoRolesRequired(
645  $this->slotsUpdate->getRemovedRoles(),
646  $status
647  );
648 
649  if ( !$status->isOK() ) {
650  return null;
651  }
652 
653  // Make sure the given content is allowed in the respective slots of this page
654  foreach ( $this->slotsUpdate->getModifiedRoles() as $role ) {
655  $slot = $this->slotsUpdate->getModifiedSlot( $role );
656  $roleHandler = $this->slotRoleRegistry->getRoleHandler( $role );
657 
658  if ( !$roleHandler->isAllowedModel( $slot->getModel(), $this->getTitle() ) ) {
659  $contentHandler = ContentHandler::getForModelID( $slot->getModel() );
660  $this->status = Status::newFatal( 'content-not-allowed-here',
661  ContentHandler::getLocalizedName( $contentHandler->getModelID() ),
662  $this->getTitle()->getPrefixedText(),
663  wfMessage( $roleHandler->getNameMessageKey() )
664  // TODO: defer message lookup to caller
665  );
666  return null;
667  }
668  }
669 
670  // Load the data from the master database if needed. Needed to check flags.
671  // NOTE: This grabs the parent revision as the CAS token, if grabParentRevision
672  // wasn't called yet. If the page is modified by another process before we are done with
673  // it, this method must fail (with status 'edit-conflict')!
674  // NOTE: The parent revision may be different from $this->originalRevisionId.
675  $this->grabParentRevision();
676  $flags = $this->checkFlags( $flags );
677 
678  // Avoid statsd noise and wasted cycles check the edit stash (T136678)
679  if ( ( $flags & EDIT_INTERNAL ) || ( $flags & EDIT_FORCE_BOT ) ) {
680  $useStashed = false;
681  } else {
682  $useStashed = $this->ajaxEditStash;
683  }
684 
685  // TODO: use this only for the legacy hook, and only if something uses the legacy hook
686  $wikiPage = $this->getWikiPage();
687 
688  $user = $this->user;
689 
690  // Prepare the update. This performs PST and generates the canonical ParserOutput.
691  $this->derivedDataUpdater->prepareContent(
692  $this->user,
693  $this->slotsUpdate,
694  $useStashed
695  );
696 
697  // TODO: don't force initialization here!
698  // This is a hack to work around the fact that late initialization of the ParserOutput
699  // causes ApiFlowEditHeaderTest::testCache to fail. Whether that failure indicates an
700  // actual problem, or is just an issue with the test setup, remains to be determined
701  // [dk, 2018-03].
702  // Anomie said in 2018-03:
703  /*
704  I suspect that what's breaking is this:
705 
706  The old version of WikiPage::doEditContent() called prepareContentForEdit() which
707  generated the ParserOutput right then, so when doEditUpdates() gets called from the
708  DeferredUpdate scheduled by WikiPage::doCreate() there's no need to parse. I note
709  there's a comment there that says "Get the pre-save transform content and final
710  parser output".
711  The new version of WikiPage::doEditContent() makes a PageUpdater and calls its
712  saveRevision(), which calls DerivedPageDataUpdater::prepareContent() and
713  PageUpdater::doCreate() without ever having to actually generate a ParserOutput.
714  Thus, when DerivedPageDataUpdater::doUpdates() is called from the DeferredUpdate
715  scheduled by PageUpdater::doCreate(), it does find that it needs to parse at that point.
716 
717  And the order of operations in that Flow test is presumably:
718 
719  - Create a page with a call to WikiPage::doEditContent(), in a way that somehow avoids
720  processing the DeferredUpdate.
721  - Set up the "no set!" mock cache in Flow\Tests\Api\ApiTestCase::expectCacheInvalidate()
722  - Then, during the course of doing that test, a $db->commit() results in the
723  DeferredUpdates being run.
724  */
725  $this->derivedDataUpdater->getCanonicalParserOutput();
726 
727  $mainContent = $this->derivedDataUpdater->getSlots()->getContent( SlotRecord::MAIN );
728 
729  // Trigger pre-save hook (using provided edit summary)
730  $hookStatus = Status::newGood( [] );
731  // TODO: replace legacy hook!
732  // TODO: avoid pass-by-reference, see T193950
733  $hook_args = [ &$wikiPage, &$user, &$mainContent, &$summary,
734  $flags & EDIT_MINOR, null, null, &$flags, &$hookStatus ];
735  // Check if the hook rejected the attempted save
736  if ( !Hooks::run( 'PageContentSave', $hook_args ) ) {
737  if ( $hookStatus->isOK() ) {
738  // Hook returned false but didn't call fatal(); use generic message
739  $hookStatus->fatal( 'edit-hook-aborted' );
740  }
741 
742  $this->status = $hookStatus;
743  return null;
744  }
745 
746  // Provide autosummaries if one is not provided and autosummaries are enabled
747  // XXX: $summary == null seems logical, but the empty string may actually come from the user
748  // XXX: Move this logic out of the storage layer! It does not belong here! Use a callback?
749  if ( $summary->text === '' && $summary->data === null ) {
750  $summary = $this->makeAutoSummary( $flags );
751  }
752 
753  // Actually create the revision and create/update the page.
754  // Do NOT yet set $this->status!
755  if ( $flags & EDIT_UPDATE ) {
756  $status = $this->doModify( $summary, $this->user, $flags );
757  } else {
758  $status = $this->doCreate( $summary, $this->user, $flags );
759  }
760 
761  // Promote user to any groups they meet the criteria for
762  DeferredUpdates::addCallableUpdate( function () use ( $user ) {
763  $user->addAutopromoteOnceGroups( 'onEdit' );
764  $user->addAutopromoteOnceGroups( 'onView' ); // b/c
765  } );
766 
767  // NOTE: set $this->status only after all hooks have been called,
768  // so wasCommitted doesn't return true wehn called indirectly from a hook handler!
769  $this->status = $status;
770 
771  // TODO: replace bad status with Exceptions!
772  return ( $this->status && $this->status->isOK() )
773  ? $this->status->value['revision-record']
774  : null;
775  }
776 
782  public function wasCommitted() {
783  return $this->status !== null;
784  }
785 
809  public function getStatus() {
810  return $this->status;
811  }
812 
818  public function wasSuccessful() {
819  return $this->status && $this->status->isOK();
820  }
821 
827  public function isNew() {
828  return $this->status && $this->status->isOK() && $this->status->value['new'];
829  }
830 
838  public function isUnchanged() {
839  return $this->status
840  && $this->status->isOK()
841  && $this->status->value['revision-record'] === null;
842  }
843 
850  public function getNewRevision() {
851  return ( $this->status && $this->status->isOK() )
852  ? $this->status->value['revision-record']
853  : null;
854  }
855 
871  private function makeNewRevision(
872  CommentStoreComment $comment,
873  User $user,
874  $flags,
876  ) {
877  $wikiPage = $this->getWikiPage();
878  $title = $this->getTitle();
879  $parent = $this->grabParentRevision();
880 
881  // XXX: we expect to get a MutableRevisionRecord here, but that's a bit brittle!
882  // TODO: introduce something like an UnsavedRevisionFactory service instead!
884  $rev = $this->derivedDataUpdater->getRevision();
885 
886  $rev->setPageId( $title->getArticleID() );
887 
888  if ( $parent ) {
889  $oldid = $parent->getId();
890  $rev->setParentId( $oldid );
891  } else {
892  $oldid = 0;
893  }
894 
895  $rev->setComment( $comment );
896  $rev->setUser( $user );
897  $rev->setMinorEdit( ( $flags & EDIT_MINOR ) > 0 );
898 
899  foreach ( $rev->getSlots()->getSlots() as $slot ) {
900  $content = $slot->getContent();
901 
902  // XXX: We may push this up to the "edit controller" level, see T192777.
903  // XXX: prepareSave() and isValid() could live in SlotRoleHandler
904  // XXX: PrepareSave should not take a WikiPage!
905  $prepStatus = $content->prepareSave( $wikiPage, $flags, $oldid, $user );
906 
907  // TODO: MCR: record which problem arose in which slot.
908  $status->merge( $prepStatus );
909  }
910 
911  $this->checkAllRequiredRoles(
912  $rev->getSlotRoles(),
913  $status
914  );
915 
916  return $rev;
917  }
918 
927  private function doModify( CommentStoreComment $summary, User $user, $flags ) {
928  $wikiPage = $this->getWikiPage(); // TODO: use for legacy hooks only!
929 
930  // Update article, but only if changed.
931  $status = Status::newGood( [ 'new' => false, 'revision' => null, 'revision-record' => null ] );
932 
933  $oldRev = $this->grabParentRevision();
934  $oldid = $oldRev ? $oldRev->getId() : 0;
935 
936  if ( !$oldRev ) {
937  // Article gone missing
938  $status->fatal( 'edit-gone-missing' );
939 
940  return $status;
941  }
942 
943  $newRevisionRecord = $this->makeNewRevision(
944  $summary,
945  $user,
946  $flags,
947  $status
948  );
949 
950  if ( !$status->isOK() ) {
951  return $status;
952  }
953 
954  $now = $newRevisionRecord->getTimestamp();
955 
956  // XXX: we may want a flag that allows a null revision to be forced!
957  $changed = $this->derivedDataUpdater->isChange();
958 
959  $dbw = $this->getDBConnectionRef( DB_MASTER );
960 
961  if ( $changed ) {
962  $dbw->startAtomic( __METHOD__ );
963 
964  // Get the latest page_latest value while locking it.
965  // Do a CAS style check to see if it's the same as when this method
966  // started. If it changed then bail out before touching the DB.
967  $latestNow = $wikiPage->lockAndGetLatest(); // TODO: move to storage service, pass DB
968  if ( $latestNow != $oldid ) {
969  // We don't need to roll back, since we did not modify the database yet.
970  // XXX: Or do we want to rollback, any transaction started by calling
971  // code will fail? If we want that, we should probably throw an exception.
972  $dbw->endAtomic( __METHOD__ );
973  // Page updated or deleted in the mean time
974  $status->fatal( 'edit-conflict' );
975 
976  return $status;
977  }
978 
979  // At this point we are now comitted to returning an OK
980  // status unless some DB query error or other exception comes up.
981  // This way callers don't have to call rollback() if $status is bad
982  // unless they actually try to catch exceptions (which is rare).
983 
984  // Save revision content and meta-data
985  $newRevisionRecord = $this->revisionStore->insertRevisionOn( $newRevisionRecord, $dbw );
986  $newLegacyRevision = new Revision( $newRevisionRecord );
987 
988  // Update page_latest and friends to reflect the new revision
989  // TODO: move to storage service
990  $wasRedirect = $this->derivedDataUpdater->wasRedirect();
991  if ( !$wikiPage->updateRevisionOn( $dbw, $newLegacyRevision, null, $wasRedirect ) ) {
992  throw new PageUpdateException( "Failed to update page row to use new revision." );
993  }
994 
995  // TODO: replace legacy hook!
996  $tags = $this->computeEffectiveTags( $flags );
997  Hooks::run(
998  'NewRevisionFromEditComplete',
999  [ $wikiPage, $newLegacyRevision, $this->getOriginalRevisionId(), $user, &$tags ]
1000  );
1001 
1002  // Update recentchanges
1003  if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
1004  // Add RC row to the DB
1006  $now,
1007  $this->getTitle(),
1008  $newRevisionRecord->isMinor(),
1009  $user,
1010  $summary->text, // TODO: pass object when that becomes possible
1011  $oldid,
1012  $newRevisionRecord->getTimestamp(),
1013  ( $flags & EDIT_FORCE_BOT ) > 0,
1014  '',
1015  $oldRev->getSize(),
1016  $newRevisionRecord->getSize(),
1017  $newRevisionRecord->getId(),
1019  $tags
1020  );
1021  }
1022 
1023  $user->incEditCount();
1024 
1025  $dbw->endAtomic( __METHOD__ );
1026 
1027  // Return the new revision to the caller
1028  $status->value['revision-record'] = $newRevisionRecord;
1029 
1030  // TODO: globally replace usages of 'revision' with getNewRevision()
1031  $status->value['revision'] = $newLegacyRevision;
1032  } else {
1033  // T34948: revision ID must be set to page {{REVISIONID}} and
1034  // related variables correctly. Likewise for {{REVISIONUSER}} (T135261).
1035  // Since we don't insert a new revision into the database, the least
1036  // error-prone way is to reuse given old revision.
1037  $newRevisionRecord = $oldRev;
1038 
1039  $status->warning( 'edit-no-change' );
1040  // Update page_touched as updateRevisionOn() was not called.
1041  // Other cache updates are managed in WikiPage::onArticleEdit()
1042  // via WikiPage::doEditUpdates().
1043  $this->getTitle()->invalidateCache( $now );
1044  }
1045 
1046  // Do secondary updates once the main changes have been committed...
1047  // NOTE: the updates have to be processed before sending the response to the client
1048  // (DeferredUpdates::PRESEND), otherwise the client may already be following the
1049  // HTTP redirect to the standard view before dervide data has been created - most
1050  // importantly, before the parser cache has been updated. This would cause the
1051  // content to be parsed a second time, or may cause stale content to be shown.
1053  $this->getAtomicSectionUpdate(
1054  $dbw,
1055  $wikiPage,
1056  $newRevisionRecord,
1057  $user,
1058  $summary,
1059  $flags,
1060  $status,
1061  [ 'changed' => $changed, ]
1062  ),
1064  );
1065 
1066  return $status;
1067  }
1068 
1078  private function doCreate( CommentStoreComment $summary, User $user, $flags ) {
1079  $wikiPage = $this->getWikiPage(); // TODO: use for legacy hooks only!
1080 
1081  if ( !$this->derivedDataUpdater->getSlots()->hasSlot( SlotRecord::MAIN ) ) {
1082  throw new PageUpdateException( 'Must provide a main slot when creating a page!' );
1083  }
1084 
1085  $status = Status::newGood( [ 'new' => true, 'revision' => null, 'revision-record' => null ] );
1086 
1087  $newRevisionRecord = $this->makeNewRevision(
1088  $summary,
1089  $user,
1090  $flags,
1091  $status
1092  );
1093 
1094  if ( !$status->isOK() ) {
1095  return $status;
1096  }
1097 
1098  $now = $newRevisionRecord->getTimestamp();
1099 
1100  $dbw = $this->getDBConnectionRef( DB_MASTER );
1101  $dbw->startAtomic( __METHOD__ );
1102 
1103  // Add the page record unless one already exists for the title
1104  // TODO: move to storage service
1105  $newid = $wikiPage->insertOn( $dbw );
1106  if ( $newid === false ) {
1107  $dbw->endAtomic( __METHOD__ );
1108  $status->fatal( 'edit-already-exists' );
1109 
1110  return $status;
1111  }
1112 
1113  // At this point we are now comitted to returning an OK
1114  // status unless some DB query error or other exception comes up.
1115  // This way callers don't have to call rollback() if $status is bad
1116  // unless they actually try to catch exceptions (which is rare).
1117  $newRevisionRecord->setPageId( $newid );
1118 
1119  // Save the revision text...
1120  $newRevisionRecord = $this->revisionStore->insertRevisionOn( $newRevisionRecord, $dbw );
1121  $newLegacyRevision = new Revision( $newRevisionRecord );
1122 
1123  // Update the page record with revision data
1124  // TODO: move to storage service
1125  if ( !$wikiPage->updateRevisionOn( $dbw, $newLegacyRevision, 0 ) ) {
1126  throw new PageUpdateException( "Failed to update page row to use new revision." );
1127  }
1128 
1129  // TODO: replace legacy hook!
1130  $tags = $this->computeEffectiveTags( $flags );
1131  Hooks::run(
1132  'NewRevisionFromEditComplete',
1133  [ $wikiPage, $newLegacyRevision, false, $user, &$tags ]
1134  );
1135 
1136  // Update recentchanges
1137  if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
1138  // Add RC row to the DB
1140  $now,
1141  $this->getTitle(),
1142  $newRevisionRecord->isMinor(),
1143  $user,
1144  $summary->text, // TODO: pass object when that becomes possible
1145  ( $flags & EDIT_FORCE_BOT ) > 0,
1146  '',
1147  $newRevisionRecord->getSize(),
1148  $newRevisionRecord->getId(),
1150  $tags
1151  );
1152  }
1153 
1154  $user->incEditCount();
1155 
1156  if ( $this->usePageCreationLog ) {
1157  // Log the page creation
1158  // @TODO: Do we want a 'recreate' action?
1159  $logEntry = new ManualLogEntry( 'create', 'create' );
1160  $logEntry->setPerformer( $user );
1161  $logEntry->setTarget( $this->getTitle() );
1162  $logEntry->setComment( $summary->text );
1163  $logEntry->setTimestamp( $now );
1164  $logEntry->setAssociatedRevId( $newRevisionRecord->getId() );
1165  $logEntry->insert();
1166  // Note that we don't publish page creation events to recentchanges
1167  // (i.e. $logEntry->publish()) since this would create duplicate entries,
1168  // one for the edit and one for the page creation.
1169  }
1170 
1171  $dbw->endAtomic( __METHOD__ );
1172 
1173  // Return the new revision to the caller
1174  // TODO: globally replace usages of 'revision' with getNewRevision()
1175  $status->value['revision'] = $newLegacyRevision;
1176  $status->value['revision-record'] = $newRevisionRecord;
1177 
1178  // Do secondary updates once the main changes have been committed...
1180  $this->getAtomicSectionUpdate(
1181  $dbw,
1182  $wikiPage,
1183  $newRevisionRecord,
1184  $user,
1185  $summary,
1186  $flags,
1187  $status,
1188  [ 'created' => true ]
1189  ),
1191  );
1192 
1193  return $status;
1194  }
1195 
1196  private function getAtomicSectionUpdate(
1197  IDatabase $dbw,
1199  RevisionRecord $newRevisionRecord,
1200  User $user,
1201  CommentStoreComment $summary,
1202  $flags,
1203  Status $status,
1204  $hints = []
1205  ) {
1206  return new AtomicSectionUpdate(
1207  $dbw,
1208  __METHOD__,
1209  function () use (
1210  $wikiPage, $newRevisionRecord, $user,
1211  $summary, $flags, $status, $hints
1212  ) {
1213  // set debug data
1214  $hints['causeAction'] = 'edit-page';
1215  $hints['causeAgent'] = $user->getName();
1216 
1217  $newLegacyRevision = new Revision( $newRevisionRecord );
1218  $mainContent = $newRevisionRecord->getContent( SlotRecord::MAIN, RevisionRecord::RAW );
1219 
1220  // Update links tables, site stats, etc.
1221  $this->derivedDataUpdater->prepareUpdate( $newRevisionRecord, $hints );
1222  $this->derivedDataUpdater->doUpdates();
1223 
1224  // TODO: replace legacy hook!
1225  // TODO: avoid pass-by-reference, see T193950
1226 
1227  if ( $hints['created'] ?? false ) {
1228  // Trigger post-create hook
1229  $params = [ &$wikiPage, &$user, $mainContent, $summary->text,
1230  $flags & EDIT_MINOR, null, null, &$flags, $newLegacyRevision ];
1231  Hooks::run( 'PageContentInsertComplete', $params );
1232  }
1233 
1234  // Trigger post-save hook
1235  $params = [ &$wikiPage, &$user, $mainContent, $summary->text,
1236  $flags & EDIT_MINOR, null, null, &$flags, $newLegacyRevision,
1238  Hooks::run( 'PageContentSaveComplete', $params );
1239  }
1240  );
1241  }
1242 
1246  private function getRequiredSlotRoles() {
1247  return $this->slotRoleRegistry->getRequiredRoles( $this->getTitle() );
1248  }
1249 
1253  private function getAllowedSlotRoles() {
1254  return $this->slotRoleRegistry->getAllowedRoles( $this->getTitle() );
1255  }
1256 
1257  private function ensureRoleAllowed( $role ) {
1258  $allowedRoles = $this->getAllowedSlotRoles();
1259  if ( !in_array( $role, $allowedRoles ) ) {
1260  throw new PageUpdateException( "Slot role `$role` is not allowed." );
1261  }
1262  }
1263 
1264  private function ensureRoleNotRequired( $role ) {
1265  $requiredRoles = $this->getRequiredSlotRoles();
1266  if ( in_array( $role, $requiredRoles ) ) {
1267  throw new PageUpdateException( "Slot role `$role` is required." );
1268  }
1269  }
1270 
1271  private function checkAllRolesAllowed( array $roles, Status $status ) {
1272  $allowedRoles = $this->getAllowedSlotRoles();
1273 
1274  $forbidden = array_diff( $roles, $allowedRoles );
1275  if ( !empty( $forbidden ) ) {
1276  $status->error(
1277  'edit-slots-cannot-add',
1278  count( $forbidden ),
1279  implode( ', ', $forbidden )
1280  );
1281  }
1282  }
1283 
1284  private function checkNoRolesRequired( array $roles, Status $status ) {
1285  $requiredRoles = $this->getRequiredSlotRoles();
1286 
1287  $needed = array_diff( $roles, $requiredRoles );
1288  if ( !empty( $needed ) ) {
1289  $status->error(
1290  'edit-slots-cannot-remove',
1291  count( $needed ),
1292  implode( ', ', $needed )
1293  );
1294  }
1295  }
1296 
1297  private function checkAllRequiredRoles( array $roles, Status $status ) {
1298  $requiredRoles = $this->getRequiredSlotRoles();
1299 
1300  $missing = array_diff( $requiredRoles, $roles );
1301  if ( !empty( $missing ) ) {
1302  $status->error(
1303  'edit-slots-missing',
1304  count( $missing ),
1305  implode( ', ', $missing )
1306  );
1307  }
1308  }
1309 
1310 }
DerivedPageDataUpdater $derivedDataUpdater
Definition: PageUpdater.php:87
RevisionSlotsUpdate $slotsUpdate
static notifyEdit( $timestamp, $title, $minor, $user, $comment, $oldId, $lastTimestamp, $bot, $ip='', $oldSize=0, $newSize=0, $newId=0, $patrol=0, $tags=[])
Makes an entry in the database corresponding to an edit.
int $rcPatrolStatus
the RC patrol status the new revision should be marked with.
getOriginalRevisionId()
Returns the ID of an earlier revision that is being repeated or restored by this update.
SlotRoleRegistry $slotRoleRegistry
isNew()
Whether saveRevision() was called and created a new page.
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
wasSuccessful()
Whether saveRevision() completed successfully.
const EDIT_INTERNAL
Definition: Defines.php:139
insertOn( $dbw, $pageId=null)
Insert a new empty page record for this article.
Definition: WikiPage.php:1335
getNewRevision()
The new revision created by saveRevision(), or null if saveRevision() has not yet been called...
A registry service for SlotRoleHandlers, used to define which slot roles are available on which page...
saveRevision(CommentStoreComment $summary, $flags=0)
Change an existing article or create a new article.
checkFlags( $flags)
Check flags and add EDIT_NEW or EDIT_UPDATE to them as needed.
doModify(CommentStoreComment $summary, User $user, $flags)
setSlot(SlotRecord $slot)
Set the new slot for the given slot role.
Value object representing a modification of revision slots.
const EDIT_MINOR
Definition: Defines.php:134
setContent( $role, Content $content)
Set the new content for the given slot role.
Value object representing a content slot associated with a page revision.
Definition: SlotRecord.php:39
const EDIT_UPDATE
Definition: Defines.php:133
checkNoRolesRequired(array $roles, Status $status)
removeSlot( $role)
Removes the slot with the given role.
static getLocalizedName( $name, Language $lang=null)
Returns the localized name for a given content model.
addTags(array $tags)
Sets tags to apply to this update.
static newUnsavedComment( $comment, array $data=null)
Create a new, unsaved CommentStoreComment.
boolean $ajaxEditStash
see $wgAjaxEditStash
const DB_MASTER
Definition: defines.php:26
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...
error( $message)
Add an error, do not set fatal flag This can be used for non-fatal errors.
boolean $useAutomaticEditSummaries
see $wgUseAutomaticEditSummaries
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2251
Exception representing a failure to update a page entry.
checkAllRolesAllowed(array $roles, Status $status)
addTag( $tag)
Sets a tag to apply to this update.
__construct(User $user, WikiPage $wikiPage, DerivedPageDataUpdater $derivedDataUpdater, ILoadBalancer $loadBalancer, RevisionStore $revisionStore, SlotRoleRegistry $slotRoleRegistry)
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:51
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable modifiable after all normalizations have been except for the $wgMaxImageArea check set to true or false to override the $wgMaxImageArea check result gives extension the possibility to transform it themselves $handler
Definition: hooks.txt:773
static newFatal( $message)
Factory function for fatal errors.
Definition: StatusValue.php:68
Status::newGood()` to allow deletion, and then `return false` from the hook function. Ensure you consume the 'ChangeTagAfterDelete' hook to carry out custom deletion actions. $tag:name of the tag $user:user initiating the action & $status:Status object. See above. 'ChangeTagsListActive':Allows you to nominate which of the tags your extension uses are in active use. & $tags:list of all active tags. Append to this array. 'ChangeTagsAfterUpdateTags':Called after tags have been updated with the ChangeTags::updateTags function. Params:$addedTags:tags effectively added in the update $removedTags:tags effectively removed in the update $prevTags:tags that were present prior to the update $rc_id:recentchanges table id $rev_id:revision table id $log_id:logging table id $params:tag params $rc:RecentChange being tagged when the tagging accompanies the action, or null $user:User who performed the tagging when the tagging is subsequent to the action, or null 'ChangeTagsAllowedAdd':Called when checking if a user can add tags to a change. & $allowedTags:List of all the tags the user is allowed to add. Any tags the user wants to add( $addTags) that are not in this array will cause it to fail. You may add or remove tags to this array as required. $addTags:List of tags user intends to add. $user:User who is adding the tags. 'ChangeUserGroups':Called before user groups are changed. $performer:The User who will perform the change $user:The User whose groups will be changed & $add:The groups that will be added & $remove:The groups that will be removed 'Collation::factory':Called if $wgCategoryCollation is an unknown collation. $collationName:Name of the collation in question & $collationObject:Null. Replace with a subclass of the Collation class that implements the collation given in $collationName. 'ConfirmEmailComplete':Called after a user 's email has been confirmed successfully. $user:user(object) whose email is being confirmed 'ContentAlterParserOutput':Modify parser output for a given content object. Called by Content::getParserOutput after parsing has finished. Can be used for changes that depend on the result of the parsing but have to be done before LinksUpdate is called(such as adding tracking categories based on the rendered HTML). $content:The Content to render $title:Title of the page, as context $parserOutput:ParserOutput to manipulate 'ContentGetParserOutput':Customize parser output for a given content object, called by AbstractContent::getParserOutput. May be used to override the normal model-specific rendering of page content. $content:The Content to render $title:Title of the page, as context $revId:The revision ID, as context $options:ParserOptions for rendering. To avoid confusing the parser cache, the output can only depend on parameters provided to this hook function, not on global state. $generateHtml:boolean, indicating whether full HTML should be generated. If false, generation of HTML may be skipped, but other information should still be present in the ParserOutput object. & $output:ParserOutput, to manipulate or replace 'ContentHandlerDefaultModelFor':Called when the default content model is determined for a given title. May be used to assign a different model for that title. $title:the Title in question & $model:the model name. Use with CONTENT_MODEL_XXX constants. 'ContentHandlerForModelID':Called when a ContentHandler is requested for a given content model name, but no entry for that model exists in $wgContentHandlers. Note:if your extension implements additional models via this hook, please use GetContentModels hook to make them known to core. $modeName:the requested content model name & $handler:set this to a ContentHandler object, if desired. 'ContentModelCanBeUsedOn':Called to determine whether that content model can be used on a given page. This is especially useful to prevent some content models to be used in some special location. $contentModel:ID of the content model in question $title:the Title in question. & $ok:Output parameter, whether it is OK to use $contentModel on $title. Handler functions that modify $ok should generally return false to prevent further hooks from further modifying $ok. 'ContribsPager::getQueryInfo':Before the contributions query is about to run & $pager:Pager object for contributions & $queryInfo:The query for the contribs Pager 'ContribsPager::reallyDoQuery':Called before really executing the query for My Contributions & $data:an array of results of all contribs queries $pager:The ContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'ContributionsLineEnding':Called before a contributions HTML line is finished $page:SpecialPage object for contributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'ContributionsToolLinks':Change tool links above Special:Contributions $id:User identifier $title:User page title & $tools:Array of tool links $specialPage:SpecialPage instance for context and services. Can be either SpecialContributions or DeletedContributionsPage. Extensions should type hint against a generic SpecialPage though. 'ConvertContent':Called by AbstractContent::convert when a conversion to another content model is requested. Handler functions that modify $result should generally return false to disable further attempts at conversion. $content:The Content object to be converted. $toModel:The ID of the content model to convert to. $lossy:boolean indicating whether lossy conversion is allowed. & $result:Output parameter, in case the handler function wants to provide a converted Content object. Note that $result->getContentModel() must return $toModel. 'ContentSecurityPolicyDefaultSource':Modify the allowed CSP load sources. This affects all directives except for the script directive. If you want to add a script source, see ContentSecurityPolicyScriptSource hook. & $defaultSrc:Array of Content-Security-Policy allowed sources $policyConfig:Current configuration for the Content-Security-Policy header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyDirectives':Modify the content security policy directives. Use this only if ContentSecurityPolicyDefaultSource and ContentSecurityPolicyScriptSource do not meet your needs. & $directives:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'ContentSecurityPolicyScriptSource':Modify the allowed CSP script sources. Note that you also have to use ContentSecurityPolicyDefaultSource if you want non-script sources to be loaded from whatever you add. & $scriptSrc:Array of CSP directives $policyConfig:Current configuration for the CSP header $mode:ContentSecurityPolicy::REPORT_ONLY_MODE or ContentSecurityPolicy::FULL_MODE depending on type of header 'CustomEditor':When invoking the page editor Return true to allow the normal editor to be used, or false if implementing a custom editor, e.g. for a special namespace, etc. $article:Article being edited $user:User performing the edit 'DeletedContribsPager::reallyDoQuery':Called before really executing the query for Special:DeletedContributions Similar to ContribsPager::reallyDoQuery & $data:an array of results of all contribs queries $pager:The DeletedContribsPager object hooked into $offset:Index offset, inclusive $limit:Exact query limit $descending:Query direction, false for ascending, true for descending 'DeletedContributionsLineEnding':Called before a DeletedContributions HTML line is finished. Similar to ContributionsLineEnding $page:SpecialPage object for DeletedContributions & $ret:the HTML line $row:the DB row for this line & $classes:the classes to add to the surrounding< li > & $attribs:associative array of other HTML attributes for the< li > element. Currently only data attributes reserved to MediaWiki are allowed(see Sanitizer::isReservedDataAttribute). 'DeleteUnknownPreferences':Called by the cleanupPreferences.php maintenance script to build a WHERE clause with which to delete preferences that are not known about. This hook is used by extensions that have dynamically-named preferences that should not be deleted in the usual cleanup process. For example, the Gadgets extension creates preferences prefixed with 'gadget-', and so anything with that prefix is excluded from the deletion. &where:An array that will be passed as the $cond parameter to IDatabase::select() to determine what will be deleted from the user_properties table. $db:The IDatabase object, useful for accessing $db->buildLike() etc. 'DifferenceEngineAfterLoadNewText':called in DifferenceEngine::loadNewText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before returning true from this function. $differenceEngine:DifferenceEngine object 'DifferenceEngineLoadTextAfterNewContentIsLoaded':called in DifferenceEngine::loadText() after the new revision 's content has been loaded into the class member variable $differenceEngine->mNewContent but before checking if the variable 's value is null. This hook can be used to inject content into said class member variable. $differenceEngine:DifferenceEngine object 'DifferenceEngineMarkPatrolledLink':Allows extensions to change the "mark as patrolled" link which is shown both on the diff header as well as on the bottom of a page, usually wrapped in a span element which has class="patrollink". $differenceEngine:DifferenceEngine object & $markAsPatrolledLink:The "mark as patrolled" link HTML(string) $rcid:Recent change ID(rc_id) for this change(int) 'DifferenceEngineMarkPatrolledRCID':Allows extensions to possibly change the rcid parameter. For example the rcid might be set to zero due to the user being the same as the performer of the change but an extension might still want to show it under certain conditions. & $rcid:rc_id(int) of the change or 0 $differenceEngine:DifferenceEngine object $change:RecentChange object $user:User object representing the current user 'DifferenceEngineNewHeader':Allows extensions to change the $newHeader variable, which contains information about the new revision, such as the revision 's author, whether the revision was marked as a minor edit or not, etc. $differenceEngine:DifferenceEngine object & $newHeader:The string containing the various #mw-diff-otitle[1-5] divs, which include things like revision author info, revision comment, RevisionDelete link and more $formattedRevisionTools:Array containing revision tools, some of which may have been injected with the DiffRevisionTools hook $nextlink:String containing the link to the next revision(if any) $status
Definition: hooks.txt:1250
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:103
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add a callable update.
inheritSlot(SlotRecord $originalSlot)
Explicitly inherit a slot from some earlier revision.
setOriginalRevisionId( $originalRevId)
Sets the ID of an earlier revision that is being repeated or restored by this update.
either a unescaped string or a HtmlArmor object after in associative array form externallinks including delete and has completed for all link tables whether this was an auto creation use $formDescriptor instead default is conds Array Extra conditions for the No matching items in log is displayed if loglist is empty msgKey Array If you want a nice box with a set this to the key of the message First element is the message additional optional elements are parameters for the key that are processed with wfMessage() -> params() ->parseAsBlock() - offset Set to overwrite offset parameter in $wgRequest set to '' to unset offset - wrap String Wrap the message in html(usually something like "&lt
static getForModelID( $modelId)
Returns the ContentHandler singleton for the given model ID.
const EDIT_FORCE_BOT
Definition: Defines.php:136
Service for looking up page revisions.
static newGood( $value=null)
Factory function for good results.
Definition: StatusValue.php:81
doCreate(CommentStoreComment $summary, User $user, $flags)
getContent( $role, $audience=self::FOR_PUBLIC, User $user=null)
Returns the Content of the given slot of this revision.
incEditCount()
Schedule a deferred update to update the user&#39;s edit count.
Definition: User.php:5016
const EDIT_AUTOSUMMARY
Definition: Defines.php:138
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such and we might be restricted by PHP settings such as safe mode or open_basedir We cannot assume that the software even has read access anywhere useful Many shared hosts run all users web applications under the same user
Wikitext formatted, in the key only.
Definition: distributors.txt:9
$params
const EDIT_SUPPRESS_RC
Definition: Defines.php:135
static getSoftwareTags( $all=false)
Loads defined core tags, checks for invalid types (if not array), and filters for supported and enabl...
Definition: ChangeTags.php:58
wasCommitted()
Whether saveRevision() has been called on this instance.
getStatus()
The Status object indicating whether saveRevision() was successful, or null if saveRevision() was not...
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
Definition: hooks.txt:773
setRcPatrolStatus( $status)
Sets the "patrolled" status of the edit.
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:918
presenting them properly to the user as errors is done by the caller return true use this to change the list i e etc $rev
Definition: hooks.txt:1759
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
Definition: distributors.txt:9
grabParentRevision()
Returns the revision that was the page&#39;s current revision when grabParentRevision() was first called...
merge( $other, $overwriteValue=false)
Merge another status object into this one.
const PRC_UNPATROLLED
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition: injection.txt:35
getRole()
Returns the role of the slot.
Definition: SlotRecord.php:489
A handle for managing updates for derived page data on edit, import, purge, etc.
checkAllRequiredRoles(array $roles, Status $status)
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the deferred list to be run later by execute()
Database cluster connection, tracking, load balancing, and transaction manager interface.
Basic database interface for live and lazy-loaded relation database handles.
Definition: IDatabase.php:38
$parent
Definition: pageupdater.txt:71
getAtomicSectionUpdate(IDatabase $dbw, WikiPage $wikiPage, RevisionRecord $newRevisionRecord, User $user, CommentStoreComment $summary, $flags, Status $status, $hints=[])
const EDIT_NEW
Definition: Defines.php:132
Controller-like object for creating and updating pages by creating new revisions. ...
Definition: PageUpdater.php:72
Exception representing a failure to look up a revision.
isUnchanged()
Whether saveRevision() did not create a revision because the content didn&#39;t change (null-edit)...
setUseAutomaticEditSummaries( $useAutomaticEditSummaries)
Can be used to enable or disable automatic summaries that are applied to certain kinds of changes...
updateRevisionOn( $dbw, $revision, $lastRevision=null, $lastRevIsRedirect=null)
Update the page record to point to a newly saved revision.
Definition: WikiPage.php:1380
setAjaxEditStash( $ajaxEditStash)
Page revision base class.
lockAndGetLatest()
Lock the page row for this title+id and return page_latest (or 0)
Definition: WikiPage.php:2986
makeNewRevision(CommentStoreComment $comment, User $user, $flags, Status $status)
Constructs a MutableRevisionRecord based on the Content prepared by the DerivedPageDataUpdater.
getExplicitTags()
Returns the list of tags set using the addTag() method.
$content
Definition: pageupdater.txt:72
setUndidRevisionId( $undidRevId)
Sets the ID of revision that was undone by the present update.
bool $usePageCreationLog
whether to create a log entry for new page creations.
hasEditConflict( $expectedParentRevision)
Checks whether this update conflicts with another update performed between the client loading data to...
setUsePageCreationLog( $use)
Whether to create a log entry for new page creations.
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
getParentContent( $role)
Returns the content of the given slot of the parent revision, with no audience checks applied...
getUndidRevisionId()
Returns the revision ID set by setUndidRevisionId(), indicating what revision is being undone by this...