MediaWiki  master
DerivedPageDataUpdater.php
Go to the documentation of this file.
1 <?php
23 namespace MediaWiki\Storage;
24 
26 use Content;
31 use Hooks;
59 use Title;
60 use User;
64 
97 
101  private $user = null;
102 
106  private $wikiPage;
107 
111  private $parserCache;
112 
116  private $revisionStore;
117 
121  private $contLang;
122 
126  private $jobQueueGroup;
127 
131  private $messageCache;
132 
137 
142 
146  private $rcWatchCategoryMembership = false;
147 
152  private $options = [
153  'changed' => true,
154  // newrev is true if prepareUpdate is handling the creation of a new revision,
155  // as opposed to a null edit or a forced update.
156  'newrev' => false,
157  'created' => false,
158  'moved' => false,
159  'restored' => false,
160  'oldrevision' => null,
161  'oldcountable' => null,
162  'oldredirect' => null,
163  'triggeringUser' => null,
164  // causeAction/causeAgent default to 'unknown' but that's handled where it's read,
165  // to make the life of prepareUpdate() callers easier.
166  'causeAction' => null,
167  'causeAgent' => null,
168  ];
169 
189  private $pageState = null;
190 
194  private $slotsUpdate = null;
195 
200 
204  private $revision = null;
205 
210 
215 
218 
227  private $stage = 'new';
228 
239  private static $transitions = [
240  'new' => [
241  'new' => true,
242  'knows-current' => true,
243  'has-content' => true,
244  'has-revision' => true,
245  ],
246  'knows-current' => [
247  'knows-current' => true,
248  'has-content' => true,
249  'has-revision' => true,
250  ],
251  'has-content' => [
252  'has-content' => true,
253  'has-revision' => true,
254  ],
255  'has-revision' => [
256  'has-revision' => true,
257  'done' => true,
258  ],
259  ];
260 
272  public function __construct(
282  ) {
283  $this->wikiPage = $wikiPage;
284 
285  $this->parserCache = $parserCache;
286  $this->revisionStore = $revisionStore;
287  $this->revisionRenderer = $revisionRenderer;
288  $this->slotRoleRegistry = $slotRoleRegistry;
289  $this->jobQueueGroup = $jobQueueGroup;
290  $this->messageCache = $messageCache;
291  $this->contLang = $contLang;
292  // XXX only needed for waiting for replicas to catch up; there should be a narrower
293  // interface for that.
294  $this->loadbalancerFactory = $loadbalancerFactory;
295  }
296 
308  private function doTransition( $newStage ) {
309  $this->assertTransition( $newStage );
310 
311  $oldStage = $this->stage;
312  $this->stage = $newStage;
313 
314  return $oldStage;
315  }
316 
326  private function assertTransition( $newStage ) {
327  if ( empty( self::$transitions[$this->stage][$newStage] ) ) {
328  throw new LogicException( "Cannot transition from {$this->stage} to $newStage" );
329  }
330  }
331 
335  private function getWikiId() {
336  // TODO: get from RevisionStore
337  return false;
338  }
339 
351  public function isReusableFor(
355  $parentId = null
356  ) {
357  if ( $revision
358  && $parentId
359  && $revision->getParentId() !== $parentId
360  ) {
361  throw new InvalidArgumentException( '$parentId should match the parent of $revision' );
362  }
363 
364  // NOTE: For null revisions, $user may be different from $this->revision->getUser
365  // and also from $revision->getUser.
366  // But $user should always match $this->user.
367  if ( $user && $this->user && $user->getName() !== $this->user->getName() ) {
368  return false;
369  }
370 
371  if ( $revision && $this->revision && $this->revision->getId()
372  && $this->revision->getId() !== $revision->getId()
373  ) {
374  return false;
375  }
376 
377  if ( $this->pageState
378  && $revision
379  && $revision->getParentId() !== null
380  && $this->pageState['oldId'] !== $revision->getParentId()
381  ) {
382  return false;
383  }
384 
385  if ( $this->pageState
386  && $parentId !== null
387  && $this->pageState['oldId'] !== $parentId
388  ) {
389  return false;
390  }
391 
392  // NOTE: this check is the primary reason for having the $this->slotsUpdate field!
393  if ( $this->slotsUpdate
394  && $slotsUpdate
395  && !$this->slotsUpdate->hasSameUpdates( $slotsUpdate )
396  ) {
397  return false;
398  }
399 
400  if ( $revision
401  && $this->revision
402  && !$this->revision->getSlots()->hasSameContent( $revision->getSlots() )
403  ) {
404  return false;
405  }
406 
407  return true;
408  }
409 
415  $this->articleCountMethod = $articleCountMethod;
416  }
417 
423  $this->rcWatchCategoryMembership = $rcWatchCategoryMembership;
424  }
425 
429  private function getTitle() {
430  // NOTE: eventually, we won't get a WikiPage passed into the constructor any more
431  return $this->wikiPage->getTitle();
432  }
433 
437  private function getWikiPage() {
438  // NOTE: eventually, we won't get a WikiPage passed into the constructor any more
439  return $this->wikiPage;
440  }
441 
449  public function pageExisted() {
450  $this->assertHasPageState( __METHOD__ );
451 
452  return $this->pageState['oldId'] > 0;
453  }
454 
464  private function getParentRevision() {
465  $this->assertPrepared( __METHOD__ );
466 
467  if ( $this->parentRevision ) {
468  return $this->parentRevision;
469  }
470 
471  if ( !$this->pageState['oldId'] ) {
472  // If there was no current revision, there is no parent revision,
473  // since the page didn't exist.
474  return null;
475  }
476 
477  $oldId = $this->revision->getParentId();
478  $flags = $this->useMaster() ? RevisionStore::READ_LATEST : 0;
479  $this->parentRevision = $oldId
480  ? $this->revisionStore->getRevisionById( $oldId, $flags )
481  : null;
482 
483  return $this->parentRevision;
484  }
485 
506  public function grabCurrentRevision() {
507  if ( $this->pageState ) {
508  return $this->pageState['oldRevision'];
509  }
510 
511  $this->assertTransition( 'knows-current' );
512 
513  // NOTE: eventually, we won't get a WikiPage passed into the constructor any more
514  $wikiPage = $this->getWikiPage();
515 
516  // Do not call WikiPage::clear(), since the caller may already have caused page data
517  // to be loaded with SELECT FOR UPDATE. Just assert it's loaded now.
518  $wikiPage->loadPageData( self::READ_LATEST );
520  $current = $rev ? $rev->getRevisionRecord() : null;
521 
522  $this->pageState = [
523  'oldRevision' => $current,
524  'oldId' => $rev ? $rev->getId() : 0,
525  'oldIsRedirect' => $wikiPage->isRedirect(), // NOTE: uses page table
526  'oldCountable' => $wikiPage->isCountable(), // NOTE: uses pagelinks table
527  ];
528 
529  $this->doTransition( 'knows-current' );
530 
531  return $this->pageState['oldRevision'];
532  }
533 
539  public function isContentPrepared() {
540  return $this->revision !== null;
541  }
542 
550  public function isUpdatePrepared() {
551  return $this->revision !== null && $this->revision->getId() !== null;
552  }
553 
557  private function getPageId() {
558  // NOTE: eventually, we won't get a WikiPage passed into the constructor any more
559  return $this->wikiPage->getId();
560  }
561 
567  public function isContentDeleted() {
568  if ( $this->revision ) {
569  // XXX: if that revision is the current revision, this should be skipped
570  return $this->revision->isDeleted( RevisionRecord::DELETED_TEXT );
571  } else {
572  // If the content has not been saved yet, it cannot have been deleted yet.
573  return false;
574  }
575  }
576 
586  public function getRawSlot( $role ) {
587  return $this->getSlots()->getSlot( $role );
588  }
589 
598  public function getRawContent( $role ) {
599  return $this->getRawSlot( $role )->getContent();
600  }
601 
608  private function getContentModel( $role ) {
609  return $this->getRawSlot( $role )->getModel();
610  }
611 
616  private function getContentHandler( $role ) {
617  // TODO: inject something like a ContentHandlerRegistry
618  return ContentHandler::getForModelID( $this->getContentModel( $role ) );
619  }
620 
621  private function useMaster() {
622  // TODO: can we just set a flag to true in prepareContent()?
623  return $this->wikiPage->wasLoadedFrom( self::READ_LATEST );
624  }
625 
629  public function isCountable() {
630  // NOTE: Keep in sync with WikiPage::isCountable.
631 
632  if ( !$this->getTitle()->isContentPage() ) {
633  return false;
634  }
635 
636  if ( $this->isContentDeleted() ) {
637  // This should be irrelevant: countability only applies to the current revision,
638  // and the current revision is never suppressed.
639  return false;
640  }
641 
642  if ( $this->isRedirect() ) {
643  return false;
644  }
645 
646  $hasLinks = null;
647 
648  if ( $this->articleCountMethod === 'link' ) {
649  // NOTE: it would be more appropriate to determine for each slot separately
650  // whether it has links, and use that information with that slot's
651  // isCountable() method. However, that would break parity with
652  // WikiPage::isCountable, which uses the pagelinks table to determine
653  // whether the current revision has links.
654  $hasLinks = (bool)count( $this->getCanonicalParserOutput()->getLinks() );
655  }
656 
657  foreach ( $this->getModifiedSlotRoles() as $role ) {
658  $roleHandler = $this->slotRoleRegistry->getRoleHandler( $role );
659  if ( $roleHandler->supportsArticleCount() ) {
660  $content = $this->getRawContent( $role );
661 
662  if ( $content->isCountable( $hasLinks ) ) {
663  return true;
664  }
665  }
666  }
667 
668  return false;
669  }
670 
674  public function isRedirect() {
675  // NOTE: main slot determines redirect status
676  // TODO: MCR: this should be controlled by a PageTypeHandler
677  $mainContent = $this->getRawContent( SlotRecord::MAIN );
678 
679  return $mainContent->isRedirect();
680  }
681 
687  private function revisionIsRedirect( RevisionRecord $rev ) {
688  // NOTE: main slot determines redirect status
689  $mainContent = $rev->getContent( SlotRecord::MAIN, RevisionRecord::RAW );
690 
691  return $mainContent->isRedirect();
692  }
693 
718  public function prepareContent(
719  User $user,
721  $useStash = true
722  ) {
723  if ( $this->slotsUpdate ) {
724  if ( !$this->user ) {
725  throw new LogicException(
726  'Unexpected state: $this->slotsUpdate was initialized, '
727  . 'but $this->user was not.'
728  );
729  }
730 
731  if ( $this->user->getName() !== $user->getName() ) {
732  throw new LogicException( 'Can\'t call prepareContent() again for different user! '
733  . 'Expected ' . $this->user->getName() . ', got ' . $user->getName()
734  );
735  }
736 
737  if ( !$this->slotsUpdate->hasSameUpdates( $slotsUpdate ) ) {
738  throw new LogicException(
739  'Can\'t call prepareContent() again with different slot content!'
740  );
741  }
742 
743  return; // prepareContent() already done, nothing to do
744  }
745 
746  $this->assertTransition( 'has-content' );
747 
748  $wikiPage = $this->getWikiPage(); // TODO: use only for legacy hooks!
749  $title = $this->getTitle();
750 
752 
753  // The edit may have already been prepared via api.php?action=stashedit
754  $stashedEdit = false;
755 
756  // TODO: MCR: allow output for all slots to be stashed.
757  if ( $useStash && $slotsUpdate->isModifiedSlot( SlotRecord::MAIN ) ) {
758  $editStash = MediaWikiServices::getInstance()->getPageEditStash();
759  $stashedEdit = $editStash->checkCache(
760  $title,
761  $slotsUpdate->getModifiedSlot( SlotRecord::MAIN )->getContent(),
762  User::newFromIdentity( $user )
763  );
764  }
765 
766  $userPopts = ParserOptions::newFromUserAndLang( $user, $this->contLang );
767  Hooks::run( 'ArticlePrepareTextForEdit', [ $wikiPage, $userPopts ] );
768 
769  $this->user = $user;
770  $this->slotsUpdate = $slotsUpdate;
771 
772  if ( $parentRevision ) {
774  } else {
775  $this->revision = new MutableRevisionRecord( $title );
776  }
777 
778  // NOTE: user and timestamp must be set, so they can be used for
779  // {{subst:REVISIONUSER}} and {{subst:REVISIONTIMESTAMP}} in PST!
780  $this->revision->setTimestamp( wfTimestampNow() );
781  $this->revision->setUser( $user );
782 
783  // Set up ParserOptions to operate on the new revision
784  $oldCallback = $userPopts->getCurrentRevisionCallback();
785  $userPopts->setCurrentRevisionCallback(
786  function ( Title $parserTitle, $parser = false ) use ( $title, $oldCallback ) {
787  if ( $parserTitle->equals( $title ) ) {
788  $legacyRevision = new Revision( $this->revision );
789  return $legacyRevision;
790  } else {
791  return call_user_func( $oldCallback, $parserTitle, $parser );
792  }
793  }
794  );
795 
796  $pstContentSlots = $this->revision->getSlots();
797 
798  foreach ( $slotsUpdate->getModifiedRoles() as $role ) {
799  $slot = $slotsUpdate->getModifiedSlot( $role );
800 
801  if ( $slot->isInherited() ) {
802  // No PST for inherited slots! Note that "modified" slots may still be inherited
803  // from an earlier version, e.g. for rollbacks.
804  $pstSlot = $slot;
805  } elseif ( $role === SlotRecord::MAIN && $stashedEdit ) {
806  // TODO: MCR: allow PST content for all slots to be stashed.
807  $pstSlot = SlotRecord::newUnsaved( $role, $stashedEdit->pstContent );
808  } else {
809  $content = $slot->getContent();
810  $pstContent = $content->preSaveTransform( $title, $this->user, $userPopts );
811  $pstSlot = SlotRecord::newUnsaved( $role, $pstContent );
812  }
813 
814  $pstContentSlots->setSlot( $pstSlot );
815  }
816 
817  foreach ( $slotsUpdate->getRemovedRoles() as $role ) {
818  $pstContentSlots->removeSlot( $role );
819  }
820 
821  $this->options['created'] = ( $parentRevision === null );
822  $this->options['changed'] = ( $parentRevision === null
823  || !$pstContentSlots->hasSameContent( $parentRevision->getSlots() ) );
824 
825  $this->doTransition( 'has-content' );
826 
827  if ( !$this->options['changed'] ) {
828  // null-edit!
829 
830  // TODO: move this into MutableRevisionRecord
831  // TODO: This needs to behave differently for a forced dummy edit!
832  $this->revision->setId( $parentRevision->getId() );
833  $this->revision->setTimestamp( $parentRevision->getTimestamp() );
834  $this->revision->setPageId( $parentRevision->getPageId() );
835  $this->revision->setParentId( $parentRevision->getParentId() );
836  $this->revision->setUser( $parentRevision->getUser( RevisionRecord::RAW ) );
837  $this->revision->setComment( $parentRevision->getComment( RevisionRecord::RAW ) );
838  $this->revision->setMinorEdit( $parentRevision->isMinor() );
839  $this->revision->setVisibility( $parentRevision->getVisibility() );
840 
841  // prepareUpdate() is redundant for null-edits
842  $this->doTransition( 'has-revision' );
843  } else {
844  $this->parentRevision = $parentRevision;
845  }
846 
847  $renderHints = [ 'use-master' => $this->useMaster(), 'audience' => RevisionRecord::RAW ];
848 
849  if ( $stashedEdit ) {
851  $output = $stashedEdit->output;
852 
853  // TODO: this should happen when stashing the ParserOutput, not now!
854  $output->setCacheTime( $stashedEdit->timestamp );
855 
856  $renderHints['known-revision-output'] = $output;
857  }
858 
859  // NOTE: we want a canonical rendering, so don't pass $this->user or ParserOptions
860  // NOTE: the revision is either new or current, so we can bypass audience checks.
861  $this->renderedRevision = $this->revisionRenderer->getRenderedRevision(
862  $this->revision,
863  null,
864  null,
865  $renderHints
866  );
867  }
868 
884  public function getRevision() {
885  $this->assertPrepared( __METHOD__ );
886  return $this->revision;
887  }
888 
892  public function getRenderedRevision() {
893  $this->assertPrepared( __METHOD__ );
894 
896  }
897 
898  private function assertHasPageState( $method ) {
899  if ( !$this->pageState ) {
900  throw new LogicException(
901  'Must call grabCurrentRevision() or prepareContent() '
902  . 'or prepareUpdate() before calling ' . $method
903  );
904  }
905  }
906 
907  private function assertPrepared( $method ) {
908  if ( !$this->revision ) {
909  throw new LogicException(
910  'Must call prepareContent() or prepareUpdate() before calling ' . $method
911  );
912  }
913  }
914 
915  private function assertHasRevision( $method ) {
916  if ( !$this->revision->getId() ) {
917  throw new LogicException(
918  'Must call prepareUpdate() before calling ' . $method
919  );
920  }
921  }
922 
928  public function isCreation() {
929  $this->assertPrepared( __METHOD__ );
930  return $this->options['created'];
931  }
932 
942  public function isChange() {
943  $this->assertPrepared( __METHOD__ );
944  return $this->options['changed'];
945  }
946 
952  public function wasRedirect() {
953  $this->assertHasPageState( __METHOD__ );
954 
955  if ( $this->pageState['oldIsRedirect'] === null ) {
957  $rev = $this->pageState['oldRevision'];
958  if ( $rev ) {
959  $this->pageState['oldIsRedirect'] = $this->revisionIsRedirect( $rev );
960  } else {
961  $this->pageState['oldIsRedirect'] = false;
962  }
963  }
964 
965  return $this->pageState['oldIsRedirect'];
966  }
967 
976  public function getSlots() {
977  $this->assertPrepared( __METHOD__ );
978  return $this->revision->getSlots();
979  }
980 
986  private function getRevisionSlotsUpdate() {
987  $this->assertPrepared( __METHOD__ );
988 
989  if ( !$this->slotsUpdate ) {
990  $old = $this->getParentRevision();
991  $this->slotsUpdate = RevisionSlotsUpdate::newFromRevisionSlots(
992  $this->revision->getSlots(),
993  $old ? $old->getSlots() : null
994  );
995  }
996  return $this->slotsUpdate;
997  }
998 
1005  public function getTouchedSlotRoles() {
1006  return $this->getRevisionSlotsUpdate()->getTouchedRoles();
1007  }
1008 
1015  public function getModifiedSlotRoles() {
1016  return $this->getRevisionSlotsUpdate()->getModifiedRoles();
1017  }
1018 
1024  public function getRemovedSlotRoles() {
1025  return $this->getRevisionSlotsUpdate()->getRemovedRoles();
1026  }
1027 
1072  Assert::parameter(
1073  !isset( $options['oldrevision'] )
1074  || $options['oldrevision'] instanceof Revision
1075  || $options['oldrevision'] instanceof RevisionRecord,
1076  '$options["oldrevision"]',
1077  'must be a RevisionRecord (or Revision)'
1078  );
1079  Assert::parameter(
1080  !isset( $options['triggeringUser'] )
1081  || $options['triggeringUser'] instanceof UserIdentity,
1082  '$options["triggeringUser"]',
1083  'must be a UserIdentity'
1084  );
1085 
1086  if ( !$revision->getId() ) {
1087  throw new InvalidArgumentException(
1088  'Revision must have an ID set for it to be used with prepareUpdate()!'
1089  );
1090  }
1091 
1092  if ( $this->revision && $this->revision->getId() ) {
1093  if ( $this->revision->getId() === $revision->getId() ) {
1094  return; // nothing to do!
1095  } else {
1096  throw new LogicException(
1097  'Trying to re-use DerivedPageDataUpdater with revision '
1098  . $revision->getId()
1099  . ', but it\'s already bound to revision '
1100  . $this->revision->getId()
1101  );
1102  }
1103  }
1104 
1105  if ( $this->revision
1106  && !$this->revision->getSlots()->hasSameContent( $revision->getSlots() )
1107  ) {
1108  throw new LogicException(
1109  'The Revision provided has mismatching content!'
1110  );
1111  }
1112 
1113  // Override fields defined in $this->options with values from $options.
1114  $this->options = array_intersect_key( $options, $this->options ) + $this->options;
1115 
1116  if ( $this->revision ) {
1117  $oldId = $this->pageState['oldId'] ?? 0;
1118  $this->options['newrev'] = ( $revision->getId() !== $oldId );
1119  } elseif ( isset( $this->options['oldrevision'] ) ) {
1121  $oldRev = $this->options['oldrevision'];
1122  $oldId = $oldRev->getId();
1123  $this->options['newrev'] = ( $revision->getId() !== $oldId );
1124  } else {
1125  $oldId = $revision->getParentId();
1126  }
1127 
1128  if ( $oldId !== null ) {
1129  // XXX: what if $options['changed'] disagrees?
1130  // MovePage creates a dummy revision with changed = false!
1131  // We may want to explicitly distinguish between "no new revision" (null-edit)
1132  // and "new revision without new content" (dummy revision).
1133 
1134  if ( $oldId === $revision->getParentId() ) {
1135  // NOTE: this may still be a NullRevision!
1136  // New revision!
1137  $this->options['changed'] = true;
1138  } elseif ( $oldId === $revision->getId() ) {
1139  // Null-edit!
1140  $this->options['changed'] = false;
1141  } else {
1142  // This indicates that calling code has given us the wrong Revision object
1143  throw new LogicException(
1144  'The Revision mismatches old revision ID: '
1145  . 'Old ID is ' . $oldId
1146  . ', parent ID is ' . $revision->getParentId()
1147  . ', revision ID is ' . $revision->getId()
1148  );
1149  }
1150  }
1151 
1152  // If prepareContent() was used to generate the PST content (which is indicated by
1153  // $this->slotsUpdate being set), and this is not a null-edit, then the given
1154  // revision must have the acting user as the revision author. Otherwise, user
1155  // signatures generated by PST would mismatch the user in the revision record.
1156  if ( $this->user !== null && $this->options['changed'] && $this->slotsUpdate ) {
1157  $user = $revision->getUser();
1158  if ( !$this->user->equals( $user ) ) {
1159  throw new LogicException(
1160  'The Revision provided has a mismatching actor: expected '
1161  . $this->user->getName()
1162  . ', got '
1163  . $user->getName()
1164  );
1165  }
1166  }
1167 
1168  // If $this->pageState was not yet initialized by grabCurrentRevision or prepareContent,
1169  // emulate the state of the page table before the edit, as good as we can.
1170  if ( !$this->pageState ) {
1171  $this->pageState = [
1172  'oldIsRedirect' => isset( $this->options['oldredirect'] )
1173  && is_bool( $this->options['oldredirect'] )
1174  ? $this->options['oldredirect']
1175  : null,
1176  'oldCountable' => isset( $this->options['oldcountable'] )
1177  && is_bool( $this->options['oldcountable'] )
1178  ? $this->options['oldcountable']
1179  : null,
1180  ];
1181 
1182  if ( $this->options['changed'] ) {
1183  // The edit created a new revision
1184  $this->pageState['oldId'] = $revision->getParentId();
1185 
1186  if ( isset( $this->options['oldrevision'] ) ) {
1187  $rev = $this->options['oldrevision'];
1188  $this->pageState['oldRevision'] = $rev instanceof Revision
1189  ? $rev->getRevisionRecord()
1190  : $rev;
1191  }
1192  } else {
1193  // This is a null-edit, so the old revision IS the new revision!
1194  $this->pageState['oldId'] = $revision->getId();
1195  $this->pageState['oldRevision'] = $revision;
1196  }
1197  }
1198 
1199  // "created" is forced here
1200  $this->options['created'] = ( $this->pageState['oldId'] === 0 );
1201 
1202  $this->revision = $revision;
1203 
1204  $this->doTransition( 'has-revision' );
1205 
1206  // NOTE: in case we have a User object, don't override with a UserIdentity.
1207  // We already checked that $revision->getUser() mathces $this->user;
1208  if ( !$this->user ) {
1209  $this->user = $revision->getUser( RevisionRecord::RAW );
1210  }
1211 
1212  // Prune any output that depends on the revision ID.
1213  if ( $this->renderedRevision ) {
1214  $this->renderedRevision->updateRevision( $revision );
1215  } else {
1216 
1217  // NOTE: we want a canonical rendering, so don't pass $this->user or ParserOptions
1218  // NOTE: the revision is either new or current, so we can bypass audience checks.
1219  $this->renderedRevision = $this->revisionRenderer->getRenderedRevision(
1220  $this->revision,
1221  null,
1222  null,
1223  [ 'use-master' => $this->useMaster(), 'audience' => RevisionRecord::RAW ]
1224  );
1225 
1226  // XXX: Since we presumably are dealing with the current revision,
1227  // we could try to get the ParserOutput from the parser cache.
1228  }
1229 
1230  // TODO: optionally get ParserOutput from the ParserCache here.
1231  // Move the logic used by RefreshLinksJob here!
1232  }
1233 
1238  public function getPreparedEdit() {
1239  $this->assertPrepared( __METHOD__ );
1240 
1242  $preparedEdit = new PreparedEdit();
1243 
1244  $preparedEdit->popts = $this->getCanonicalParserOptions();
1245  $preparedEdit->parserOutputCallback = [ $this, 'getCanonicalParserOutput' ];
1246  $preparedEdit->pstContent = $this->revision->getContent( SlotRecord::MAIN );
1247  $preparedEdit->newContent =
1249  ? $slotsUpdate->getModifiedSlot( SlotRecord::MAIN )->getContent()
1250  : $this->revision->getContent( SlotRecord::MAIN ); // XXX: can we just remove this?
1251  $preparedEdit->oldContent = null; // unused. // XXX: could get this from the parent revision
1252  $preparedEdit->revid = $this->revision ? $this->revision->getId() : null;
1253  $preparedEdit->timestamp = $preparedEdit->output->getCacheTime();
1254  $preparedEdit->format = $preparedEdit->pstContent->getDefaultFormat();
1255 
1256  return $preparedEdit;
1257  }
1258 
1264  public function getSlotParserOutput( $role, $generateHtml = true ) {
1265  return $this->getRenderedRevision()->getSlotParserOutput(
1266  $role,
1267  [ 'generate-html' => $generateHtml ]
1268  );
1269  }
1270 
1274  public function getCanonicalParserOutput() {
1275  return $this->getRenderedRevision()->getRevisionParserOutput();
1276  }
1277 
1281  public function getCanonicalParserOptions() {
1282  return $this->getRenderedRevision()->getOptions();
1283  }
1284 
1290  public function getSecondaryDataUpdates( $recursive = false ) {
1291  if ( $this->isContentDeleted() ) {
1292  // This shouldn't happen, since the current content is always public,
1293  // and DataUpates are only needed for current content.
1294  return [];
1295  }
1296 
1297  $output = $this->getCanonicalParserOutput();
1298 
1299  // Construct a LinksUpdate for the combined canonical output.
1300  $linksUpdate = new LinksUpdate(
1301  $this->getTitle(),
1302  $output,
1303  $recursive
1304  );
1305 
1306  $allUpdates = [ $linksUpdate ];
1307 
1308  // NOTE: Run updates for all slots, not just the modified slots! Otherwise,
1309  // info for an inherited slot may end up being removed. This is also needed
1310  // to ensure that purges are effective.
1312  foreach ( $this->getSlots()->getSlotRoles() as $role ) {
1313  $slot = $this->getRawSlot( $role );
1314  $content = $slot->getContent();
1315  $handler = $content->getContentHandler();
1316 
1317  $updates = $handler->getSecondaryDataUpdates(
1318  $this->getTitle(),
1319  $content,
1320  $role,
1322  );
1323  $allUpdates = array_merge( $allUpdates, $updates );
1324 
1325  // TODO: remove B/C hack in 1.32!
1326  // NOTE: we assume that the combined output contains all relevant meta-data for
1327  // all slots!
1328  $legacyUpdates = $content->getSecondaryDataUpdates(
1329  $this->getTitle(),
1330  null,
1331  $recursive,
1332  $output
1333  );
1334 
1335  // HACK: filter out redundant and incomplete LinksUpdates
1336  $legacyUpdates = array_filter( $legacyUpdates, function ( $update ) {
1337  return !( $update instanceof LinksUpdate );
1338  } );
1339 
1340  $allUpdates = array_merge( $allUpdates, $legacyUpdates );
1341  }
1342 
1343  // XXX: if a slot was removed by an earlier edit, but deletion updates failed to run at
1344  // that time, we don't know for which slots to run deletion updates when purging a page.
1345  // We'd have to examine the entire history of the page to determine that. Perhaps there
1346  // could be a "try extra hard" mode for that case that would run a DB query to find all
1347  // roles/models ever used on the page. On the other hand, removing slots should be quite
1348  // rare, so perhaps this isn't worth the trouble.
1349 
1350  // TODO: consolidate with similar logic in WikiPage::getDeletionUpdates()
1351  $wikiPage = $this->getWikiPage();
1353  foreach ( $this->getRemovedSlotRoles() as $role ) {
1354  // HACK: we should get the content model of the removed slot from a SlotRoleHandler!
1355  // For now, find the slot in the parent revision - if the slot was removed, it should
1356  // always exist in the parent revision.
1357  $parentSlot = $parentRevision->getSlot( $role, RevisionRecord::RAW );
1358  $content = $parentSlot->getContent();
1359  $handler = $content->getContentHandler();
1360 
1361  $updates = $handler->getDeletionUpdates(
1362  $this->getTitle(),
1363  $role
1364  );
1365  $allUpdates = array_merge( $allUpdates, $updates );
1366 
1367  // TODO: remove B/C hack in 1.32!
1368  $legacyUpdates = $content->getDeletionUpdates( $wikiPage );
1369 
1370  // HACK: filter out redundant and incomplete LinksDeletionUpdate
1371  $legacyUpdates = array_filter( $legacyUpdates, function ( $update ) {
1372  return !( $update instanceof LinksDeletionUpdate );
1373  } );
1374 
1375  $allUpdates = array_merge( $allUpdates, $legacyUpdates );
1376  }
1377 
1378  // TODO: hard deprecate SecondaryDataUpdates in favor of RevisionDataUpdates in 1.33!
1379  Hooks::run(
1380  'RevisionDataUpdates',
1381  [ $this->getTitle(), $renderedRevision, &$allUpdates ]
1382  );
1383 
1384  return $allUpdates;
1385  }
1386 
1397  public function doUpdates() {
1398  $this->assertTransition( 'done' );
1399 
1400  // TODO: move logic into a PageEventEmitter service
1401 
1402  $wikiPage = $this->getWikiPage(); // TODO: use only for legacy hooks!
1403 
1404  $legacyUser = User::newFromIdentity( $this->user );
1405  $legacyRevision = new Revision( $this->revision );
1406 
1407  $userParserOptions = ParserOptions::newFromUser( $legacyUser );
1408  // Decide whether to save the final canonical parser ouput based on the fact that
1409  // users are typically redirected to viewing pages right after they edit those pages.
1410  // Due to vary-revision-id, getting/saving that output here might require a reparse.
1411  if ( $userParserOptions->matchesForCacheKey( $this->getCanonicalParserOptions() ) ) {
1412  // Whether getting the final output requires a reparse or not, the user will
1413  // need canonical output anyway, since that is what their parser options use.
1414  // A reparse now at least has the benefit of various warm process caches.
1415  $this->doParserCacheUpdate();
1416  } else {
1417  // If the user does not have canonical parse options, then don't risk another parse
1418  // to make output they cannot use on the page refresh that typically occurs after
1419  // editing. Doing the parser output save post-send will still benefit *other* users.
1420  DeferredUpdates::addCallableUpdate( function () {
1421  $this->doParserCacheUpdate();
1422  } );
1423  }
1424 
1425  // Defer the getCannonicalParserOutput() call triggered by getSecondaryDataUpdates()
1426  DeferredUpdates::addCallableUpdate( function () {
1427  $this->doSecondaryDataUpdates( [
1428  // T52785 do not update any other pages on a null edit
1429  'recursive' => $this->options['changed']
1430  ] );
1431  } );
1432 
1433  // TODO: MCR: check if *any* changed slot supports categories!
1434  if ( $this->rcWatchCategoryMembership
1435  && $this->getContentHandler( SlotRecord::MAIN )->supportsCategories() === true
1436  && ( $this->options['changed'] || $this->options['created'] )
1437  && !$this->options['restored']
1438  ) {
1439  // Note: jobs are pushed after deferred updates, so the job should be able to see
1440  // the recent change entry (also done via deferred updates) and carry over any
1441  // bot/deletion/IP flags, ect.
1442  $this->jobQueueGroup->lazyPush(
1444  $this->getTitle(),
1445  $this->revision->getTimestamp()
1446  )
1447  );
1448  }
1449 
1450  // TODO: replace legacy hook! Use a listener on PageEventEmitter instead!
1451  // @note: Extensions should *avoid* calling getCannonicalParserOutput() when using
1452  // this hook whenever possible in order to avoid unnecessary additional parses.
1453  $editInfo = $this->getPreparedEdit();
1454  Hooks::run( 'ArticleEditUpdates',
1455  [ &$wikiPage, &$editInfo, $this->options['changed'] ] );
1456 
1457  // TODO: replace legacy hook! Use a listener on PageEventEmitter instead!
1458  if ( Hooks::run( 'ArticleEditUpdatesDeleteFromRecentchanges', [ &$wikiPage ] ) ) {
1459  // Flush old entries from the `recentchanges` table
1460  if ( mt_rand( 0, 9 ) == 0 ) {
1461  $this->jobQueueGroup->lazyPush( RecentChangesUpdateJob::newPurgeJob() );
1462  }
1463  }
1464 
1465  $id = $this->getPageId();
1466  $title = $this->getTitle();
1467  $dbKey = $title->getPrefixedDBkey();
1468  $shortTitle = $title->getDBkey();
1469 
1470  if ( !$title->exists() ) {
1471  wfDebug( __METHOD__ . ": Page doesn't exist any more, bailing out\n" );
1472 
1473  $this->doTransition( 'done' );
1474  return;
1475  }
1476 
1477  if ( $this->options['oldcountable'] === 'no-change' ||
1478  ( !$this->options['changed'] && !$this->options['moved'] )
1479  ) {
1480  $good = 0;
1481  } elseif ( $this->options['created'] ) {
1482  $good = (int)$this->isCountable();
1483  } elseif ( $this->options['oldcountable'] !== null ) {
1484  $good = (int)$this->isCountable()
1485  - (int)$this->options['oldcountable'];
1486  } elseif ( isset( $this->pageState['oldCountable'] ) ) {
1487  $good = (int)$this->isCountable()
1488  - (int)$this->pageState['oldCountable'];
1489  } else {
1490  $good = 0;
1491  }
1492  $edits = $this->options['changed'] ? 1 : 0;
1493  $pages = $this->options['created'] ? 1 : 0;
1494 
1496  [ 'edits' => $edits, 'articles' => $good, 'pages' => $pages ]
1497  ) );
1498 
1499  // TODO: make search infrastructure aware of slots!
1500  $mainSlot = $this->revision->getSlot( SlotRecord::MAIN );
1501  if ( !$mainSlot->isInherited() && !$this->isContentDeleted() ) {
1502  DeferredUpdates::addUpdate( new SearchUpdate( $id, $dbKey, $mainSlot->getContent() ) );
1503  }
1504 
1505  // If this is another user's talk page, update newtalk.
1506  // Don't do this if $options['changed'] = false (null-edits) nor if
1507  // it's a minor edit and the user making the edit doesn't generate notifications for those.
1508  if ( $this->options['changed']
1509  && $title->getNamespace() == NS_USER_TALK
1510  && $shortTitle != $legacyUser->getTitleKey()
1511  && !( $this->revision->isMinor() && $legacyUser->isAllowed( 'nominornewtalk' ) )
1512  ) {
1513  $recipient = User::newFromName( $shortTitle, false );
1514  if ( !$recipient ) {
1515  wfDebug( __METHOD__ . ": invalid username\n" );
1516  } else {
1517  // Allow extensions to prevent user notification
1518  // when a new message is added to their talk page
1519  // TODO: replace legacy hook! Use a listener on PageEventEmitter instead!
1520  if ( Hooks::run( 'ArticleEditUpdateNewTalk', [ &$wikiPage, $recipient ] ) ) {
1521  if ( User::isIP( $shortTitle ) ) {
1522  // An anonymous user
1523  $recipient->setNewtalk( true, $legacyRevision );
1524  } elseif ( $recipient->isLoggedIn() ) {
1525  $recipient->setNewtalk( true, $legacyRevision );
1526  } else {
1527  wfDebug( __METHOD__ . ": don't need to notify a nonexistent user\n" );
1528  }
1529  }
1530  }
1531  }
1532 
1533  if ( $title->getNamespace() == NS_MEDIAWIKI
1534  && $this->getRevisionSlotsUpdate()->isModifiedSlot( SlotRecord::MAIN )
1535  ) {
1536  $mainContent = $this->isContentDeleted() ? null : $this->getRawContent( SlotRecord::MAIN );
1537 
1538  $this->messageCache->updateMessageOverride( $title, $mainContent );
1539  }
1540 
1541  // TODO: move onArticleCreate and onArticle into a PageEventEmitter service
1542  if ( $this->options['created'] ) {
1544  } elseif ( $this->options['changed'] ) { // T52785
1545  WikiPage::onArticleEdit( $title, $legacyRevision, $this->getTouchedSlotRoles() );
1546  }
1547 
1548  $oldRevision = $this->getParentRevision();
1549  $oldLegacyRevision = $oldRevision ? new Revision( $oldRevision ) : null;
1550 
1551  // TODO: In the wiring, register a listener for this on the new PageEventEmitter
1553  $title, $oldLegacyRevision, $legacyRevision, $this->getWikiId() ?: wfWikiID()
1554  );
1555 
1556  $this->doTransition( 'done' );
1557  }
1558 
1573  public function doSecondaryDataUpdates( array $options = [] ) {
1574  $this->assertHasRevision( __METHOD__ );
1575  $options += [
1576  'recursive' => false,
1577  'defer' => false,
1578  'transactionTicket' => null,
1579  ];
1580  $deferValues = [ false, DeferredUpdates::PRESEND, DeferredUpdates::POSTSEND ];
1581  if ( !in_array( $options['defer'], $deferValues, true ) ) {
1582  throw new InvalidArgumentException( 'invalid value for defer: ' . $options['defer'] );
1583  }
1584  Assert::parameterType( 'integer|null', $options['transactionTicket'],
1585  '$options[\'transactionTicket\']' );
1586 
1587  $updates = $this->getSecondaryDataUpdates( $options['recursive'] );
1588 
1589  $triggeringUser = $this->options['triggeringUser'] ?? $this->user;
1590  if ( !$triggeringUser instanceof User ) {
1591  $triggeringUser = User::newFromIdentity( $triggeringUser );
1592  }
1593  $causeAction = $this->options['causeAction'] ?? 'unknown';
1594  $causeAgent = $this->options['causeAgent'] ?? 'unknown';
1595  $legacyRevision = new Revision( $this->revision );
1596 
1597  if ( $options['defer'] === false && $options['transactionTicket'] !== null ) {
1598  // For legacy hook handlers doing updates via LinksUpdateConstructed, make sure
1599  // any pending writes they made get flushed before the doUpdate() calls below.
1600  // This avoids snapshot-clearing errors in LinksUpdate::acquirePageLock().
1601  $this->loadbalancerFactory->commitAndWaitForReplication(
1602  __METHOD__, $options['transactionTicket']
1603  );
1604  }
1605 
1606  foreach ( $updates as $update ) {
1607  if ( $update instanceof DataUpdate ) {
1608  $update->setCause( $causeAction, $causeAgent );
1609  }
1610  if ( $update instanceof LinksUpdate ) {
1611  $update->setRevision( $legacyRevision );
1612  $update->setTriggeringUser( $triggeringUser );
1613  }
1614 
1615  if ( $options['defer'] === false ) {
1616  if ( $update instanceof DataUpdate && $options['transactionTicket'] !== null ) {
1617  $update->setTransactionTicket( $options['transactionTicket'] );
1618  }
1619  $update->doUpdate();
1620  } else {
1621  DeferredUpdates::addUpdate( $update, $options['defer'] );
1622  }
1623  }
1624  }
1625 
1626  public function doParserCacheUpdate() {
1627  $this->assertHasRevision( __METHOD__ );
1628 
1629  $wikiPage = $this->getWikiPage(); // TODO: ParserCache should accept a RevisionRecord instead
1630 
1631  // NOTE: this may trigger the first parsing of the new content after an edit (when not
1632  // using pre-generated stashed output).
1633  // XXX: we may want to use the PoolCounter here. This would perhaps allow the initial parse
1634  // to be performed post-send. The client could already follow a HTTP redirect to the
1635  // page view, but would then have to wait for a response until rendering is complete.
1636  $output = $this->getCanonicalParserOutput();
1637 
1638  // Save it to the parser cache. Use the revision timestamp in the case of a
1639  // freshly saved edit, as that matches page_touched and a mismatch would trigger an
1640  // unnecessary reparse.
1641  $timestamp = $this->options['newrev'] ? $this->revision->getTimestamp()
1642  : $output->getCacheTime();
1643  $this->parserCache->save(
1645  $timestamp, $this->revision->getId()
1646  );
1647  }
1648 
1649 }
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
In both all secondary updates will be triggered handle like object that caches derived data representing a and can trigger updates of cached copies of that e g in the links the ParserCache
Definition: pageupdater.txt:78
static onArticleCreate(Title $title)
The onArticle*() functions are supposed to be a kind of hooks which should be called whenever any of ...
Definition: WikiPage.php:3377
doUpdates()
Do standard updates after page edit, purge, or import.
isReusableFor(UserIdentity $user=null, RevisionRecord $revision=null, RevisionSlotsUpdate $slotsUpdate=null, $parentId=null)
Checks whether this DerivedPageDataUpdater can be re-used for running updates targeting the given rev...
isMinor()
MCR migration note: this replaces Revision::isMinor.
getRemovedSlotRoles()
Returns the role names of the slots removed by the new revision.
loadPageData( $from='fromdb')
Load the object from a given source by title.
Definition: WikiPage.php:485
static invalidateModuleCache(Title $title, Revision $old=null, Revision $new=null, $domain)
Clear the preloadTitleInfo() cache for all wiki modules on this wiki on page change if it was a JS or...
An interface for generating database load balancers.
Definition: LBFactory.php:39
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:969
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
Represents information returned by WikiPage::prepareContentForEdit()
The RevisionRenderer service provides access to rendered output for revisions.
getContentModel( $role)
Returns the content model of the given slot.
getSlots()
Returns the slots defined for this revision.
static newFromUserAndLang(User $user, Language $lang)
Get a ParserOptions object from a given user and language.
getRevisionSlotsUpdate()
Returns the RevisionSlotsUpdate for this updater.
A registry service for SlotRoleHandlers, used to define which slot roles are available on which page...
isUpdatePrepared()
Whether prepareUpdate() has been called on this instance.
doSecondaryDataUpdates(array $options=[])
Do secondary data updates (such as updating link tables).
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency MediaWikiServices
Definition: injection.txt:23
Value object representing a modification of revision slots.
getTouchedSlotRoles()
Returns the role names of the slots touched by the new revision, including removed roles...
wasRedirect()
Whether the page was a redirect before the edit.
static getInstance()
Returns the global default instance of the top level service locator.
isContentDeleted()
Whether the content is deleted and thus not visible to the public.
see documentation in includes Linker php for Linker::makeImageLink or false for current used if you return false $parser
Definition: hooks.txt:1799
either a unescaped string or a HtmlArmor object after in associative array form externallinks $linksUpdate
Definition: hooks.txt:2061
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2454
pageExisted()
Determines whether the page being edited already existed.
isRedirect()
Tests if the article content represents a redirect.
Definition: WikiPage.php:630
isContentPrepared()
Whether prepareUpdate() or prepareContent() have been called on this instance.
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title e g db for database replication lag or jobqueue for job queue size converted to pseudo seconds It is possible to add more fields and they will be returned to the user in the API response after the basic globals have been set but before ordinary actions take place $output
Definition: hooks.txt:2217
Mutable RevisionRecord implementation, for building new revision entries programmatically.
getRevision()
Returns the update&#39;s target revision - that is, the revision that will be the current revision after ...
string $articleCountMethod
see $wgArticleCountMethod
static onArticleEdit(Title $title, Revision $revision=null, $slotsChanged=null)
Purge caches on page update etc.
Definition: WikiPage.php:3464
Interface for objects representing user identity.
The User object encapsulates all of the user-specific settings (user_id, name, rights, email address, options, last login time).
Definition: User.php:48
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:780
prepareContent(User $user, RevisionSlotsUpdate $slotsUpdate, $useStash=true)
Prepare updates based on an update which has not yet been saved.
getRevision()
Get the latest revision.
Definition: WikiPage.php:783
static addCallableUpdate( $callable, $stage=self::POSTSEND, $dbw=null)
Add a callable update.
getRawContent( $role)
Returns the content of the given slot, with no audience checks.
static newFromRevisionSlots(RevisionSlots $newSlots, RevisionSlots $parentSlots=null)
Constructs a RevisionSlotsUpdate representing the update that turned $parentSlots into $newSlots...
static getForModelID( $modelId)
Returns the ContentHandler singleton for the given model ID.
getVisibility()
Get the deletion bitfield of the revision.
static factory(array $deltas)
array $pageState
The state of the relevant row in page table before the edit.
boolean $rcWatchCategoryMembership
see $wgRCWatchCategoryMembership
Service for looking up page revisions.
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
getContent( $role, $audience=self::FOR_PUBLIC, User $user=null)
Returns the Content of the given slot of this revision.
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
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
setRcWatchCategoryMembership( $rcWatchCategoryMembership)
isChange()
Whether the edit created, or should create, a new revision (that is, it&#39;s not a null-edit).
hasSameUpdates(RevisionSlotsUpdate $other)
Returns true if $other represents the same update - that is, if all methods defined by RevisionSlotsU...
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:780
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:925
equals(Title $title)
Compare with another title.
Definition: Title.php:4037
wfWikiID()
Get an ASCII string identifying this wiki This is used as a prefix in memcached keys.
$options
Stores (most of) the $options parameter of prepareUpdate().
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:1766
isModifiedSlot( $role)
Returns whether getModifiedSlot() will return a SlotRecord for the given role.
getRawSlot( $role)
Returns the slot, modified or inherited, after PST, with no audience checks applied.
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
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:654
static newFromParentRevision(RevisionRecord $parent)
Returns an incomplete MutableRevisionRecord which uses $parent as its parent revision, and inherits all slots form it.
grabCurrentRevision()
Returns the revision that was the page&#39;s current revision when grabCurrentRevision() was first called...
const NS_MEDIAWIKI
Definition: Defines.php:72
string $stage
A stage identifier for managing the life cycle of this instance.
isCountable( $editInfo=false)
Determine whether a page would be suitable for being counted as an article in the site_stats table ba...
Definition: WikiPage.php:942
getSlot( $role, $audience=self::FOR_PUBLIC, User $user=null)
Returns meta-data for the given slot.
getId()
Get revision ID.
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
A handle for managing updates for derived page data on edit, import, purge, etc.
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the deferred list to be run later by execute()
Using a hook running we can avoid having all this option specific stuff in our mainline code Using the function We ve cleaned up the code here by removing clumps of infrequently used code and moving them off somewhere else It s much easier for someone working with this code to see what s _really_ going and make changes or fix bugs In we can take all the code that deals with the little used title reversing options(say) and put it in one place. Instead of having little title-reversing if-blocks spread all over the codebase in showAnArticle
static array [] __construct(WikiPage $wikiPage, RevisionStore $revisionStore, RevisionRenderer $revisionRenderer, SlotRoleRegistry $slotRoleRegistry, ParserCache $parserCache, JobQueueGroup $jobQueueGroup, MessageCache $messageCache, Language $contLang, LBFactory $loadbalancerFactory)
getModifiedRoles()
Returns a list of modified slot roles, that is, roles modified by calling modifySlot(), and not later removed by calling removeSlot().
static newSpec(Title $title, $revisionTimestamp)
getParentRevision()
Returns the parent revision of the new revision wrapped by this update.
static newFromUser( $user)
Get a ParserOptions object from a given user.
getModifiedSlot( $role)
Returns the SlotRecord associated with the given role, if the slot with that role was modified (and n...
getTimestamp()
MCR migration note: this replaces Revision::getTimestamp.
getSlots()
Returns the slots of the target revision, after PST.
In both all secondary updates will be triggered handle like object that caches derived data representing a revision
Definition: pageupdater.txt:78
isCreation()
Whether the edit creates the page.
static newUnsaved( $role, Content $content)
Constructs a new Slot from a Content object for a new revision.
Definition: SlotRecord.php:129
Page revision base class.
getUser( $audience=self::FOR_PUBLIC, User $user=null)
Fetch revision&#39;s author&#39;s user identity, if it&#39;s available to the specified audience.
prepareUpdate(RevisionRecord $revision, array $options=[])
Prepare derived data updates targeting the given Revision.
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:587
getRemovedRoles()
Returns a list of removed slot roles, that is, roles removed by calling removeSlot(), and not later re-introduced by calling modifySlot().
getComment( $audience=self::FOR_PUBLIC, User $user=null)
Fetch revision comment, if it&#39;s available to the specified audience.
assertTransition( $newStage)
Asserts that a transition to the given stage is possible, without performing it.
$content
Definition: pageupdater.txt:72
const NS_USER_TALK
Definition: Defines.php:67
getParentId()
Get parent revision ID (the original previous page revision).
doTransition( $newStage)
Transition function for managing the life cycle of this instances.
static array [] $transitions
Transition table for managing the life cycle of DerivedPageDateUpdater instances. ...
getPageId()
Get the page ID.
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
getModifiedSlotRoles()
Returns the role names of the slots modified by the new revision, not including removed roles...