MediaWiki  1.33.0
DerivedPageDataUpdater.php
Go to the documentation of this file.
1 <?php
23 namespace MediaWiki\Storage;
24 
27 use Content;
32 use Hooks;
34 use InvalidArgumentException;
39 use LogicException;
57 use SearchUpdate;
59 use Title;
60 use User;
61 use Wikimedia\Assert\Assert;
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 
199  private $parentRevision = null;
200 
204  private $revision = null;
205 
209  private $renderedRevision = null;
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(
352  UserIdentity $user = null,
353  RevisionRecord $revision = null,
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  $this->slotsOutput = [];
754  $this->canonicalParserOutput = null;
755 
756  // The edit may have already been prepared via api.php?action=stashedit
757  $stashedEdit = false;
758 
759  // TODO: MCR: allow output for all slots to be stashed.
760  if ( $useStash && $slotsUpdate->isModifiedSlot( SlotRecord::MAIN ) ) {
761  $mainContent = $slotsUpdate->getModifiedSlot( SlotRecord::MAIN )->getContent();
762  $legacyUser = User::newFromIdentity( $user );
763  $stashedEdit = ApiStashEdit::checkCache( $title, $mainContent, $legacyUser );
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->output = $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  $this->doParserCacheUpdate();
1408 
1409  $this->doSecondaryDataUpdates( [
1410  // T52785 do not update any other pages on a null edit
1411  'recursive' => $this->options['changed'],
1412  'defer' => DeferredUpdates::POSTSEND,
1413  ] );
1414 
1415  // TODO: MCR: check if *any* changed slot supports categories!
1416  if ( $this->rcWatchCategoryMembership
1417  && $this->getContentHandler( SlotRecord::MAIN )->supportsCategories() === true
1418  && ( $this->options['changed'] || $this->options['created'] )
1419  && !$this->options['restored']
1420  ) {
1421  // Note: jobs are pushed after deferred updates, so the job should be able to see
1422  // the recent change entry (also done via deferred updates) and carry over any
1423  // bot/deletion/IP flags, ect.
1424  $this->jobQueueGroup->lazyPush(
1426  $this->getTitle(),
1427  $this->revision->getTimestamp()
1428  )
1429  );
1430  }
1431 
1432  // TODO: replace legacy hook! Use a listener on PageEventEmitter instead!
1433  $editInfo = $this->getPreparedEdit();
1434  Hooks::run( 'ArticleEditUpdates', [ &$wikiPage, &$editInfo, $this->options['changed'] ] );
1435 
1436  // TODO: replace legacy hook! Use a listener on PageEventEmitter instead!
1437  if ( Hooks::run( 'ArticleEditUpdatesDeleteFromRecentchanges', [ &$wikiPage ] ) ) {
1438  // Flush old entries from the `recentchanges` table
1439  if ( mt_rand( 0, 9 ) == 0 ) {
1440  $this->jobQueueGroup->lazyPush( RecentChangesUpdateJob::newPurgeJob() );
1441  }
1442  }
1443 
1444  $id = $this->getPageId();
1445  $title = $this->getTitle();
1446  $dbKey = $title->getPrefixedDBkey();
1447  $shortTitle = $title->getDBkey();
1448 
1449  if ( !$title->exists() ) {
1450  wfDebug( __METHOD__ . ": Page doesn't exist any more, bailing out\n" );
1451 
1452  $this->doTransition( 'done' );
1453  return;
1454  }
1455 
1456  if ( $this->options['oldcountable'] === 'no-change' ||
1457  ( !$this->options['changed'] && !$this->options['moved'] )
1458  ) {
1459  $good = 0;
1460  } elseif ( $this->options['created'] ) {
1461  $good = (int)$this->isCountable();
1462  } elseif ( $this->options['oldcountable'] !== null ) {
1463  $good = (int)$this->isCountable()
1464  - (int)$this->options['oldcountable'];
1465  } elseif ( isset( $this->pageState['oldCountable'] ) ) {
1466  $good = (int)$this->isCountable()
1467  - (int)$this->pageState['oldCountable'];
1468  } else {
1469  $good = 0;
1470  }
1471  $edits = $this->options['changed'] ? 1 : 0;
1472  $pages = $this->options['created'] ? 1 : 0;
1473 
1475  [ 'edits' => $edits, 'articles' => $good, 'pages' => $pages ]
1476  ) );
1477 
1478  // TODO: make search infrastructure aware of slots!
1479  $mainSlot = $this->revision->getSlot( SlotRecord::MAIN );
1480  if ( !$mainSlot->isInherited() && !$this->isContentDeleted() ) {
1481  DeferredUpdates::addUpdate( new SearchUpdate( $id, $dbKey, $mainSlot->getContent() ) );
1482  }
1483 
1484  // If this is another user's talk page, update newtalk.
1485  // Don't do this if $options['changed'] = false (null-edits) nor if
1486  // it's a minor edit and the user making the edit doesn't generate notifications for those.
1487  if ( $this->options['changed']
1488  && $title->getNamespace() == NS_USER_TALK
1489  && $shortTitle != $legacyUser->getTitleKey()
1490  && !( $this->revision->isMinor() && $legacyUser->isAllowed( 'nominornewtalk' ) )
1491  ) {
1492  $recipient = User::newFromName( $shortTitle, false );
1493  if ( !$recipient ) {
1494  wfDebug( __METHOD__ . ": invalid username\n" );
1495  } else {
1496  // Allow extensions to prevent user notification
1497  // when a new message is added to their talk page
1498  // TODO: replace legacy hook! Use a listener on PageEventEmitter instead!
1499  if ( Hooks::run( 'ArticleEditUpdateNewTalk', [ &$wikiPage, $recipient ] ) ) {
1500  if ( User::isIP( $shortTitle ) ) {
1501  // An anonymous user
1502  $recipient->setNewtalk( true, $legacyRevision );
1503  } elseif ( $recipient->isLoggedIn() ) {
1504  $recipient->setNewtalk( true, $legacyRevision );
1505  } else {
1506  wfDebug( __METHOD__ . ": don't need to notify a nonexistent user\n" );
1507  }
1508  }
1509  }
1510  }
1511 
1512  if ( $title->getNamespace() == NS_MEDIAWIKI
1513  && $this->getRevisionSlotsUpdate()->isModifiedSlot( SlotRecord::MAIN )
1514  ) {
1515  $mainContent = $this->isContentDeleted() ? null : $this->getRawContent( SlotRecord::MAIN );
1516 
1517  $this->messageCache->updateMessageOverride( $title, $mainContent );
1518  }
1519 
1520  // TODO: move onArticleCreate and onArticle into a PageEventEmitter service
1521  if ( $this->options['created'] ) {
1523  } elseif ( $this->options['changed'] ) { // T52785
1524  WikiPage::onArticleEdit( $title, $legacyRevision, $this->getTouchedSlotRoles() );
1525  }
1526 
1527  $oldRevision = $this->getParentRevision();
1528  $oldLegacyRevision = $oldRevision ? new Revision( $oldRevision ) : null;
1529 
1530  // TODO: In the wiring, register a listener for this on the new PageEventEmitter
1532  $title, $oldLegacyRevision, $legacyRevision, $this->getWikiId() ?: wfWikiID()
1533  );
1534 
1535  $this->doTransition( 'done' );
1536  }
1537 
1552  public function doSecondaryDataUpdates( array $options = [] ) {
1553  $this->assertHasRevision( __METHOD__ );
1554  $options += [
1555  'recursive' => false,
1556  'defer' => false,
1557  'transactionTicket' => null,
1558  ];
1559  $deferValues = [ false, DeferredUpdates::PRESEND, DeferredUpdates::POSTSEND ];
1560  if ( !in_array( $options['defer'], $deferValues, true ) ) {
1561  throw new InvalidArgumentException( 'invalid value for defer: ' . $options['defer'] );
1562  }
1563  Assert::parameterType( 'integer|null', $options['transactionTicket'],
1564  '$options[\'transactionTicket\']' );
1565 
1566  $updates = $this->getSecondaryDataUpdates( $options['recursive'] );
1567 
1568  $triggeringUser = $this->options['triggeringUser'] ?? $this->user;
1569  if ( !$triggeringUser instanceof User ) {
1570  $triggeringUser = User::newFromIdentity( $triggeringUser );
1571  }
1572  $causeAction = $this->options['causeAction'] ?? 'unknown';
1573  $causeAgent = $this->options['causeAgent'] ?? 'unknown';
1574  $legacyRevision = new Revision( $this->revision );
1575 
1576  if ( $options['defer'] === false && $options['transactionTicket'] !== null ) {
1577  // For legacy hook handlers doing updates via LinksUpdateConstructed, make sure
1578  // any pending writes they made get flushed before the doUpdate() calls below.
1579  // This avoids snapshot-clearing errors in LinksUpdate::acquirePageLock().
1580  $this->loadbalancerFactory->commitAndWaitForReplication(
1581  __METHOD__, $options['transactionTicket']
1582  );
1583  }
1584 
1585  foreach ( $updates as $update ) {
1586  if ( $update instanceof DataUpdate ) {
1587  $update->setCause( $causeAction, $causeAgent );
1588  }
1589  if ( $update instanceof LinksUpdate ) {
1590  $update->setRevision( $legacyRevision );
1591  $update->setTriggeringUser( $triggeringUser );
1592  }
1593 
1594  if ( $options['defer'] === false ) {
1595  if ( $update instanceof DataUpdate && $options['transactionTicket'] !== null ) {
1596  $update->setTransactionTicket( $options['transactionTicket'] );
1597  }
1598  $update->doUpdate();
1599  } else {
1600  DeferredUpdates::addUpdate( $update, $options['defer'] );
1601  }
1602  }
1603  }
1604 
1605  public function doParserCacheUpdate() {
1606  $this->assertHasRevision( __METHOD__ );
1607 
1608  $wikiPage = $this->getWikiPage(); // TODO: ParserCache should accept a RevisionRecord instead
1609 
1610  // NOTE: this may trigger the first parsing of the new content after an edit (when not
1611  // using pre-generated stashed output).
1612  // XXX: we may want to use the PoolCounter here. This would perhaps allow the initial parse
1613  // to be performed post-send. The client could already follow a HTTP redirect to the
1614  // page view, but would then have to wait for a response until rendering is complete.
1615  $output = $this->getCanonicalParserOutput();
1616 
1617  // Save it to the parser cache. Use the revision timestamp in the case of a
1618  // freshly saved edit, as that matches page_touched and a mismatch would trigger an
1619  // unnecessary reparse.
1620  $timestamp = $this->options['newrev'] ? $this->revision->getTimestamp()
1621  : $output->getCacheTime();
1622  $this->parserCache->save(
1624  $timestamp, $this->revision->getId()
1625  );
1626  }
1627 
1628 }
MediaWiki\Storage\RevisionSlotsUpdate\getModifiedRoles
getModifiedRoles()
Returns a list of modified slot roles, that is, roles modified by calling modifySlot(),...
Definition: RevisionSlotsUpdate.php:137
MediaWiki\Storage\DerivedPageDataUpdater\getCanonicalParserOutput
getCanonicalParserOutput()
Definition: DerivedPageDataUpdater.php:1274
ParserOptions
Set options of the Parser.
Definition: ParserOptions.php:42
ContentHandler
A content handler knows how do deal with a specific type of content on a wiki page.
Definition: ContentHandler.php:53
MediaWiki\Storage\RevisionSlotsUpdate\getModifiedSlot
getModifiedSlot( $role)
Returns the SlotRecord associated with the given role, if the slot with that role was modified (and n...
Definition: RevisionSlotsUpdate.php:217
ContentHandler\getForModelID
static getForModelID( $modelId)
Returns the ContentHandler singleton for the given model ID.
Definition: ContentHandler.php:252
MediaWiki\Storage\RevisionSlotsUpdate\isModifiedSlot
isModifiedSlot( $role)
Returns whether getModifiedSlot() will return a SlotRecord for the given role.
Definition: RevisionSlotsUpdate.php:234
MediaWiki\Storage\DerivedPageDataUpdater\$contLang
Language $contLang
Definition: DerivedPageDataUpdater.php:121
WikiPage\onArticleCreate
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:3375
MediaWiki\Storage\DerivedPageDataUpdater\$parserCache
ParserCache $parserCache
Definition: DerivedPageDataUpdater.php:111
Revision\RevisionRecord
Page revision base class.
Definition: RevisionRecord.php:45
WikiPage\loadPageData
loadPageData( $from='fromdb')
Load the object from a given source by title.
Definition: WikiPage.php:485
MediaWiki\Storage\DerivedPageDataUpdater\pageExisted
pageExisted()
Determines whether the page being edited already existed.
Definition: DerivedPageDataUpdater.php:449
ParserOutput
Definition: ParserOutput.php:25
MediaWiki\Storage\DerivedPageDataUpdater\getParentRevision
getParentRevision()
Returns the parent revision of the new revision wrapped by this update.
Definition: DerivedPageDataUpdater.php:464
MediaWiki\Storage\DerivedPageDataUpdater\getRevision
getRevision()
Returns the update's target revision - that is, the revision that will be the current revision after ...
Definition: DerivedPageDataUpdater.php:884
MediaWiki\Storage\DerivedPageDataUpdater\$articleCountMethod
string $articleCountMethod
see $wgArticleCountMethod
Definition: DerivedPageDataUpdater.php:141
MediaWiki\Storage\DerivedPageDataUpdater\prepareContent
prepareContent(User $user, RevisionSlotsUpdate $slotsUpdate, $useStash=true)
Prepare updates based on an update which has not yet been saved.
Definition: DerivedPageDataUpdater.php:718
MediaWiki\Storage\DerivedPageDataUpdater\getSlotParserOutput
getSlotParserOutput( $role, $generateHtml=true)
Definition: DerivedPageDataUpdater.php:1264
MediaWiki\Storage\DerivedPageDataUpdater\$user
UserIdentity null $user
Definition: DerivedPageDataUpdater.php:101
RecentChangesUpdateJob
Job for pruning recent changes.
Definition: RecentChangesUpdateJob.php:29
MediaWiki\Storage\DerivedPageDataUpdater\prepareUpdate
prepareUpdate(RevisionRecord $revision, array $options=[])
Prepare derived data updates targeting the given Revision.
Definition: DerivedPageDataUpdater.php:1071
MediaWiki\Storage\DerivedPageDataUpdater\setArticleCountMethod
setArticleCountMethod( $articleCountMethod)
Definition: DerivedPageDataUpdater.php:414
captcha-old.count
count
Definition: captcha-old.py:249
Revision\RevisionStore
Service for looking up page revisions.
Definition: RevisionStore.php:76
MediaWiki\Storage\DerivedPageDataUpdater\getSlots
getSlots()
Returns the slots of the target revision, after PST.
Definition: DerivedPageDataUpdater.php:976
MediaWiki\Storage\DerivedPageDataUpdater\$loadbalancerFactory
LBFactory $loadbalancerFactory
Definition: DerivedPageDataUpdater.php:136
MediaWiki\Storage\DerivedPageDataUpdater\$parentRevision
RevisionRecord null $parentRevision
Definition: DerivedPageDataUpdater.php:199
MediaWiki\Storage\DerivedPageDataUpdater\setRcWatchCategoryMembership
setRcWatchCategoryMembership( $rcWatchCategoryMembership)
Definition: DerivedPageDataUpdater.php:422
MediaWiki\Storage\DerivedPageDataUpdater\getPreparedEdit
getPreparedEdit()
Definition: DerivedPageDataUpdater.php:1238
Revision\MutableRevisionRecord\newFromParentRevision
static newFromParentRevision(RevisionRecord $parent)
Returns an incomplete MutableRevisionRecord which uses $parent as its parent revision,...
Definition: MutableRevisionRecord.php:52
ResourceLoaderWikiModule
Abstraction for ResourceLoader modules which pull from wiki pages.
Definition: ResourceLoaderWikiModule.php:51
Revision\RevisionRecord\getTimestamp
getTimestamp()
MCR migration note: this replaces Revision::getTimestamp.
Definition: RevisionRecord.php:436
DeferredUpdates\addUpdate
static addUpdate(DeferrableUpdate $update, $stage=self::POSTSEND)
Add an update to the deferred list to be run later by execute()
Definition: DeferredUpdates.php:79
WikiPage
Class representing a MediaWiki article and history.
Definition: WikiPage.php:45
MediaWiki\Storage\DerivedPageDataUpdater\$revisionRenderer
RevisionRenderer $revisionRenderer
Definition: DerivedPageDataUpdater.php:214
LinksUpdate
Class the manages updates of *_link tables as well as similar extension-managed tables.
Definition: LinksUpdate.php:35
MediaWiki\Storage\DerivedPageDataUpdater\getPageId
getPageId()
Definition: DerivedPageDataUpdater.php:557
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:585
Revision\RevisionRecord\getSlot
getSlot( $role, $audience=self::FOR_PUBLIC, User $user=null)
Returns meta-data for the given slot.
Definition: RevisionRecord.php:191
WikiPage\getRevision
getRevision()
Get the latest revision.
Definition: WikiPage.php:783
MediaWiki\Storage\DerivedPageDataUpdater\$options
$options
Stores (most of) the $options parameter of prepareUpdate().
Definition: DerivedPageDataUpdater.php:152
MediaWiki\Storage\DerivedPageDataUpdater\$renderedRevision
RenderedRevision $renderedRevision
Definition: DerivedPageDataUpdater.php:209
User\newFromIdentity
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:652
User
User
Definition: All_system_messages.txt:425
MediaWiki\Storage\DerivedPageDataUpdater\$slotsUpdate
RevisionSlotsUpdate null $slotsUpdate
Definition: DerivedPageDataUpdater.php:194
IDBAccessObject
Interface for database access objects.
Definition: IDBAccessObject.php:55
WikiPage\onArticleEdit
static onArticleEdit(Title $title, Revision $revision=null, $slotsChanged=null)
Purge caches on page update etc.
Definition: WikiPage.php:3462
MediaWiki\Storage\DerivedPageDataUpdater\isRedirect
isRedirect()
Definition: DerivedPageDataUpdater.php:674
MediaWiki\Storage\DerivedPageDataUpdater\getCanonicalParserOptions
getCanonicalParserOptions()
Definition: DerivedPageDataUpdater.php:1281
MediaWiki\Storage\DerivedPageDataUpdater\doUpdates
doUpdates()
Do standard updates after page edit, purge, or import.
Definition: DerivedPageDataUpdater.php:1397
MediaWiki\User\UserIdentity
Interface for objects representing user identity.
Definition: UserIdentity.php:32
DataUpdate
Abstract base class for update jobs that do something with some secondary data extracted from article...
Definition: DataUpdate.php:28
php
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
MediaWiki\Storage\DerivedPageDataUpdater\$revisionStore
RevisionStore $revisionStore
Definition: DerivedPageDataUpdater.php:116
MediaWiki\Storage\DerivedPageDataUpdater\assertHasRevision
assertHasRevision( $method)
Definition: DerivedPageDataUpdater.php:915
Revision
Definition: Revision.php:40
MediaWiki\Storage\DerivedPageDataUpdater\$slotRoleRegistry
SlotRoleRegistry $slotRoleRegistry
Definition: DerivedPageDataUpdater.php:217
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:925
user
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
Definition: distributors.txt:9
MediaWiki\Storage\DerivedPageDataUpdater\isReusableFor
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...
Definition: DerivedPageDataUpdater.php:351
ParserOptions\newFromUserAndLang
static newFromUserAndLang(User $user, Language $lang)
Get a ParserOptions object from a given user and language.
Definition: ParserOptions.php:1031
$handler
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
Revision\RevisionRecord\getUser
getUser( $audience=self::FOR_PUBLIC, User $user=null)
Fetch revision's author's user identity, if it's available to the specified audience.
Definition: RevisionRecord.php:365
MediaWiki\Storage\DerivedPageDataUpdater\getRevisionSlotsUpdate
getRevisionSlotsUpdate()
Returns the RevisionSlotsUpdate for this updater.
Definition: DerivedPageDataUpdater.php:986
DeferredUpdates
Class for managing the deferred updates.
Definition: DeferredUpdates.php:56
MediaWiki\Storage\RevisionSlotsUpdate\hasSameUpdates
hasSameUpdates(RevisionSlotsUpdate $other)
Returns true if $other represents the same update - that is, if all methods defined by RevisionSlotsU...
Definition: RevisionSlotsUpdate.php:261
MediaWiki\Storage\DerivedPageDataUpdater\getContentHandler
getContentHandler( $role)
Definition: DerivedPageDataUpdater.php:616
Revision\RevisionRecord\isMinor
isMinor()
MCR migration note: this replaces Revision::isMinor.
Definition: RevisionRecord.php:403
MediaWiki\Storage\DerivedPageDataUpdater\__construct
__construct(WikiPage $wikiPage, RevisionStore $revisionStore, RevisionRenderer $revisionRenderer, SlotRoleRegistry $slotRoleRegistry, ParserCache $parserCache, JobQueueGroup $jobQueueGroup, MessageCache $messageCache, Language $contLang, LBFactory $loadbalancerFactory)
Definition: DerivedPageDataUpdater.php:272
MediaWiki\Storage\DerivedPageDataUpdater\assertPrepared
assertPrepared( $method)
Definition: DerivedPageDataUpdater.php:907
User\isIP
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:967
MediaWiki\Storage\DerivedPageDataUpdater\getContentModel
getContentModel( $role)
Returns the content model of the given slot.
Definition: DerivedPageDataUpdater.php:608
use
as see the revision history and available at free of to any person obtaining a copy of this software and associated documentation to deal in the Software without including without limitation the rights to use
Definition: MIT-LICENSE.txt:10
DeferredUpdates\POSTSEND
const POSTSEND
Definition: DeferredUpdates.php:64
Revision\RevisionRecord\RAW
const RAW
Definition: RevisionRecord.php:59
$parser
see documentation in includes Linker php for Linker::makeImageLink or false for current used if you return false $parser
Definition: hooks.txt:1802
MediaWiki\User\UserIdentity\getName
getName()
MediaWiki\Storage\DerivedPageDataUpdater\wasRedirect
wasRedirect()
Whether the page was a redirect before the edit.
Definition: DerivedPageDataUpdater.php:952
SiteStatsUpdate\factory
static factory(array $deltas)
Definition: SiteStatsUpdate.php:66
$output
$output
Definition: SyntaxHighlight.php:334
Title\equals
equals(Title $title)
Compare with another title.
Definition: Title.php:4006
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:1941
SiteStatsUpdate
Class for handling updates to the site_stats table.
Definition: SiteStatsUpdate.php:27
MediaWiki\Storage\DerivedPageDataUpdater\isCreation
isCreation()
Whether the edit creates the page.
Definition: DerivedPageDataUpdater.php:928
array
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))
MediaWiki\Storage\DerivedPageDataUpdater\assertTransition
assertTransition( $newStage)
Asserts that a transition to the given stage is possible, without performing it.
Definition: DerivedPageDataUpdater.php:326
wfDebug
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
Definition: GlobalFunctions.php:949
Revision\RevisionRecord\getPageId
getPageId()
Get the page ID.
Definition: RevisionRecord.php:325
WikiPage\isCountable
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
MediaWiki\Storage\DerivedPageDataUpdater\useMaster
useMaster()
Definition: DerivedPageDataUpdater.php:621
Revision\SlotRecord\newUnsaved
static newUnsaved( $role, Content $content)
Constructs a new Slot from a Content object for a new revision.
Definition: SlotRecord.php:129
CategoryMembershipChangeJob
Job to add recent change entries mentioning category membership changes.
Definition: CategoryMembershipChangeJob.php:38
null
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
Revision\RevisionRecord\getId
getId()
Get revision ID.
Definition: RevisionRecord.php:273
MediaWiki\Storage\DerivedPageDataUpdater\getWikiId
getWikiId()
Definition: DerivedPageDataUpdater.php:335
Revision\RevisionRecord\getParentId
getParentId()
Get parent revision ID (the original previous page revision).
Definition: RevisionRecord.php:289
MediaWiki\Storage\RevisionSlotsUpdate
Value object representing a modification of revision slots.
Definition: RevisionSlotsUpdate.php:36
Revision\RevisionRenderer
The RevisionRenderer service provides access to rendered output for revisions.
Definition: RevisionRenderer.php:45
wfWikiID
wfWikiID()
Get an ASCII string identifying this wiki This is used as a prefix in memcached keys.
Definition: GlobalFunctions.php:2602
NS_USER_TALK
const NS_USER_TALK
Definition: Defines.php:67
MediaWiki\Storage\DerivedPageDataUpdater\$transitions
static array[] $transitions
Transition table for managing the life cycle of DerivedPageDateUpdater instances.
Definition: DerivedPageDataUpdater.php:239
MediaWiki\Storage\DerivedPageDataUpdater\isContentDeleted
isContentDeleted()
Whether the content is deleted and thus not visible to the public.
Definition: DerivedPageDataUpdater.php:567
Revision\MutableRevisionRecord
Mutable RevisionRecord implementation, for building new revision entries programmatically.
Definition: MutableRevisionRecord.php:41
MediaWiki\Storage\DerivedPageDataUpdater\getWikiPage
getWikiPage()
Definition: DerivedPageDataUpdater.php:437
MediaWiki\Storage\DerivedPageDataUpdater\isCountable
isCountable()
Definition: DerivedPageDataUpdater.php:629
ResourceLoaderWikiModule\invalidateModuleCache
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...
Definition: ResourceLoaderWikiModule.php:535
MediaWiki\Storage\DerivedPageDataUpdater\isContentPrepared
isContentPrepared()
Whether prepareUpdate() or prepareContent() have been called on this instance.
Definition: DerivedPageDataUpdater.php:539
LinksDeletionUpdate
Update object handling the cleanup of links tables after a page was deleted.
Definition: LinksDeletionUpdate.php:28
Revision\RevisionRecord\getComment
getComment( $audience=self::FOR_PUBLIC, User $user=null)
Fetch revision comment, if it's available to the specified audience.
Definition: RevisionRecord.php:390
$linksUpdate
either a unescaped string or a HtmlArmor object after in associative array form externallinks $linksUpdate
Definition: hooks.txt:2064
MediaWiki\Storage\DerivedPageDataUpdater\getRemovedSlotRoles
getRemovedSlotRoles()
Returns the role names of the slots removed by the new revision.
Definition: DerivedPageDataUpdater.php:1024
MediaWiki\Storage\DerivedPageDataUpdater\$pageState
array $pageState
The state of the relevant row in page table before the edit.
Definition: DerivedPageDataUpdater.php:189
MediaWiki\Storage
Definition: BlobAccessException.php:23
Revision\SlotRecord\MAIN
const MAIN
Definition: SlotRecord.php:41
MediaWiki\Storage\DerivedPageDataUpdater\$messageCache
MessageCache $messageCache
Definition: DerivedPageDataUpdater.php:131
MediaWiki\Storage\DerivedPageDataUpdater\doTransition
doTransition( $newStage)
Transition function for managing the life cycle of this instances.
Definition: DerivedPageDataUpdater.php:308
Revision\RevisionRecord\getSlots
getSlots()
Returns the slots defined for this revision.
Definition: RevisionRecord.php:227
MediaWiki\Storage\DerivedPageDataUpdater\getModifiedSlotRoles
getModifiedSlotRoles()
Returns the role names of the slots modified by the new revision, not including removed roles.
Definition: DerivedPageDataUpdater.php:1015
Content
Base interface for content objects.
Definition: Content.php:34
MediaWiki\Storage\DerivedPageDataUpdater\$jobQueueGroup
JobQueueGroup $jobQueueGroup
Definition: DerivedPageDataUpdater.php:126
Revision\RevisionRecord\getVisibility
getVisibility()
Get the deletion bitfield of the revision.
Definition: RevisionRecord.php:425
Title
Represents a title within MediaWiki.
Definition: Title.php:40
MediaWiki\Storage\RevisionSlotsUpdate\newFromRevisionSlots
static newFromRevisionSlots(RevisionSlots $newSlots, RevisionSlots $parentSlots=null)
Constructs a RevisionSlotsUpdate representing the update that turned $parentSlots into $newSlots.
Definition: RevisionSlotsUpdate.php:58
MediaWiki\Storage\DerivedPageDataUpdater\getRawContent
getRawContent( $role)
Returns the content of the given slot, with no audience checks.
Definition: DerivedPageDataUpdater.php:598
DeferredUpdates\PRESEND
const PRESEND
Definition: DeferredUpdates.php:63
MediaWiki\Storage\RevisionSlotsUpdate\getRemovedRoles
getRemovedRoles()
Returns a list of removed slot roles, that is, roles removed by calling removeSlot(),...
Definition: RevisionSlotsUpdate.php:147
MediaWiki\Storage\DerivedPageDataUpdater\getTitle
getTitle()
Definition: DerivedPageDataUpdater.php:429
MediaWiki\Storage\DerivedPageDataUpdater\getSecondaryDataUpdates
getSecondaryDataUpdates( $recursive=false)
Definition: DerivedPageDataUpdater.php:1290
Wikimedia\Rdbms\LBFactory
An interface for generating database load balancers.
Definition: LBFactory.php:39
MediaWiki\Storage\DerivedPageDataUpdater\doParserCacheUpdate
doParserCacheUpdate()
Definition: DerivedPageDataUpdater.php:1605
Revision\RevisionRecord\DELETED_TEXT
const DELETED_TEXT
Definition: RevisionRecord.php:48
$rev
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:1769
as
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
MediaWiki\Storage\DerivedPageDataUpdater\getRenderedRevision
getRenderedRevision()
Definition: DerivedPageDataUpdater.php:892
Revision\RevisionSlots
Value object representing the set of slots belonging to a revision.
Definition: RevisionSlots.php:35
MediaWiki\Storage\DerivedPageDataUpdater\$rcWatchCategoryMembership
boolean $rcWatchCategoryMembership
see $wgRCWatchCategoryMembership
Definition: DerivedPageDataUpdater.php:146
ParserCache
Definition: ParserCache.php:30
RecentChangesUpdateJob\newPurgeJob
static newPurgeJob()
Definition: RecentChangesUpdateJob.php:44
MediaWiki\Storage\DerivedPageDataUpdater\$revision
RevisionRecord null $revision
Definition: DerivedPageDataUpdater.php:204
revision
In both all secondary updates will be triggered handle like object that caches derived data representing a revision
Definition: pageupdater.txt:78
$content
$content
Definition: pageupdater.txt:72
MediaWiki\Storage\DerivedPageDataUpdater\getRawSlot
getRawSlot( $role)
Returns the slot, modified or inherited, after PST, with no audience checks applied.
Definition: DerivedPageDataUpdater.php:586
Revision\SlotRoleRegistry
A registry service for SlotRoleHandlers, used to define which slot roles are available on which page.
Definition: SlotRoleRegistry.php:48
Revision\RevisionRecord\hasSameContent
hasSameContent(RevisionRecord $rec)
Definition: RevisionRecord.php:127
MediaWiki\Edit\PreparedEdit
Represents information returned by WikiPage::prepareContentForEdit()
Definition: PreparedEdit.php:34
MediaWiki\Storage\DerivedPageDataUpdater\doSecondaryDataUpdates
doSecondaryDataUpdates(array $options=[])
Do secondary data updates (such as updating link tables).
Definition: DerivedPageDataUpdater.php:1552
Revision\RenderedRevision
RenderedRevision represents the rendered representation of a revision.
Definition: RenderedRevision.php:44
CategoryMembershipChangeJob\newSpec
static newSpec(Title $title, $revisionTimestamp)
Definition: CategoryMembershipChangeJob.php:54
MediaWiki\Storage\DerivedPageDataUpdater\isChange
isChange()
Whether the edit created, or should create, a new revision (that is, it's not a null-edit).
Definition: DerivedPageDataUpdater.php:942
NS_MEDIAWIKI
const NS_MEDIAWIKI
Definition: Defines.php:72
MediaWiki\Storage\DerivedPageDataUpdater\assertHasPageState
assertHasPageState( $method)
Definition: DerivedPageDataUpdater.php:898
DeferrableUpdate
Interface that deferrable updates should implement.
Definition: DeferrableUpdate.php:9
MediaWiki\Storage\DerivedPageDataUpdater\getTouchedSlotRoles
getTouchedSlotRoles()
Returns the role names of the slots touched by the new revision, including removed roles.
Definition: DerivedPageDataUpdater.php:1005
MediaWiki\Storage\DerivedPageDataUpdater\$stage
string $stage
A stage identifier for managing the life cycle of this instance.
Definition: DerivedPageDataUpdater.php:227
WikiPage\isRedirect
isRedirect()
Tests if the article content represents a redirect.
Definition: WikiPage.php:630
options
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
MediaWiki\Storage\DerivedPageDataUpdater
A handle for managing updates for derived page data on edit, import, purge, etc.
Definition: DerivedPageDataUpdater.php:96
MediaWiki\Storage\DerivedPageDataUpdater\$wikiPage
WikiPage $wikiPage
Definition: DerivedPageDataUpdater.php:106
MessageCache
Cache of messages that are defined by MediaWiki namespace pages or by hooks.
Definition: MessageCache.php:40
User
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition: User.php:48
Hooks\run
static run( $event, array $args=[], $deprecatedVersion=null)
Call hook functions defined in Hooks::register and $wgHooks.
Definition: Hooks.php:200
MediaWiki\Storage\DerivedPageDataUpdater\revisionIsRedirect
revisionIsRedirect(RevisionRecord $rev)
Definition: DerivedPageDataUpdater.php:687
User\getName
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2452
ApiStashEdit
Prepare an edit in shared cache so that it can be reused on edit.
Definition: ApiStashEdit.php:38
ApiStashEdit\checkCache
static checkCache(Title $title, Content $content, User $user)
Check that a prepared edit is in cache and still up-to-date.
Definition: ApiStashEdit.php:264
Language
Internationalisation code.
Definition: Language.php:36
MediaWiki\Storage\DerivedPageDataUpdater\grabCurrentRevision
grabCurrentRevision()
Returns the revision that was the page's current revision when grabCurrentRevision() was first called...
Definition: DerivedPageDataUpdater.php:506
MediaWiki\Storage\DerivedPageDataUpdater\isUpdatePrepared
isUpdatePrepared()
Whether prepareUpdate() has been called on this instance.
Definition: DerivedPageDataUpdater.php:550
Hooks
Hooks class.
Definition: Hooks.php:34
Revision\SlotRecord
Value object representing a content slot associated with a page revision.
Definition: SlotRecord.php:39
JobQueueGroup
Class to handle enqueueing of background jobs.
Definition: JobQueueGroup.php:30
ParserCache
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