MediaWiki  1.32.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;
56 use SearchUpdate;
58 use Title;
59 use User;
60 use Wikimedia\Assert\Assert;
63 
96 
100  private $user = null;
101 
105  private $wikiPage;
106 
110  private $parserCache;
111 
115  private $revisionStore;
116 
120  private $contLang;
121 
125  private $jobQueueGroup;
126 
130  private $messageCache;
131 
136 
141 
145  private $rcWatchCategoryMembership = false;
146 
151  private $options = [
152  'changed' => true,
153  // newrev is true if prepareUpdate is handling the creation of a new revision,
154  // as opposed to a null edit or a forced update.
155  'newrev' => false,
156  'created' => false,
157  'moved' => false,
158  'restored' => false,
159  'oldrevision' => null,
160  'oldcountable' => null,
161  'oldredirect' => null,
162  'triggeringUser' => null,
163  // causeAction/causeAgent default to 'unknown' but that's handled where it's read,
164  // to make the life of prepareUpdate() callers easier.
165  'causeAction' => null,
166  'causeAgent' => null,
167  ];
168 
188  private $pageState = null;
189 
193  private $slotsUpdate = null;
194 
198  private $parentRevision = null;
199 
203  private $revision = null;
204 
208  private $renderedRevision = null;
209 
214 
223  private $stage = 'new';
224 
235  private static $transitions = [
236  'new' => [
237  'new' => true,
238  'knows-current' => true,
239  'has-content' => true,
240  'has-revision' => true,
241  ],
242  'knows-current' => [
243  'knows-current' => true,
244  'has-content' => true,
245  'has-revision' => true,
246  ],
247  'has-content' => [
248  'has-content' => true,
249  'has-revision' => true,
250  ],
251  'has-revision' => [
252  'has-revision' => true,
253  'done' => true,
254  ],
255  ];
256 
267  public function __construct(
276  ) {
277  $this->wikiPage = $wikiPage;
278 
279  $this->parserCache = $parserCache;
280  $this->revisionStore = $revisionStore;
281  $this->revisionRenderer = $revisionRenderer;
282  $this->jobQueueGroup = $jobQueueGroup;
283  $this->messageCache = $messageCache;
284  $this->contLang = $contLang;
285  // XXX only needed for waiting for slaves to catch up; there should be a narrower
286  // interface for that.
287  $this->loadbalancerFactory = $loadbalancerFactory;
288  }
289 
301  private function doTransition( $newStage ) {
302  $this->assertTransition( $newStage );
303 
304  $oldStage = $this->stage;
305  $this->stage = $newStage;
306 
307  return $oldStage;
308  }
309 
319  private function assertTransition( $newStage ) {
320  if ( empty( self::$transitions[$this->stage][$newStage] ) ) {
321  throw new LogicException( "Cannot transition from {$this->stage} to $newStage" );
322  }
323  }
324 
328  private function getWikiId() {
329  // TODO: get from RevisionStore
330  return false;
331  }
332 
344  public function isReusableFor(
345  UserIdentity $user = null,
346  RevisionRecord $revision = null,
348  $parentId = null
349  ) {
350  if ( $revision
351  && $parentId
352  && $revision->getParentId() !== $parentId
353  ) {
354  throw new InvalidArgumentException( '$parentId should match the parent of $revision' );
355  }
356 
357  if ( $revision
358  && $user
359  && $revision->getUser( RevisionRecord::RAW )->getName() !== $user->getName()
360  ) {
361  throw new InvalidArgumentException( '$user should match the author of $revision' );
362  }
363 
364  if ( $user && $this->user && $user->getName() !== $this->user->getName() ) {
365  return false;
366  }
367 
368  if ( $revision && $this->revision && $this->revision->getId()
369  && $this->revision->getId() !== $revision->getId()
370  ) {
371  return false;
372  }
373 
374  if ( $revision && !$user ) {
376  }
377 
378  if ( $this->pageState
379  && $revision
380  && $revision->getParentId() !== null
381  && $this->pageState['oldId'] !== $revision->getParentId()
382  ) {
383  return false;
384  }
385 
386  if ( $this->pageState
387  && $parentId !== null
388  && $this->pageState['oldId'] !== $parentId
389  ) {
390  return false;
391  }
392 
393  if ( $this->revision
394  && $user
395  && $this->revision->getUser( RevisionRecord::RAW )
396  && $this->revision->getUser( RevisionRecord::RAW )->getName() !== $user->getName()
397  ) {
398  return false;
399  }
400 
401  if ( $revision
402  && $this->user
403  && $this->revision->getUser( RevisionRecord::RAW )
404  && $revision->getUser( RevisionRecord::RAW )->getName() !== $this->user->getName()
405  ) {
406  return false;
407  }
408 
409  // NOTE: this check is the primary reason for having the $this->slotsUpdate field!
410  if ( $this->slotsUpdate
411  && $slotsUpdate
412  && !$this->slotsUpdate->hasSameUpdates( $slotsUpdate )
413  ) {
414  return false;
415  }
416 
417  if ( $revision
418  && $this->revision
419  && !$this->revision->getSlots()->hasSameContent( $revision->getSlots() )
420  ) {
421  return false;
422  }
423 
424  return true;
425  }
426 
432  $this->articleCountMethod = $articleCountMethod;
433  }
434 
440  $this->rcWatchCategoryMembership = $rcWatchCategoryMembership;
441  }
442 
446  private function getTitle() {
447  // NOTE: eventually, we won't get a WikiPage passed into the constructor any more
448  return $this->wikiPage->getTitle();
449  }
450 
454  private function getWikiPage() {
455  // NOTE: eventually, we won't get a WikiPage passed into the constructor any more
456  return $this->wikiPage;
457  }
458 
466  public function pageExisted() {
467  $this->assertHasPageState( __METHOD__ );
468 
469  return $this->pageState['oldId'] > 0;
470  }
471 
481  private function getParentRevision() {
482  $this->assertPrepared( __METHOD__ );
483 
484  if ( $this->parentRevision ) {
485  return $this->parentRevision;
486  }
487 
488  if ( !$this->pageState['oldId'] ) {
489  // If there was no current revision, there is no parent revision,
490  // since the page didn't exist.
491  return null;
492  }
493 
494  $oldId = $this->revision->getParentId();
495  $flags = $this->useMaster() ? RevisionStore::READ_LATEST : 0;
496  $this->parentRevision = $oldId
497  ? $this->revisionStore->getRevisionById( $oldId, $flags )
498  : null;
499 
500  return $this->parentRevision;
501  }
502 
523  public function grabCurrentRevision() {
524  if ( $this->pageState ) {
525  return $this->pageState['oldRevision'];
526  }
527 
528  $this->assertTransition( 'knows-current' );
529 
530  // NOTE: eventually, we won't get a WikiPage passed into the constructor any more
531  $wikiPage = $this->getWikiPage();
532 
533  // Do not call WikiPage::clear(), since the caller may already have caused page data
534  // to be loaded with SELECT FOR UPDATE. Just assert it's loaded now.
535  $wikiPage->loadPageData( self::READ_LATEST );
537  $current = $rev ? $rev->getRevisionRecord() : null;
538 
539  $this->pageState = [
540  'oldRevision' => $current,
541  'oldId' => $rev ? $rev->getId() : 0,
542  'oldIsRedirect' => $wikiPage->isRedirect(), // NOTE: uses page table
543  'oldCountable' => $wikiPage->isCountable(), // NOTE: uses pagelinks table
544  ];
545 
546  $this->doTransition( 'knows-current' );
547 
548  return $this->pageState['oldRevision'];
549  }
550 
556  public function isContentPrepared() {
557  return $this->revision !== null;
558  }
559 
567  public function isUpdatePrepared() {
568  return $this->revision !== null && $this->revision->getId() !== null;
569  }
570 
574  private function getPageId() {
575  // NOTE: eventually, we won't get a WikiPage passed into the constructor any more
576  return $this->wikiPage->getId();
577  }
578 
584  public function isContentDeleted() {
585  if ( $this->revision ) {
586  // XXX: if that revision is the current revision, this should be skipped
587  return $this->revision->isDeleted( RevisionRecord::DELETED_TEXT );
588  } else {
589  // If the content has not been saved yet, it cannot have been deleted yet.
590  return false;
591  }
592  }
593 
603  public function getRawSlot( $role ) {
604  return $this->getSlots()->getSlot( $role );
605  }
606 
615  public function getRawContent( $role ) {
616  return $this->getRawSlot( $role )->getContent();
617  }
618 
625  private function getContentModel( $role ) {
626  return $this->getRawSlot( $role )->getModel();
627  }
628 
633  private function getContentHandler( $role ) {
634  // TODO: inject something like a ContentHandlerRegistry
635  return ContentHandler::getForModelID( $this->getContentModel( $role ) );
636  }
637 
638  private function useMaster() {
639  // TODO: can we just set a flag to true in prepareContent()?
640  return $this->wikiPage->wasLoadedFrom( self::READ_LATEST );
641  }
642 
646  public function isCountable() {
647  // NOTE: Keep in sync with WikiPage::isCountable.
648 
649  if ( !$this->getTitle()->isContentPage() ) {
650  return false;
651  }
652 
653  if ( $this->isContentDeleted() ) {
654  // This should be irrelevant: countability only applies to the current revision,
655  // and the current revision is never suppressed.
656  return false;
657  }
658 
659  if ( $this->isRedirect() ) {
660  return false;
661  }
662 
663  $hasLinks = null;
664 
665  if ( $this->articleCountMethod === 'link' ) {
666  $hasLinks = (bool)count( $this->getCanonicalParserOutput()->getLinks() );
667  }
668 
669  // TODO: MCR: ask all slots if they have links [SlotHandler/PageTypeHandler]
670  $mainContent = $this->getRawContent( SlotRecord::MAIN );
671  return $mainContent->isCountable( $hasLinks );
672  }
673 
677  public function isRedirect() {
678  // NOTE: main slot determines redirect status
679  $mainContent = $this->getRawContent( SlotRecord::MAIN );
680 
681  return $mainContent->isRedirect();
682  }
683 
689  private function revisionIsRedirect( RevisionRecord $rev ) {
690  // NOTE: main slot determines redirect status
691  $mainContent = $rev->getContent( SlotRecord::MAIN, RevisionRecord::RAW );
692 
693  return $mainContent->isRedirect();
694  }
695 
720  public function prepareContent(
721  User $user,
723  $useStash = true
724  ) {
725  if ( $this->slotsUpdate ) {
726  if ( !$this->user ) {
727  throw new LogicException(
728  'Unexpected state: $this->slotsUpdate was initialized, '
729  . 'but $this->user was not.'
730  );
731  }
732 
733  if ( $this->user->getName() !== $user->getName() ) {
734  throw new LogicException( 'Can\'t call prepareContent() again for different user! '
735  . 'Expected ' . $this->user->getName() . ', got ' . $user->getName()
736  );
737  }
738 
739  if ( !$this->slotsUpdate->hasSameUpdates( $slotsUpdate ) ) {
740  throw new LogicException(
741  'Can\'t call prepareContent() again with different slot content!'
742  );
743  }
744 
745  return; // prepareContent() already done, nothing to do
746  }
747 
748  $this->assertTransition( 'has-content' );
749 
750  $wikiPage = $this->getWikiPage(); // TODO: use only for legacy hooks!
751  $title = $this->getTitle();
752 
754 
755  $this->slotsOutput = [];
756  $this->canonicalParserOutput = null;
757 
758  // The edit may have already been prepared via api.php?action=stashedit
759  $stashedEdit = false;
760 
761  // TODO: MCR: allow output for all slots to be stashed.
762  if ( $useStash && $slotsUpdate->isModifiedSlot( SlotRecord::MAIN ) ) {
763  $mainContent = $slotsUpdate->getModifiedSlot( SlotRecord::MAIN )->getContent();
764  $legacyUser = User::newFromIdentity( $user );
765  $stashedEdit = ApiStashEdit::checkCache( $title, $mainContent, $legacyUser );
766  }
767 
768  if ( $stashedEdit ) {
770  $output = $stashedEdit->output;
771 
772  // TODO: this should happen when stashing the ParserOutput, not now!
773  $output->setCacheTime( $stashedEdit->timestamp );
774 
775  // TODO: MCR: allow output for all slots to be stashed.
776  $this->canonicalParserOutput = $output;
777  }
778 
779  $userPopts = ParserOptions::newFromUserAndLang( $user, $this->contLang );
780  Hooks::run( 'ArticlePrepareTextForEdit', [ $wikiPage, $userPopts ] );
781 
782  $this->user = $user;
783  $this->slotsUpdate = $slotsUpdate;
784 
785  if ( $parentRevision ) {
787  } else {
788  $this->revision = new MutableRevisionRecord( $title );
789  }
790 
791  // NOTE: user and timestamp must be set, so they can be used for
792  // {{subst:REVISIONUSER}} and {{subst:REVISIONTIMESTAMP}} in PST!
793  $this->revision->setTimestamp( wfTimestampNow() );
794  $this->revision->setUser( $user );
795 
796  // Set up ParserOptions to operate on the new revision
797  $oldCallback = $userPopts->getCurrentRevisionCallback();
798  $userPopts->setCurrentRevisionCallback(
799  function ( Title $parserTitle, $parser = false ) use ( $title, $oldCallback ) {
800  if ( $parserTitle->equals( $title ) ) {
801  $legacyRevision = new Revision( $this->revision );
802  return $legacyRevision;
803  } else {
804  return call_user_func( $oldCallback, $parserTitle, $parser );
805  }
806  }
807  );
808 
809  $pstContentSlots = $this->revision->getSlots();
810 
811  foreach ( $slotsUpdate->getModifiedRoles() as $role ) {
812  $slot = $slotsUpdate->getModifiedSlot( $role );
813 
814  if ( $slot->isInherited() ) {
815  // No PST for inherited slots! Note that "modified" slots may still be inherited
816  // from an earlier version, e.g. for rollbacks.
817  $pstSlot = $slot;
818  } elseif ( $role === SlotRecord::MAIN && $stashedEdit ) {
819  // TODO: MCR: allow PST content for all slots to be stashed.
820  $pstSlot = SlotRecord::newUnsaved( $role, $stashedEdit->pstContent );
821  } else {
822  $content = $slot->getContent();
823  $pstContent = $content->preSaveTransform( $title, $this->user, $userPopts );
824  $pstSlot = SlotRecord::newUnsaved( $role, $pstContent );
825  }
826 
827  $pstContentSlots->setSlot( $pstSlot );
828  }
829 
830  foreach ( $slotsUpdate->getRemovedRoles() as $role ) {
831  $pstContentSlots->removeSlot( $role );
832  }
833 
834  $this->options['created'] = ( $parentRevision === null );
835  $this->options['changed'] = ( $parentRevision === null
836  || !$pstContentSlots->hasSameContent( $parentRevision->getSlots() ) );
837 
838  $this->doTransition( 'has-content' );
839 
840  if ( !$this->options['changed'] ) {
841  // null-edit!
842 
843  // TODO: move this into MutableRevisionRecord
844  // TODO: This needs to behave differently for a forced dummy edit!
845  $this->revision->setId( $parentRevision->getId() );
846  $this->revision->setTimestamp( $parentRevision->getTimestamp() );
847  $this->revision->setPageId( $parentRevision->getPageId() );
848  $this->revision->setParentId( $parentRevision->getParentId() );
849  $this->revision->setUser( $parentRevision->getUser( RevisionRecord::RAW ) );
850  $this->revision->setComment( $parentRevision->getComment( RevisionRecord::RAW ) );
851  $this->revision->setMinorEdit( $parentRevision->isMinor() );
852  $this->revision->setVisibility( $parentRevision->getVisibility() );
853 
854  // prepareUpdate() is redundant for null-edits
855  $this->doTransition( 'has-revision' );
856  } else {
857  $this->parentRevision = $parentRevision;
858  }
859  }
860 
876  public function getRevision() {
877  $this->assertPrepared( __METHOD__ );
878  return $this->revision;
879  }
880 
884  public function getRenderedRevision() {
885  if ( !$this->renderedRevision ) {
886  $this->assertPrepared( __METHOD__ );
887 
888  // NOTE: we want a canonical rendering, so don't pass $this->user or ParserOptions
889  // NOTE: the revision is either new or current, so we can bypass audience checks.
890  $this->renderedRevision = $this->revisionRenderer->getRenderedRevision(
891  $this->revision,
892  null,
893  null,
894  [ 'use-master' => $this->useMaster(), 'audience' => RevisionRecord::RAW ]
895  );
896  }
897 
899  }
900 
901  private function assertHasPageState( $method ) {
902  if ( !$this->pageState ) {
903  throw new LogicException(
904  'Must call grabCurrentRevision() or prepareContent() '
905  . 'or prepareUpdate() before calling ' . $method
906  );
907  }
908  }
909 
910  private function assertPrepared( $method ) {
911  if ( !$this->revision ) {
912  throw new LogicException(
913  'Must call prepareContent() or prepareUpdate() before calling ' . $method
914  );
915  }
916  }
917 
918  private function assertHasRevision( $method ) {
919  if ( !$this->revision->getId() ) {
920  throw new LogicException(
921  'Must call prepareUpdate() before calling ' . $method
922  );
923  }
924  }
925 
931  public function isCreation() {
932  $this->assertPrepared( __METHOD__ );
933  return $this->options['created'];
934  }
935 
945  public function isChange() {
946  $this->assertPrepared( __METHOD__ );
947  return $this->options['changed'];
948  }
949 
955  public function wasRedirect() {
956  $this->assertHasPageState( __METHOD__ );
957 
958  if ( $this->pageState['oldIsRedirect'] === null ) {
960  $rev = $this->pageState['oldRevision'];
961  if ( $rev ) {
962  $this->pageState['oldIsRedirect'] = $this->revisionIsRedirect( $rev );
963  } else {
964  $this->pageState['oldIsRedirect'] = false;
965  }
966  }
967 
968  return $this->pageState['oldIsRedirect'];
969  }
970 
979  public function getSlots() {
980  $this->assertPrepared( __METHOD__ );
981  return $this->revision->getSlots();
982  }
983 
989  private function getRevisionSlotsUpdate() {
990  $this->assertPrepared( __METHOD__ );
991 
992  if ( !$this->slotsUpdate ) {
993  $old = $this->getParentRevision();
994  $this->slotsUpdate = RevisionSlotsUpdate::newFromRevisionSlots(
995  $this->revision->getSlots(),
996  $old ? $old->getSlots() : null
997  );
998  }
999  return $this->slotsUpdate;
1000  }
1001 
1008  public function getTouchedSlotRoles() {
1009  return $this->getRevisionSlotsUpdate()->getTouchedRoles();
1010  }
1011 
1018  public function getModifiedSlotRoles() {
1019  return $this->getRevisionSlotsUpdate()->getModifiedRoles();
1020  }
1021 
1027  public function getRemovedSlotRoles() {
1028  return $this->getRevisionSlotsUpdate()->getRemovedRoles();
1029  }
1030 
1075  Assert::parameter(
1076  !isset( $options['oldrevision'] )
1077  || $options['oldrevision'] instanceof Revision
1078  || $options['oldrevision'] instanceof RevisionRecord,
1079  '$options["oldrevision"]',
1080  'must be a RevisionRecord (or Revision)'
1081  );
1082  Assert::parameter(
1083  !isset( $options['triggeringUser'] )
1084  || $options['triggeringUser'] instanceof UserIdentity,
1085  '$options["triggeringUser"]',
1086  'must be a UserIdentity'
1087  );
1088 
1089  if ( !$revision->getId() ) {
1090  throw new InvalidArgumentException(
1091  'Revision must have an ID set for it to be used with prepareUpdate()!'
1092  );
1093  }
1094 
1095  if ( $this->revision && $this->revision->getId() ) {
1096  if ( $this->revision->getId() === $revision->getId() ) {
1097  return; // nothing to do!
1098  } else {
1099  throw new LogicException(
1100  'Trying to re-use DerivedPageDataUpdater with revision '
1101  . $revision->getId()
1102  . ', but it\'s already bound to revision '
1103  . $this->revision->getId()
1104  );
1105  }
1106  }
1107 
1108  if ( $this->revision
1109  && !$this->revision->getSlots()->hasSameContent( $revision->getSlots() )
1110  ) {
1111  throw new LogicException(
1112  'The Revision provided has mismatching content!'
1113  );
1114  }
1115 
1116  // Override fields defined in $this->options with values from $options.
1117  $this->options = array_intersect_key( $options, $this->options ) + $this->options;
1118 
1119  if ( $this->revision ) {
1120  $oldId = $this->pageState['oldId'] ?? 0;
1121  $this->options['newrev'] = ( $revision->getId() !== $oldId );
1122  } elseif ( isset( $this->options['oldrevision'] ) ) {
1124  $oldRev = $this->options['oldrevision'];
1125  $oldId = $oldRev->getId();
1126  $this->options['newrev'] = ( $revision->getId() !== $oldId );
1127  } else {
1128  $oldId = $revision->getParentId();
1129  }
1130 
1131  if ( $oldId !== null ) {
1132  // XXX: what if $options['changed'] disagrees?
1133  // MovePage creates a dummy revision with changed = false!
1134  // We may want to explicitly distinguish between "no new revision" (null-edit)
1135  // and "new revision without new content" (dummy revision).
1136 
1137  if ( $oldId === $revision->getParentId() ) {
1138  // NOTE: this may still be a NullRevision!
1139  // New revision!
1140  $this->options['changed'] = true;
1141  } elseif ( $oldId === $revision->getId() ) {
1142  // Null-edit!
1143  $this->options['changed'] = false;
1144  } else {
1145  // This indicates that calling code has given us the wrong Revision object
1146  throw new LogicException(
1147  'The Revision mismatches old revision ID: '
1148  . 'Old ID is ' . $oldId
1149  . ', parent ID is ' . $revision->getParentId()
1150  . ', revision ID is ' . $revision->getId()
1151  );
1152  }
1153  }
1154 
1155  // If prepareContent() was used to generate the PST content (which is indicated by
1156  // $this->slotsUpdate being set), and this is not a null-edit, then the given
1157  // revision must have the acting user as the revision author. Otherwise, user
1158  // signatures generated by PST would mismatch the user in the revision record.
1159  if ( $this->user !== null && $this->options['changed'] && $this->slotsUpdate ) {
1160  $user = $revision->getUser();
1161  if ( !$this->user->equals( $user ) ) {
1162  throw new LogicException(
1163  'The Revision provided has a mismatching actor: expected '
1164  . $this->user->getName()
1165  . ', got '
1166  . $user->getName()
1167  );
1168  }
1169  }
1170 
1171  // If $this->pageState was not yet initialized by grabCurrentRevision or prepareContent,
1172  // emulate the state of the page table before the edit, as good as we can.
1173  if ( !$this->pageState ) {
1174  $this->pageState = [
1175  'oldIsRedirect' => isset( $this->options['oldredirect'] )
1176  && is_bool( $this->options['oldredirect'] )
1177  ? $this->options['oldredirect']
1178  : null,
1179  'oldCountable' => isset( $this->options['oldcountable'] )
1180  && is_bool( $this->options['oldcountable'] )
1181  ? $this->options['oldcountable']
1182  : null,
1183  ];
1184 
1185  if ( $this->options['changed'] ) {
1186  // The edit created a new revision
1187  $this->pageState['oldId'] = $revision->getParentId();
1188 
1189  if ( isset( $this->options['oldrevision'] ) ) {
1190  $rev = $this->options['oldrevision'];
1191  $this->pageState['oldRevision'] = $rev instanceof Revision
1192  ? $rev->getRevisionRecord()
1193  : $rev;
1194  }
1195  } else {
1196  // This is a null-edit, so the old revision IS the new revision!
1197  $this->pageState['oldId'] = $revision->getId();
1198  $this->pageState['oldRevision'] = $revision;
1199  }
1200  }
1201 
1202  // "created" is forced here
1203  $this->options['created'] = ( $this->pageState['oldId'] === 0 );
1204 
1205  $this->revision = $revision;
1206 
1207  $this->doTransition( 'has-revision' );
1208 
1209  // NOTE: in case we have a User object, don't override with a UserIdentity.
1210  // We already checked that $revision->getUser() mathces $this->user;
1211  if ( !$this->user ) {
1212  $this->user = $revision->getUser( RevisionRecord::RAW );
1213  }
1214 
1215  // Prune any output that depends on the revision ID.
1216  if ( $this->renderedRevision ) {
1217  $this->renderedRevision->updateRevision( $revision );
1218  }
1219 
1220  // TODO: optionally get ParserOutput from the ParserCache here.
1221  // Move the logic used by RefreshLinksJob here!
1222  }
1223 
1228  public function getPreparedEdit() {
1229  $this->assertPrepared( __METHOD__ );
1230 
1232  $preparedEdit = new PreparedEdit();
1233 
1234  $preparedEdit->popts = $this->getCanonicalParserOptions();
1235  $preparedEdit->output = $this->getCanonicalParserOutput();
1236  $preparedEdit->pstContent = $this->revision->getContent( SlotRecord::MAIN );
1237  $preparedEdit->newContent =
1239  ? $slotsUpdate->getModifiedSlot( SlotRecord::MAIN )->getContent()
1240  : $this->revision->getContent( SlotRecord::MAIN ); // XXX: can we just remove this?
1241  $preparedEdit->oldContent = null; // unused. // XXX: could get this from the parent revision
1242  $preparedEdit->revid = $this->revision ? $this->revision->getId() : null;
1243  $preparedEdit->timestamp = $preparedEdit->output->getCacheTime();
1244  $preparedEdit->format = $preparedEdit->pstContent->getDefaultFormat();
1245 
1246  return $preparedEdit;
1247  }
1248 
1254  public function getSlotParserOutput( $role, $generateHtml = true ) {
1255  return $this->getRenderedRevision()->getSlotParserOutput(
1256  $role,
1257  [ 'generate-html' => $generateHtml ]
1258  );
1259  }
1260 
1264  public function getCanonicalParserOutput() {
1265  return $this->getRenderedRevision()->getRevisionParserOutput();
1266  }
1267 
1271  public function getCanonicalParserOptions() {
1272  return $this->getRenderedRevision()->getOptions();
1273  }
1274 
1280  public function getSecondaryDataUpdates( $recursive = false ) {
1281  if ( $this->isContentDeleted() ) {
1282  // This shouldn't happen, since the current content is always public,
1283  // and DataUpates are only needed for current content.
1284  return [];
1285  }
1286 
1287  $output = $this->getCanonicalParserOutput();
1288 
1289  // Construct a LinksUpdate for the combined canonical output.
1290  $linksUpdate = new LinksUpdate(
1291  $this->getTitle(),
1292  $output,
1293  $recursive
1294  );
1295 
1296  $allUpdates = [ $linksUpdate ];
1297 
1298  // NOTE: Run updates for all slots, not just the modified slots! Otherwise,
1299  // info for an inherited slot may end up being removed. This is also needed
1300  // to ensure that purges are effective.
1302  foreach ( $this->getSlots()->getSlotRoles() as $role ) {
1303  $slot = $this->getRawSlot( $role );
1304  $content = $slot->getContent();
1305  $handler = $content->getContentHandler();
1306 
1307  $updates = $handler->getSecondaryDataUpdates(
1308  $this->getTitle(),
1309  $content,
1310  $role,
1312  );
1313  $allUpdates = array_merge( $allUpdates, $updates );
1314 
1315  // TODO: remove B/C hack in 1.32!
1316  // NOTE: we assume that the combined output contains all relevant meta-data for
1317  // all slots!
1318  $legacyUpdates = $content->getSecondaryDataUpdates(
1319  $this->getTitle(),
1320  null,
1321  $recursive,
1322  $output
1323  );
1324 
1325  // HACK: filter out redundant and incomplete LinksUpdates
1326  $legacyUpdates = array_filter( $legacyUpdates, function ( $update ) {
1327  return !( $update instanceof LinksUpdate );
1328  } );
1329 
1330  $allUpdates = array_merge( $allUpdates, $legacyUpdates );
1331  }
1332 
1333  // XXX: if a slot was removed by an earlier edit, but deletion updates failed to run at
1334  // that time, we don't know for which slots to run deletion updates when purging a page.
1335  // We'd have to examine the entire history of the page to determine that. Perhaps there
1336  // could be a "try extra hard" mode for that case that would run a DB query to find all
1337  // roles/models ever used on the page. On the other hand, removing slots should be quite
1338  // rare, so perhaps this isn't worth the trouble.
1339 
1340  // TODO: consolidate with similar logic in WikiPage::getDeletionUpdates()
1341  $wikiPage = $this->getWikiPage();
1343  foreach ( $this->getRemovedSlotRoles() as $role ) {
1344  // HACK: we should get the content model of the removed slot from a SlotRoleHandler!
1345  // For now, find the slot in the parent revision - if the slot was removed, it should
1346  // always exist in the parent revision.
1347  $parentSlot = $parentRevision->getSlot( $role, RevisionRecord::RAW );
1348  $content = $parentSlot->getContent();
1349  $handler = $content->getContentHandler();
1350 
1351  $updates = $handler->getDeletionUpdates(
1352  $this->getTitle(),
1353  $role
1354  );
1355  $allUpdates = array_merge( $allUpdates, $updates );
1356 
1357  // TODO: remove B/C hack in 1.32!
1358  $legacyUpdates = $content->getDeletionUpdates( $wikiPage );
1359 
1360  // HACK: filter out redundant and incomplete LinksDeletionUpdate
1361  $legacyUpdates = array_filter( $legacyUpdates, function ( $update ) {
1362  return !( $update instanceof LinksDeletionUpdate );
1363  } );
1364 
1365  $allUpdates = array_merge( $allUpdates, $legacyUpdates );
1366  }
1367 
1368  // TODO: hard deprecate SecondaryDataUpdates in favor of RevisionDataUpdates in 1.33!
1369  Hooks::run(
1370  'RevisionDataUpdates',
1371  [ $this->getTitle(), $renderedRevision, &$allUpdates ]
1372  );
1373 
1374  return $allUpdates;
1375  }
1376 
1387  public function doUpdates() {
1388  $this->assertTransition( 'done' );
1389 
1390  // TODO: move logic into a PageEventEmitter service
1391 
1392  $wikiPage = $this->getWikiPage(); // TODO: use only for legacy hooks!
1393 
1394  $legacyUser = User::newFromIdentity( $this->user );
1395  $legacyRevision = new Revision( $this->revision );
1396 
1397  $this->doParserCacheUpdate();
1398 
1399  $this->doSecondaryDataUpdates( [
1400  // T52785 do not update any other pages on a null edit
1401  'recursive' => $this->options['changed'],
1402  'defer' => DeferredUpdates::POSTSEND,
1403  ] );
1404 
1405  // TODO: MCR: check if *any* changed slot supports categories!
1406  if ( $this->rcWatchCategoryMembership
1407  && $this->getContentHandler( SlotRecord::MAIN )->supportsCategories() === true
1408  && ( $this->options['changed'] || $this->options['created'] )
1409  && !$this->options['restored']
1410  ) {
1411  // Note: jobs are pushed after deferred updates, so the job should be able to see
1412  // the recent change entry (also done via deferred updates) and carry over any
1413  // bot/deletion/IP flags, ect.
1414  $this->jobQueueGroup->lazyPush(
1416  $this->getTitle(),
1417  [
1418  'pageId' => $this->getPageId(),
1419  'revTimestamp' => $this->revision->getTimestamp(),
1420  ]
1421  )
1422  );
1423  }
1424 
1425  // TODO: replace legacy hook! Use a listener on PageEventEmitter instead!
1426  $editInfo = $this->getPreparedEdit();
1427  Hooks::run( 'ArticleEditUpdates', [ &$wikiPage, &$editInfo, $this->options['changed'] ] );
1428 
1429  // TODO: replace legacy hook! Use a listener on PageEventEmitter instead!
1430  if ( Hooks::run( 'ArticleEditUpdatesDeleteFromRecentchanges', [ &$wikiPage ] ) ) {
1431  // Flush old entries from the `recentchanges` table
1432  if ( mt_rand( 0, 9 ) == 0 ) {
1433  $this->jobQueueGroup->lazyPush( RecentChangesUpdateJob::newPurgeJob() );
1434  }
1435  }
1436 
1437  $id = $this->getPageId();
1438  $title = $this->getTitle();
1439  $dbKey = $title->getPrefixedDBkey();
1440  $shortTitle = $title->getDBkey();
1441 
1442  if ( !$title->exists() ) {
1443  wfDebug( __METHOD__ . ": Page doesn't exist any more, bailing out\n" );
1444 
1445  $this->doTransition( 'done' );
1446  return;
1447  }
1448 
1449  if ( $this->options['oldcountable'] === 'no-change' ||
1450  ( !$this->options['changed'] && !$this->options['moved'] )
1451  ) {
1452  $good = 0;
1453  } elseif ( $this->options['created'] ) {
1454  $good = (int)$this->isCountable();
1455  } elseif ( $this->options['oldcountable'] !== null ) {
1456  $good = (int)$this->isCountable()
1457  - (int)$this->options['oldcountable'];
1458  } elseif ( isset( $this->pageState['oldCountable'] ) ) {
1459  $good = (int)$this->isCountable()
1460  - (int)$this->pageState['oldCountable'];
1461  } else {
1462  $good = 0;
1463  }
1464  $edits = $this->options['changed'] ? 1 : 0;
1465  $pages = $this->options['created'] ? 1 : 0;
1466 
1468  [ 'edits' => $edits, 'articles' => $good, 'pages' => $pages ]
1469  ) );
1470 
1471  // TODO: make search infrastructure aware of slots!
1472  $mainSlot = $this->revision->getSlot( SlotRecord::MAIN );
1473  if ( !$mainSlot->isInherited() && !$this->isContentDeleted() ) {
1474  DeferredUpdates::addUpdate( new SearchUpdate( $id, $dbKey, $mainSlot->getContent() ) );
1475  }
1476 
1477  // If this is another user's talk page, update newtalk.
1478  // Don't do this if $options['changed'] = false (null-edits) nor if
1479  // it's a minor edit and the user making the edit doesn't generate notifications for those.
1480  if ( $this->options['changed']
1481  && $title->getNamespace() == NS_USER_TALK
1482  && $shortTitle != $legacyUser->getTitleKey()
1483  && !( $this->revision->isMinor() && $legacyUser->isAllowed( 'nominornewtalk' ) )
1484  ) {
1485  $recipient = User::newFromName( $shortTitle, false );
1486  if ( !$recipient ) {
1487  wfDebug( __METHOD__ . ": invalid username\n" );
1488  } else {
1489  // Allow extensions to prevent user notification
1490  // when a new message is added to their talk page
1491  // TODO: replace legacy hook! Use a listener on PageEventEmitter instead!
1492  if ( Hooks::run( 'ArticleEditUpdateNewTalk', [ &$wikiPage, $recipient ] ) ) {
1493  if ( User::isIP( $shortTitle ) ) {
1494  // An anonymous user
1495  $recipient->setNewtalk( true, $legacyRevision );
1496  } elseif ( $recipient->isLoggedIn() ) {
1497  $recipient->setNewtalk( true, $legacyRevision );
1498  } else {
1499  wfDebug( __METHOD__ . ": don't need to notify a nonexistent user\n" );
1500  }
1501  }
1502  }
1503  }
1504 
1505  if ( $title->getNamespace() == NS_MEDIAWIKI
1506  && $this->getRevisionSlotsUpdate()->isModifiedSlot( SlotRecord::MAIN )
1507  ) {
1508  $mainContent = $this->isContentDeleted() ? null : $this->getRawContent( SlotRecord::MAIN );
1509 
1510  $this->messageCache->updateMessageOverride( $title, $mainContent );
1511  }
1512 
1513  // TODO: move onArticleCreate and onArticle into a PageEventEmitter service
1514  if ( $this->options['created'] ) {
1516  } elseif ( $this->options['changed'] ) { // T52785
1517  WikiPage::onArticleEdit( $title, $legacyRevision, $this->getTouchedSlotRoles() );
1518  }
1519 
1520  $oldRevision = $this->getParentRevision();
1521  $oldLegacyRevision = $oldRevision ? new Revision( $oldRevision ) : null;
1522 
1523  // TODO: In the wiring, register a listener for this on the new PageEventEmitter
1525  $title, $oldLegacyRevision, $legacyRevision, $this->getWikiId() ?: wfWikiID()
1526  );
1527 
1528  $this->doTransition( 'done' );
1529  }
1530 
1545  public function doSecondaryDataUpdates( array $options = [] ) {
1546  $this->assertHasRevision( __METHOD__ );
1547  $options += [
1548  'recursive' => false,
1549  'defer' => false,
1550  'transactionTicket' => null,
1551  ];
1552  $deferValues = [ false, DeferredUpdates::PRESEND, DeferredUpdates::POSTSEND ];
1553  if ( !in_array( $options['defer'], $deferValues, true ) ) {
1554  throw new InvalidArgumentException( 'invalid value for defer: ' . $options['defer'] );
1555  }
1556  Assert::parameterType( 'integer|null', $options['transactionTicket'],
1557  '$options[\'transactionTicket\']' );
1558 
1559  $updates = $this->getSecondaryDataUpdates( $options['recursive'] );
1560 
1561  $triggeringUser = $this->options['triggeringUser'] ?? $this->user;
1562  if ( !$triggeringUser instanceof User ) {
1563  $triggeringUser = User::newFromIdentity( $triggeringUser );
1564  }
1565  $causeAction = $this->options['causeAction'] ?? 'unknown';
1566  $causeAgent = $this->options['causeAgent'] ?? 'unknown';
1567  $legacyRevision = new Revision( $this->revision );
1568 
1569  if ( $options['defer'] === false && $options['transactionTicket'] !== null ) {
1570  // For legacy hook handlers doing updates via LinksUpdateConstructed, make sure
1571  // any pending writes they made get flushed before the doUpdate() calls below.
1572  // This avoids snapshot-clearing errors in LinksUpdate::acquirePageLock().
1573  $this->loadbalancerFactory->commitAndWaitForReplication(
1574  __METHOD__, $options['transactionTicket']
1575  );
1576  }
1577 
1578  foreach ( $updates as $update ) {
1579  if ( $update instanceof DataUpdate ) {
1580  $update->setCause( $causeAction, $causeAgent );
1581  }
1582  if ( $update instanceof LinksUpdate ) {
1583  $update->setRevision( $legacyRevision );
1584  $update->setTriggeringUser( $triggeringUser );
1585  }
1586  if ( $options['defer'] === false ) {
1587  if ( $options['transactionTicket'] !== null ) {
1588  $update->setTransactionTicket( $options['transactionTicket'] );
1589  }
1590  $update->doUpdate();
1591  } else {
1592  DeferredUpdates::addUpdate( $update, $options['defer'] );
1593  }
1594  }
1595  }
1596 
1597  public function doParserCacheUpdate() {
1598  $this->assertHasRevision( __METHOD__ );
1599 
1600  $wikiPage = $this->getWikiPage(); // TODO: ParserCache should accept a RevisionRecord instead
1601 
1602  // NOTE: this may trigger the first parsing of the new content after an edit (when not
1603  // using pre-generated stashed output).
1604  // XXX: we may want to use the PoolCounter here. This would perhaps allow the initial parse
1605  // to be performed post-send. The client could already follow a HTTP redirect to the
1606  // page view, but would then have to wait for a response until rendering is complete.
1607  $output = $this->getCanonicalParserOutput();
1608 
1609  // Save it to the parser cache. Use the revision timestamp in the case of a
1610  // freshly saved edit, as that matches page_touched and a mismatch would trigger an
1611  // unnecessary reparse.
1612  $timestamp = $this->options['newrev'] ? $this->revision->getTimestamp()
1613  : $output->getCacheTime();
1614  $this->parserCache->save(
1616  $timestamp, $this->revision->getId()
1617  );
1618  }
1619 
1620 }
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:1264
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:297
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:120
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:3344
MediaWiki\Storage\DerivedPageDataUpdater\$parserCache
ParserCache $parserCache
Definition: DerivedPageDataUpdater.php:110
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:467
MediaWiki\Storage\DerivedPageDataUpdater\pageExisted
pageExisted()
Determines whether the page being edited already existed.
Definition: DerivedPageDataUpdater.php:466
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:481
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:876
MediaWiki\Storage\DerivedPageDataUpdater\$articleCountMethod
string $articleCountMethod
see $wgArticleCountMethod
Definition: DerivedPageDataUpdater.php:140
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:720
MediaWiki\Storage\DerivedPageDataUpdater\getSlotParserOutput
getSlotParserOutput( $role, $generateHtml=true)
Definition: DerivedPageDataUpdater.php:1254
MediaWiki\Storage\DerivedPageDataUpdater\$user
UserIdentity null $user
Definition: DerivedPageDataUpdater.php:100
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:1074
MediaWiki\Storage\DerivedPageDataUpdater\setArticleCountMethod
setArticleCountMethod( $articleCountMethod)
Definition: DerivedPageDataUpdater.php:431
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:979
MediaWiki\Storage\DerivedPageDataUpdater\$loadbalancerFactory
LBFactory $loadbalancerFactory
Definition: DerivedPageDataUpdater.php:135
MediaWiki\Storage\DerivedPageDataUpdater\$parentRevision
RevisionRecord null $parentRevision
Definition: DerivedPageDataUpdater.php:198
MediaWiki\Storage\DerivedPageDataUpdater\setRcWatchCategoryMembership
setRcWatchCategoryMembership( $rcWatchCategoryMembership)
Definition: DerivedPageDataUpdater.php:439
MediaWiki\Storage\DerivedPageDataUpdater\getPreparedEdit
getPreparedEdit()
Definition: DerivedPageDataUpdater.php:1228
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:50
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:44
MediaWiki\Storage\DerivedPageDataUpdater\$revisionRenderer
RevisionRenderer $revisionRenderer
Definition: DerivedPageDataUpdater.php:213
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:574
User\newFromName
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition: User.php:592
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:765
MediaWiki\Storage\DerivedPageDataUpdater\$options
$options
Stores (most of) the $options parameter of prepareUpdate().
Definition: DerivedPageDataUpdater.php:151
MediaWiki\Storage\DerivedPageDataUpdater\$renderedRevision
RenderedRevision $renderedRevision
Definition: DerivedPageDataUpdater.php:208
User\newFromIdentity
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition: User.php:658
User
User
Definition: All_system_messages.txt:425
MediaWiki\Storage\DerivedPageDataUpdater\$slotsUpdate
RevisionSlotsUpdate null $slotsUpdate
Definition: DerivedPageDataUpdater.php:193
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:3431
MediaWiki\Storage\DerivedPageDataUpdater\isRedirect
isRedirect()
Definition: DerivedPageDataUpdater.php:677
MediaWiki\Storage\DerivedPageDataUpdater\getCanonicalParserOptions
getCanonicalParserOptions()
Definition: DerivedPageDataUpdater.php:1271
MediaWiki\Storage\DerivedPageDataUpdater\doUpdates
doUpdates()
Do standard updates after page edit, purge, or import.
Definition: DerivedPageDataUpdater.php:1387
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:115
MediaWiki\Storage\DerivedPageDataUpdater\assertHasRevision
assertHasRevision( $method)
Definition: DerivedPageDataUpdater.php:918
Revision
Definition: Revision.php:41
$title
namespace and then decline to actually register it file or subcat img or subcat $title
Definition: hooks.txt:964
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:344
ParserOptions\newFromUserAndLang
static newFromUserAndLang(User $user, Language $lang)
Get a ParserOptions object from a given user and language.
Definition: ParserOptions.php:1028
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:989
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:633
Revision\RevisionRecord\isMinor
isMinor()
MCR migration note: this replaces Revision::isMinor.
Definition: RevisionRecord.php:403
MediaWiki\Storage\DerivedPageDataUpdater\assertPrepared
assertPrepared( $method)
Definition: DerivedPageDataUpdater.php:910
User\isIP
static isIP( $name)
Does the string match an anonymous IP address?
Definition: User.php:971
MediaWiki\Storage\DerivedPageDataUpdater\getContentModel
getContentModel( $role)
Returns the content model of the given slot.
Definition: DerivedPageDataUpdater.php:625
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:1841
MediaWiki\User\UserIdentity\getName
getName()
MediaWiki\Storage\DerivedPageDataUpdater\wasRedirect
wasRedirect()
Whether the page was a redirect before the edit.
Definition: DerivedPageDataUpdater.php:955
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:4644
wfTimestampNow
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
Definition: GlobalFunctions.php:1983
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:931
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:319
MediaWiki\Storage\DerivedPageDataUpdater\__construct
__construct(WikiPage $wikiPage, RevisionStore $revisionStore, RevisionRenderer $revisionRenderer, ParserCache $parserCache, JobQueueGroup $jobQueueGroup, MessageCache $messageCache, Language $contLang, LBFactory $loadbalancerFactory)
Definition: DerivedPageDataUpdater.php:267
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:988
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:923
MediaWiki\Storage\DerivedPageDataUpdater\useMaster
useMaster()
Definition: DerivedPageDataUpdater.php:638
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
Revision\RevisionRecord\getId
getId()
Get revision ID.
Definition: RevisionRecord.php:273
MediaWiki\Storage\DerivedPageDataUpdater\getWikiId
getWikiId()
Definition: DerivedPageDataUpdater.php:328
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:2644
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:235
MediaWiki\Storage\DerivedPageDataUpdater\isContentDeleted
isContentDeleted()
Whether the content is deleted and thus not visible to the public.
Definition: DerivedPageDataUpdater.php:584
Revision\MutableRevisionRecord
Mutable RevisionRecord implementation, for building new revision entries programmatically.
Definition: MutableRevisionRecord.php:41
MediaWiki\Storage\DerivedPageDataUpdater\getWikiPage
getWikiPage()
Definition: DerivedPageDataUpdater.php:454
MediaWiki\Storage\DerivedPageDataUpdater\isCountable
isCountable()
Definition: DerivedPageDataUpdater.php:646
MediaWiki\Storage\DerivedPageDataUpdater\isContentPrepared
isContentPrepared()
Whether prepareUpdate() or prepareContent() have been called on this instance.
Definition: DerivedPageDataUpdater.php:556
LinksDeletionUpdate
Update object handling the cleanup of links tables after a page was deleted.
Definition: LinksDeletionUpdate.php:29
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:2115
MediaWiki\Storage\DerivedPageDataUpdater\getRemovedSlotRoles
getRemovedSlotRoles()
Returns the role names of the slots removed by the new revision.
Definition: DerivedPageDataUpdater.php:1027
MediaWiki\Storage\DerivedPageDataUpdater\$pageState
array $pageState
The state of the relevant row in page table before the edit.
Definition: DerivedPageDataUpdater.php:188
$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 probably a stub 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:813
MediaWiki\Storage
Definition: BlobAccessException.php:23
Revision\SlotRecord\MAIN
const MAIN
Definition: SlotRecord.php:41
MediaWiki\Storage\DerivedPageDataUpdater\$messageCache
MessageCache $messageCache
Definition: DerivedPageDataUpdater.php:130
MediaWiki\Storage\DerivedPageDataUpdater\doTransition
doTransition( $newStage)
Transition function for managing the life cycle of this instances.
Definition: DerivedPageDataUpdater.php:301
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:1018
Content
Base interface for content objects.
Definition: Content.php:34
MediaWiki\Storage\DerivedPageDataUpdater\$jobQueueGroup
JobQueueGroup $jobQueueGroup
Definition: DerivedPageDataUpdater.php:125
Revision\RevisionRecord\getVisibility
getVisibility()
Get the deletion bitfield of the revision.
Definition: RevisionRecord.php:425
Title
Represents a title within MediaWiki.
Definition: Title.php:39
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:615
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:446
MediaWiki\Storage\DerivedPageDataUpdater\getSecondaryDataUpdates
getSecondaryDataUpdates( $recursive=false)
Definition: DerivedPageDataUpdater.php:1280
Wikimedia\Rdbms\LBFactory
An interface for generating database load balancers.
Definition: LBFactory.php:39
MediaWiki\Storage\DerivedPageDataUpdater\doParserCacheUpdate
doParserCacheUpdate()
Definition: DerivedPageDataUpdater.php:1597
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:1808
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:884
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:145
ParserCache
Definition: ParserCache.php:30
RecentChangesUpdateJob\newPurgeJob
static newPurgeJob()
Definition: RecentChangesUpdateJob.php:44
MediaWiki\Storage\DerivedPageDataUpdater\$revision
RevisionRecord null $revision
Definition: DerivedPageDataUpdater.php:203
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:603
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:1545
Revision\RenderedRevision
RenderedRevision represents the rendered representation of a revision.
Definition: RenderedRevision.php:44
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:945
NS_MEDIAWIKI
const NS_MEDIAWIKI
Definition: Defines.php:72
MediaWiki\Storage\DerivedPageDataUpdater\assertHasPageState
assertHasPageState( $method)
Definition: DerivedPageDataUpdater.php:901
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:1008
MediaWiki\Storage\DerivedPageDataUpdater\$stage
string $stage
A stage identifier for managing the life cycle of this instance.
Definition: DerivedPageDataUpdater.php:223
WikiPage\isRedirect
isRedirect()
Tests if the article content represents a redirect.
Definition: WikiPage.php:612
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:95
MediaWiki\Storage\DerivedPageDataUpdater\$wikiPage
WikiPage $wikiPage
Definition: DerivedPageDataUpdater.php:105
ResourceLoaderWikiModule\invalidateModuleCache
static invalidateModuleCache(Title $title, Revision $old=null, Revision $new=null, $wikiId)
Clear the preloadTitleInfo() cache for all wiki modules on this wiki on page change if it was a JS or...
Definition: ResourceLoaderWikiModule.php:534
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:47
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:689
User\getName
getName()
Get the user name, or the IP of an anonymous user.
Definition: User.php:2463
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:265
Language
Internationalisation code.
Definition: Language.php:35
MediaWiki\Storage\DerivedPageDataUpdater\grabCurrentRevision
grabCurrentRevision()
Returns the revision that was the page's current revision when grabCurrentRevision() was first called...
Definition: DerivedPageDataUpdater.php:523
MediaWiki\Storage\DerivedPageDataUpdater\isUpdatePrepared
isUpdatePrepared()
Whether prepareUpdate() has been called on this instance.
Definition: DerivedPageDataUpdater.php:567
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:29
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