MediaWiki REL1_32
DerivedPageDataUpdater.php
Go to the documentation of this file.
1<?php
23namespace MediaWiki\Storage;
24
34use InvalidArgumentException;
39use LogicException;
59use User;
60use Wikimedia\Assert\Assert;
63
96
100 private $user = null;
101
105 private $wikiPage;
106
111
116
120 private $contLang;
121
126
131
136
141
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,
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 ) {
375 $user = $revision->getUser( RevisionRecord::RAW );
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 ) {
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
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
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 ) {
786 $this->revision = MutableRevisionRecord::newFromParentRevision( $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();
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
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->options['created'] ||
1204 ( $this->pageState['oldId'] === 0 ) );
1205
1206 $this->revision = $revision;
1207
1208 $this->doTransition( 'has-revision' );
1209
1210 // NOTE: in case we have a User object, don't override with a UserIdentity.
1211 // We already checked that $revision->getUser() mathces $this->user;
1212 if ( !$this->user ) {
1213 $this->user = $revision->getUser( RevisionRecord::RAW );
1214 }
1215
1216 // Prune any output that depends on the revision ID.
1217 if ( $this->renderedRevision ) {
1218 $this->renderedRevision->updateRevision( $revision );
1219 }
1220
1221 // TODO: optionally get ParserOutput from the ParserCache here.
1222 // Move the logic used by RefreshLinksJob here!
1223 }
1224
1229 public function getPreparedEdit() {
1230 $this->assertPrepared( __METHOD__ );
1231
1233 $preparedEdit = new PreparedEdit();
1234
1235 $preparedEdit->popts = $this->getCanonicalParserOptions();
1236 $preparedEdit->output = $this->getCanonicalParserOutput();
1237 $preparedEdit->pstContent = $this->revision->getContent( SlotRecord::MAIN );
1238 $preparedEdit->newContent =
1239 $slotsUpdate->isModifiedSlot( SlotRecord::MAIN )
1240 ? $slotsUpdate->getModifiedSlot( SlotRecord::MAIN )->getContent()
1241 : $this->revision->getContent( SlotRecord::MAIN ); // XXX: can we just remove this?
1242 $preparedEdit->oldContent = null; // unused. // XXX: could get this from the parent revision
1243 $preparedEdit->revid = $this->revision ? $this->revision->getId() : null;
1244 $preparedEdit->timestamp = $preparedEdit->output->getCacheTime();
1245 $preparedEdit->format = $preparedEdit->pstContent->getDefaultFormat();
1246
1247 return $preparedEdit;
1248 }
1249
1255 public function getSlotParserOutput( $role, $generateHtml = true ) {
1256 return $this->getRenderedRevision()->getSlotParserOutput(
1257 $role,
1258 [ 'generate-html' => $generateHtml ]
1259 );
1260 }
1261
1265 public function getCanonicalParserOutput() {
1266 return $this->getRenderedRevision()->getRevisionParserOutput();
1267 }
1268
1272 public function getCanonicalParserOptions() {
1273 return $this->getRenderedRevision()->getOptions();
1274 }
1275
1281 public function getSecondaryDataUpdates( $recursive = false ) {
1282 if ( $this->isContentDeleted() ) {
1283 // This shouldn't happen, since the current content is always public,
1284 // and DataUpates are only needed for current content.
1285 return [];
1286 }
1287
1289
1290 // Construct a LinksUpdate for the combined canonical output.
1292 $this->getTitle(),
1293 $output,
1294 $recursive
1295 );
1296
1297 $allUpdates = [ $linksUpdate ];
1298
1299 // NOTE: Run updates for all slots, not just the modified slots! Otherwise,
1300 // info for an inherited slot may end up being removed. This is also needed
1301 // to ensure that purges are effective.
1303 foreach ( $this->getSlots()->getSlotRoles() as $role ) {
1304 $slot = $this->getRawSlot( $role );
1305 $content = $slot->getContent();
1306 $handler = $content->getContentHandler();
1307
1308 $updates = $handler->getSecondaryDataUpdates(
1309 $this->getTitle(),
1310 $content,
1311 $role,
1313 );
1314 $allUpdates = array_merge( $allUpdates, $updates );
1315
1316 // TODO: remove B/C hack in 1.32!
1317 // NOTE: we assume that the combined output contains all relevant meta-data for
1318 // all slots!
1319 $legacyUpdates = $content->getSecondaryDataUpdates(
1320 $this->getTitle(),
1321 null,
1322 $recursive,
1323 $output
1324 );
1325
1326 // HACK: filter out redundant and incomplete LinksUpdates
1327 $legacyUpdates = array_filter( $legacyUpdates, function ( $update ) {
1328 return !( $update instanceof LinksUpdate );
1329 } );
1330
1331 $allUpdates = array_merge( $allUpdates, $legacyUpdates );
1332 }
1333
1334 // XXX: if a slot was removed by an earlier edit, but deletion updates failed to run at
1335 // that time, we don't know for which slots to run deletion updates when purging a page.
1336 // We'd have to examine the entire history of the page to determine that. Perhaps there
1337 // could be a "try extra hard" mode for that case that would run a DB query to find all
1338 // roles/models ever used on the page. On the other hand, removing slots should be quite
1339 // rare, so perhaps this isn't worth the trouble.
1340
1341 // TODO: consolidate with similar logic in WikiPage::getDeletionUpdates()
1342 $wikiPage = $this->getWikiPage();
1344 foreach ( $this->getRemovedSlotRoles() as $role ) {
1345 // HACK: we should get the content model of the removed slot from a SlotRoleHandler!
1346 // For now, find the slot in the parent revision - if the slot was removed, it should
1347 // always exist in the parent revision.
1348 $parentSlot = $parentRevision->getSlot( $role, RevisionRecord::RAW );
1349 $content = $parentSlot->getContent();
1350 $handler = $content->getContentHandler();
1351
1352 $updates = $handler->getDeletionUpdates(
1353 $this->getTitle(),
1354 $role
1355 );
1356 $allUpdates = array_merge( $allUpdates, $updates );
1357
1358 // TODO: remove B/C hack in 1.32!
1359 $legacyUpdates = $content->getDeletionUpdates( $wikiPage );
1360
1361 // HACK: filter out redundant and incomplete LinksDeletionUpdate
1362 $legacyUpdates = array_filter( $legacyUpdates, function ( $update ) {
1363 return !( $update instanceof LinksDeletionUpdate );
1364 } );
1365
1366 $allUpdates = array_merge( $allUpdates, $legacyUpdates );
1367 }
1368
1369 // TODO: hard deprecate SecondaryDataUpdates in favor of RevisionDataUpdates in 1.33!
1370 Hooks::run(
1371 'RevisionDataUpdates',
1372 [ $this->getTitle(), $renderedRevision, &$allUpdates ]
1373 );
1374
1375 return $allUpdates;
1376 }
1377
1388 public function doUpdates() {
1389 $this->assertTransition( 'done' );
1390
1391 // TODO: move logic into a PageEventEmitter service
1392
1393 $wikiPage = $this->getWikiPage(); // TODO: use only for legacy hooks!
1394
1395 $legacyUser = User::newFromIdentity( $this->user );
1396 $legacyRevision = new Revision( $this->revision );
1397
1398 $this->doParserCacheUpdate();
1399
1400 $this->doSecondaryDataUpdates( [
1401 // T52785 do not update any other pages on a null edit
1402 'recursive' => $this->options['changed'],
1403 'defer' => DeferredUpdates::POSTSEND,
1404 ] );
1405
1406 // TODO: MCR: check if *any* changed slot supports categories!
1407 if ( $this->rcWatchCategoryMembership
1408 && $this->getContentHandler( SlotRecord::MAIN )->supportsCategories() === true
1409 && ( $this->options['changed'] || $this->options['created'] )
1410 && !$this->options['restored']
1411 ) {
1412 // Note: jobs are pushed after deferred updates, so the job should be able to see
1413 // the recent change entry (also done via deferred updates) and carry over any
1414 // bot/deletion/IP flags, ect.
1415 $this->jobQueueGroup->lazyPush(
1417 $this->getTitle(),
1418 [
1419 'pageId' => $this->getPageId(),
1420 'revTimestamp' => $this->revision->getTimestamp(),
1421 ]
1422 )
1423 );
1424 }
1425
1426 // TODO: replace legacy hook! Use a listener on PageEventEmitter instead!
1427 $editInfo = $this->getPreparedEdit();
1428 Hooks::run( 'ArticleEditUpdates', [ &$wikiPage, &$editInfo, $this->options['changed'] ] );
1429
1430 // TODO: replace legacy hook! Use a listener on PageEventEmitter instead!
1431 if ( Hooks::run( 'ArticleEditUpdatesDeleteFromRecentchanges', [ &$wikiPage ] ) ) {
1432 // Flush old entries from the `recentchanges` table
1433 if ( mt_rand( 0, 9 ) == 0 ) {
1434 $this->jobQueueGroup->lazyPush( RecentChangesUpdateJob::newPurgeJob() );
1435 }
1436 }
1437
1438 $id = $this->getPageId();
1439 $title = $this->getTitle();
1440 $dbKey = $title->getPrefixedDBkey();
1441 $shortTitle = $title->getDBkey();
1442
1443 if ( !$title->exists() ) {
1444 wfDebug( __METHOD__ . ": Page doesn't exist any more, bailing out\n" );
1445
1446 $this->doTransition( 'done' );
1447 return;
1448 }
1449
1450 if ( $this->options['oldcountable'] === 'no-change' ||
1451 ( !$this->options['changed'] && !$this->options['moved'] )
1452 ) {
1453 $good = 0;
1454 } elseif ( $this->options['created'] ) {
1455 $good = (int)$this->isCountable();
1456 } elseif ( $this->options['oldcountable'] !== null ) {
1457 $good = (int)$this->isCountable()
1458 - (int)$this->options['oldcountable'];
1459 } elseif ( isset( $this->pageState['oldCountable'] ) ) {
1460 $good = (int)$this->isCountable()
1461 - (int)$this->pageState['oldCountable'];
1462 } else {
1463 $good = 0;
1464 }
1465 $edits = $this->options['changed'] ? 1 : 0;
1466 $pages = $this->options['created'] ? 1 : 0;
1467
1468 DeferredUpdates::addUpdate( SiteStatsUpdate::factory(
1469 [ 'edits' => $edits, 'articles' => $good, 'pages' => $pages ]
1470 ) );
1471
1472 // TODO: make search infrastructure aware of slots!
1473 $mainSlot = $this->revision->getSlot( SlotRecord::MAIN );
1474 if ( !$mainSlot->isInherited() && !$this->isContentDeleted() ) {
1475 DeferredUpdates::addUpdate( new SearchUpdate( $id, $dbKey, $mainSlot->getContent() ) );
1476 }
1477
1478 // If this is another user's talk page, update newtalk.
1479 // Don't do this if $options['changed'] = false (null-edits) nor if
1480 // it's a minor edit and the user making the edit doesn't generate notifications for those.
1481 if ( $this->options['changed']
1482 && $title->getNamespace() == NS_USER_TALK
1483 && $shortTitle != $legacyUser->getTitleKey()
1484 && !( $this->revision->isMinor() && $legacyUser->isAllowed( 'nominornewtalk' ) )
1485 ) {
1486 $recipient = User::newFromName( $shortTitle, false );
1487 if ( !$recipient ) {
1488 wfDebug( __METHOD__ . ": invalid username\n" );
1489 } else {
1490 // Allow extensions to prevent user notification
1491 // when a new message is added to their talk page
1492 // TODO: replace legacy hook! Use a listener on PageEventEmitter instead!
1493 if ( Hooks::run( 'ArticleEditUpdateNewTalk', [ &$wikiPage, $recipient ] ) ) {
1494 if ( User::isIP( $shortTitle ) ) {
1495 // An anonymous user
1496 $recipient->setNewtalk( true, $legacyRevision );
1497 } elseif ( $recipient->isLoggedIn() ) {
1498 $recipient->setNewtalk( true, $legacyRevision );
1499 } else {
1500 wfDebug( __METHOD__ . ": don't need to notify a nonexistent user\n" );
1501 }
1502 }
1503 }
1504 }
1505
1506 if ( $title->getNamespace() == NS_MEDIAWIKI
1507 && $this->getRevisionSlotsUpdate()->isModifiedSlot( SlotRecord::MAIN )
1508 ) {
1509 $mainContent = $this->isContentDeleted() ? null : $this->getRawContent( SlotRecord::MAIN );
1510
1511 $this->messageCache->updateMessageOverride( $title, $mainContent );
1512 }
1513
1514 // TODO: move onArticleCreate and onArticle into a PageEventEmitter service
1515 if ( $this->options['created'] ) {
1516 WikiPage::onArticleCreate( $title );
1517 } elseif ( $this->options['changed'] ) { // T52785
1518 WikiPage::onArticleEdit( $title, $legacyRevision, $this->getTouchedSlotRoles() );
1519 }
1520
1521 $oldRevision = $this->getParentRevision();
1522 $oldLegacyRevision = $oldRevision ? new Revision( $oldRevision ) : null;
1523
1524 // TODO: In the wiring, register a listener for this on the new PageEventEmitter
1526 $title, $oldLegacyRevision, $legacyRevision, $this->getWikiId() ?: wfWikiID()
1527 );
1528
1529 $this->doTransition( 'done' );
1530 }
1531
1546 public function doSecondaryDataUpdates( array $options = [] ) {
1547 $this->assertHasRevision( __METHOD__ );
1548 $options += [
1549 'recursive' => false,
1550 'defer' => false,
1551 'transactionTicket' => null,
1552 ];
1553 $deferValues = [ false, DeferredUpdates::PRESEND, DeferredUpdates::POSTSEND ];
1554 if ( !in_array( $options['defer'], $deferValues, true ) ) {
1555 throw new InvalidArgumentException( 'invalid value for defer: ' . $options['defer'] );
1556 }
1557 Assert::parameterType( 'integer|null', $options['transactionTicket'],
1558 '$options[\'transactionTicket\']' );
1559
1560 $updates = $this->getSecondaryDataUpdates( $options['recursive'] );
1561
1562 $triggeringUser = $this->options['triggeringUser'] ?? $this->user;
1563 if ( !$triggeringUser instanceof User ) {
1564 $triggeringUser = User::newFromIdentity( $triggeringUser );
1565 }
1566 $causeAction = $this->options['causeAction'] ?? 'unknown';
1567 $causeAgent = $this->options['causeAgent'] ?? 'unknown';
1568 $legacyRevision = new Revision( $this->revision );
1569
1570 if ( $options['defer'] === false && $options['transactionTicket'] !== null ) {
1571 // For legacy hook handlers doing updates via LinksUpdateConstructed, make sure
1572 // any pending writes they made get flushed before the doUpdate() calls below.
1573 // This avoids snapshot-clearing errors in LinksUpdate::acquirePageLock().
1574 $this->loadbalancerFactory->commitAndWaitForReplication(
1575 __METHOD__, $options['transactionTicket']
1576 );
1577 }
1578
1579 foreach ( $updates as $update ) {
1580 if ( $update instanceof DataUpdate ) {
1581 $update->setCause( $causeAction, $causeAgent );
1582 }
1583 if ( $update instanceof LinksUpdate ) {
1584 $update->setRevision( $legacyRevision );
1585 $update->setTriggeringUser( $triggeringUser );
1586 }
1587 if ( $options['defer'] === false ) {
1588 if ( $options['transactionTicket'] !== null ) {
1589 $update->setTransactionTicket( $options['transactionTicket'] );
1590 }
1591 $update->doUpdate();
1592 } else {
1593 DeferredUpdates::addUpdate( $update, $options['defer'] );
1594 }
1595 }
1596 }
1597
1598 public function doParserCacheUpdate() {
1599 $this->assertHasRevision( __METHOD__ );
1600
1601 $wikiPage = $this->getWikiPage(); // TODO: ParserCache should accept a RevisionRecord instead
1602
1603 // NOTE: this may trigger the first parsing of the new content after an edit (when not
1604 // using pre-generated stashed output).
1605 // XXX: we may want to use the PoolCounter here. This would perhaps allow the initial parse
1606 // to be performed post-send. The client could already follow a HTTP redirect to the
1607 // page view, but would then have to wait for a response until rendering is complete.
1609
1610 // Save it to the parser cache. Use the revision timestamp in the case of a
1611 // freshly saved edit, as that matches page_touched and a mismatch would trigger an
1612 // unnecessary reparse.
1613 $timestamp = $this->options['newrev'] ? $this->revision->getTimestamp()
1614 : $output->getCacheTime();
1615 $this->parserCache->save(
1617 $timestamp, $this->revision->getId()
1618 );
1619 }
1620
1621}
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for use
wfDebug( $text, $dest='all', array $context=[])
Sends a line to the debug log if enabled or, optionally, to a comment in output.
wfTimestampNow()
Convenience function; returns MediaWiki timestamp for the present time.
wfWikiID()
Get an ASCII string identifying this wiki This is used as a prefix in memcached keys.
Prepare an edit in shared cache so that it can be reused on edit.
static checkCache(Title $title, Content $content, User $user)
Check that a prepared edit is in cache and still up-to-date.
Job to add recent change entries mentioning category membership changes.
A content handler knows how do deal with a specific type of content on a wiki page.
Abstract base class for update jobs that do something with some secondary data extracted from article...
Class for managing the deferred updates.
Hooks class.
Definition Hooks.php:34
Class to handle enqueueing of background jobs.
Internationalisation code.
Definition Language.php:35
Update object handling the cleanup of links tables after a page was deleted.
Class the manages updates of *_link tables as well as similar extension-managed tables.
Represents information returned by WikiPage::prepareContentForEdit()
Mutable RevisionRecord implementation, for building new revision entries programmatically.
RenderedRevision represents the rendered representation of a revision.
Page revision base class.
getParentId()
Get parent revision ID (the original previous page revision).
getComment( $audience=self::FOR_PUBLIC, User $user=null)
Fetch revision comment, if it's available to the specified audience.
getVisibility()
Get the deletion bitfield of the revision.
getSlots()
Returns the slots defined for this revision.
getTimestamp()
MCR migration note: this replaces Revision::getTimestamp.
getSlot( $role, $audience=self::FOR_PUBLIC, User $user=null)
Returns meta-data for the given slot.
isMinor()
MCR migration note: this replaces Revision::isMinor.
getUser( $audience=self::FOR_PUBLIC, User $user=null)
Fetch revision's author's user identity, if it's available to the specified audience.
The RevisionRenderer service provides access to rendered output for revisions.
Value object representing the set of slots belonging to a revision.
Service for looking up page revisions.
Value object representing a content slot associated with a page revision.
A handle for managing updates for derived page data on edit, import, purge, etc.
getModifiedSlotRoles()
Returns the role names of the slots modified by the new revision, not including removed roles.
setRcWatchCategoryMembership( $rcWatchCategoryMembership)
static array[] $transitions
Transition table for managing the life cycle of DerivedPageDateUpdater instances.
string $stage
A stage identifier for managing the life cycle of this instance.
getParentRevision()
Returns the parent revision of the new revision wrapped by this update.
array $pageState
The state of the relevant row in page table before the edit.
doSecondaryDataUpdates(array $options=[])
Do secondary data updates (such as updating link tables).
isContentDeleted()
Whether the content is deleted and thus not visible to the public.
prepareUpdate(RevisionRecord $revision, array $options=[])
Prepare derived data updates targeting the given Revision.
string $articleCountMethod
see $wgArticleCountMethod
doTransition( $newStage)
Transition function for managing the life cycle of this instances.
isCreation()
Whether the edit creates the page.
__construct(WikiPage $wikiPage, RevisionStore $revisionStore, RevisionRenderer $revisionRenderer, ParserCache $parserCache, JobQueueGroup $jobQueueGroup, MessageCache $messageCache, Language $contLang, LBFactory $loadbalancerFactory)
getRemovedSlotRoles()
Returns the role names of the slots removed by the new revision.
getRawContent( $role)
Returns the content of the given slot, with no audience checks.
getRevisionSlotsUpdate()
Returns the RevisionSlotsUpdate for this updater.
isChange()
Whether the edit created, or should create, a new revision (that is, it's not a null-edit).
grabCurrentRevision()
Returns the revision that was the page's current revision when grabCurrentRevision() was first called...
doUpdates()
Do standard updates after page edit, purge, or import.
pageExisted()
Determines whether the page being edited already existed.
wasRedirect()
Whether the page was a redirect before the edit.
isUpdatePrepared()
Whether prepareUpdate() has been called on this instance.
getContentModel( $role)
Returns the content model of the given slot.
boolean $rcWatchCategoryMembership
see $wgRCWatchCategoryMembership
getSlots()
Returns the slots of the target revision, after PST.
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...
getTouchedSlotRoles()
Returns the role names of the slots touched by the new revision, including removed roles.
isContentPrepared()
Whether prepareUpdate() or prepareContent() have been called on this instance.
getRawSlot( $role)
Returns the slot, modified or inherited, after PST, with no audience checks applied.
$options
Stores (most of) the $options parameter of prepareUpdate().
assertTransition( $newStage)
Asserts that a transition to the given stage is possible, without performing it.
getRevision()
Returns the update's target revision - that is, the revision that will be the current revision after ...
prepareContent(User $user, RevisionSlotsUpdate $slotsUpdate, $useStash=true)
Prepare updates based on an update which has not yet been saved.
Value object representing a modification of revision slots.
getRemovedRoles()
Returns a list of removed slot roles, that is, roles removed by calling removeSlot(),...
hasSameUpdates(RevisionSlotsUpdate $other)
Returns true if $other represents the same update - that is, if all methods defined by RevisionSlotsU...
getModifiedRoles()
Returns a list of modified slot roles, that is, roles modified by calling modifySlot(),...
getModifiedSlot( $role)
Returns the SlotRecord associated with the given role, if the slot with that role was modified (and n...
static newFromRevisionSlots(RevisionSlots $newSlots, RevisionSlots $parentSlots=null)
Constructs a RevisionSlotsUpdate representing the update that turned $parentSlots into $newSlots.
isModifiedSlot( $role)
Returns whether getModifiedSlot() will return a SlotRecord for the given role.
Cache of messages that are defined by MediaWiki namespace pages or by hooks.
Set options of the Parser.
Job for pruning recent changes.
Abstraction for ResourceLoader modules which pull from wiki pages.
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...
getRevisionRecord()
Definition Revision.php:637
Database independant search index updater.
Class for handling updates to the site_stats table.
static factory(array $deltas)
Represents a title within MediaWiki.
Definition Title.php:39
equals(Title $title)
Compare with another title.
Definition Title.php:4648
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
Definition User.php:47
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
Definition User.php:592
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
Definition User.php:658
static isIP( $name)
Does the string match an anonymous IP address?
Definition User.php:971
Class representing a MediaWiki article and history.
Definition WikiPage.php:44
getRevision()
Get the latest revision.
Definition WikiPage.php:765
isRedirect()
Tests if the article content represents a redirect.
Definition WikiPage.php:612
loadPageData( $from='fromdb')
Load the object from a given source by title.
Definition WikiPage.php:467
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
An interface for generating database load balancers.
Definition LBFactory.php:39
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
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such and we might be restricted by PHP settings such as safe mode or open_basedir We cannot assume that the software even has read access anywhere useful Many shared hosts run all users web applications under the same user
Wikitext formatted, in the key only.
const NS_MEDIAWIKI
Definition Defines.php:72
const NS_USER_TALK
Definition Defines.php:67
see documentation in includes Linker php for Linker::makeImageLink or false for current used if you return false $parser
Definition hooks.txt:1873
namespace and then decline to actually register it file or subcat img or subcat $title
Definition hooks.txt:994
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
either a unescaped string or a HtmlArmor object after in associative array form externallinks $linksUpdate
Definition hooks.txt:2139
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:933
static configuration should be added through ResourceLoaderGetConfigVars instead can be used to get the real title e g db for database replication lag or jobqueue for job queue size converted to pseudo seconds It is possible to add more fields and they will be returned to the user in the API response after the basic globals have been set but before ordinary actions take place $output
Definition hooks.txt:2317
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:1818
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:37
Base interface for content objects.
Definition Content.php:34
Interface that deferrable updates should implement.
Interface for database access objects.
Interface for objects representing user identity.
The wiki should then use memcached to cache various data To use multiple just add more items to the array To increase the weight of a make its entry a array("192.168.0.1:11211", 2))
In both all secondary updates will be triggered handle like object that caches derived data representing a revision
$content