34use InvalidArgumentException;
60use Wikimedia\Assert\Assert;
159 'oldrevision' =>
null,
160 'oldcountable' =>
null,
161 'oldredirect' =>
null,
162 'triggeringUser' =>
null,
165 'causeAction' =>
null,
166 'causeAgent' =>
null,
238 'knows-current' =>
true,
239 'has-content' =>
true,
240 'has-revision' =>
true,
243 'knows-current' =>
true,
244 'has-content' =>
true,
245 'has-revision' =>
true,
248 'has-content' =>
true,
249 'has-revision' =>
true,
252 'has-revision' =>
true,
305 $this->stage = $newStage;
320 if ( empty( self::$transitions[$this->stage][$newStage] ) ) {
321 throw new LogicException(
"Cannot transition from {$this->stage} to $newStage" );
354 throw new InvalidArgumentException(
'$parentId should match the parent of $revision' );
361 throw new InvalidArgumentException(
'$user should match the author of $revision' );
378 if ( $this->pageState
386 if ( $this->pageState
387 && $parentId !==
null
388 && $this->pageState[
'oldId'] !== $parentId
395 && $this->
revision->getUser( RevisionRecord::RAW )
396 && $this->revision->getUser( RevisionRecord::RAW )->getName() !==
$user->
getName()
403 && $this->
revision->getUser( RevisionRecord::RAW )
404 &&
$revision->
getUser( RevisionRecord::RAW )->getName() !== $this->user->getName()
410 if ( $this->slotsUpdate
448 return $this->wikiPage->getTitle();
469 return $this->pageState[
'oldId'] > 0;
484 if ( $this->parentRevision ) {
488 if ( !$this->pageState[
'oldId'] ) {
494 $oldId = $this->
revision->getParentId();
495 $flags = $this->
useMaster() ? RevisionStore::READ_LATEST : 0;
496 $this->parentRevision = $oldId
497 ? $this->revisionStore->getRevisionById( $oldId, $flags )
524 if ( $this->pageState ) {
525 return $this->pageState[
'oldRevision'];
537 $current =
$rev ?
$rev->getRevisionRecord() :
null;
540 'oldRevision' => $current,
541 'oldId' =>
$rev ?
$rev->getId() : 0,
548 return $this->pageState[
'oldRevision'];
576 return $this->wikiPage->getId();
587 return $this->
revision->isDeleted( RevisionRecord::DELETED_TEXT );
604 return $this->
getSlots()->getSlot( $role );
616 return $this->
getRawSlot( $role )->getContent();
626 return $this->
getRawSlot( $role )->getModel();
635 return ContentHandler::getForModelID( $this->
getContentModel( $role ) );
640 return $this->wikiPage->wasLoadedFrom( self::READ_LATEST );
649 if ( !$this->
getTitle()->isContentPage() ) {
665 if ( $this->articleCountMethod ===
'link' ) {
671 return $mainContent->isCountable( $hasLinks );
681 return $mainContent->isRedirect();
691 $mainContent =
$rev->getContent( SlotRecord::MAIN, RevisionRecord::RAW );
693 return $mainContent->isRedirect();
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.'
734 throw new LogicException(
'Can\'t call prepareContent() again for different user! '
739 if ( !$this->slotsUpdate->hasSameUpdates(
$slotsUpdate ) ) {
740 throw new LogicException(
741 'Can\'t call prepareContent() again with different slot content!'
755 $this->slotsOutput = [];
756 $this->canonicalParserOutput =
null;
759 $stashedEdit =
false;
768 if ( $stashedEdit ) {
770 $output = $stashedEdit->output;
773 $output->setCacheTime( $stashedEdit->timestamp );
776 $this->canonicalParserOutput =
$output;
779 $userPopts = ParserOptions::newFromUserAndLang(
$user, $this->contLang );
780 Hooks::run(
'ArticlePrepareTextForEdit', [
$wikiPage, $userPopts ] );
797 $oldCallback = $userPopts->getCurrentRevisionCallback();
798 $userPopts->setCurrentRevisionCallback(
801 $legacyRevision = new Revision( $this->revision );
802 return $legacyRevision;
804 return call_user_func( $oldCallback, $parserTitle, $parser );
809 $pstContentSlots = $this->
revision->getSlots();
814 if ( $slot->isInherited() ) {
818 } elseif ( $role === SlotRecord::MAIN && $stashedEdit ) {
820 $pstSlot = SlotRecord::newUnsaved( $role, $stashedEdit->pstContent );
824 $pstSlot = SlotRecord::newUnsaved( $role, $pstContent );
827 $pstContentSlots->setSlot( $pstSlot );
831 $pstContentSlots->removeSlot( $role );
840 if ( !$this->
options[
'changed'] ) {
885 if ( !$this->renderedRevision ) {
890 $this->renderedRevision = $this->revisionRenderer->getRenderedRevision(
894 [
'use-master' => $this->
useMaster(),
'audience' => RevisionRecord::RAW ]
902 if ( !$this->pageState ) {
903 throw new LogicException(
904 'Must call grabCurrentRevision() or prepareContent() '
905 .
'or prepareUpdate() before calling ' . $method
912 throw new LogicException(
913 'Must call prepareContent() or prepareUpdate() before calling ' . $method
920 throw new LogicException(
921 'Must call prepareUpdate() before calling ' . $method
933 return $this->
options[
'created'];
947 return $this->
options[
'changed'];
958 if ( $this->pageState[
'oldIsRedirect'] ===
null ) {
960 $rev = $this->pageState[
'oldRevision'];
964 $this->pageState[
'oldIsRedirect'] =
false;
968 return $this->pageState[
'oldIsRedirect'];
992 if ( !$this->slotsUpdate ) {
996 $old ? $old->getSlots() : null
1079 '$options["oldrevision"]',
1080 'must be a RevisionRecord (or Revision)'
1083 !isset(
$options[
'triggeringUser'] )
1085 '$options["triggeringUser"]',
1086 'must be a UserIdentity'
1090 throw new InvalidArgumentException(
1091 'Revision must have an ID set for it to be used with prepareUpdate()!'
1099 throw new LogicException(
1100 'Trying to re-use DerivedPageDataUpdater with revision '
1102 .
', but it\'s already bound to revision '
1103 . $this->revision->getId()
1111 throw new LogicException(
1112 'The Revision provided has mismatching content!'
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 );
1131 if ( $oldId !==
null ) {
1140 $this->
options[
'changed'] =
true;
1143 $this->
options[
'changed'] =
false;
1146 throw new LogicException(
1147 'The Revision mismatches old revision ID: '
1148 .
'Old ID is ' . $oldId
1159 if ( $this->
user !==
null && $this->
options[
'changed'] && $this->slotsUpdate ) {
1162 throw new LogicException(
1163 'The Revision provided has a mismatching actor: expected '
1164 . $this->
user->getName()
1173 if ( !$this->pageState ) {
1174 $this->pageState = [
1175 'oldIsRedirect' => isset( $this->
options[
'oldredirect'] )
1176 && is_bool( $this->
options[
'oldredirect'] )
1177 ? $this->
options[
'oldredirect']
1179 'oldCountable' => isset( $this->
options[
'oldcountable'] )
1180 && is_bool( $this->
options[
'oldcountable'] )
1181 ? $this->
options[
'oldcountable']
1185 if ( $this->
options[
'changed'] ) {
1189 if ( isset( $this->
options[
'oldrevision'] ) ) {
1191 $this->pageState[
'oldRevision'] =
$rev instanceof
Revision
1198 $this->pageState[
'oldRevision'] =
$revision;
1204 ( $this->pageState[
'oldId'] === 0 ) );
1212 if ( !$this->
user ) {
1217 if ( $this->renderedRevision ) {
1218 $this->renderedRevision->updateRevision(
$revision );
1237 $preparedEdit->pstContent = $this->
revision->getContent( SlotRecord::MAIN );
1238 $preparedEdit->newContent =
1241 : $this->
revision->getContent( SlotRecord::MAIN );
1242 $preparedEdit->oldContent =
null;
1244 $preparedEdit->timestamp = $preparedEdit->output->getCacheTime();
1245 $preparedEdit->format = $preparedEdit->pstContent->getDefaultFormat();
1247 return $preparedEdit;
1258 [
'generate-html' => $generateHtml ]
1303 foreach ( $this->
getSlots()->getSlotRoles()
as $role ) {
1308 $updates =
$handler->getSecondaryDataUpdates(
1314 $allUpdates = array_merge( $allUpdates, $updates );
1319 $legacyUpdates =
$content->getSecondaryDataUpdates(
1327 $legacyUpdates = array_filter( $legacyUpdates,
function ( $update ) {
1331 $allUpdates = array_merge( $allUpdates, $legacyUpdates );
1349 $content = $parentSlot->getContent();
1352 $updates =
$handler->getDeletionUpdates(
1356 $allUpdates = array_merge( $allUpdates, $updates );
1362 $legacyUpdates = array_filter( $legacyUpdates,
function ( $update ) {
1366 $allUpdates = array_merge( $allUpdates, $legacyUpdates );
1371 'RevisionDataUpdates',
1402 'recursive' => $this->
options[
'changed'],
1403 'defer' => DeferredUpdates::POSTSEND,
1407 if ( $this->rcWatchCategoryMembership
1410 && !$this->
options[
'restored']
1415 $this->jobQueueGroup->lazyPush(
1420 'revTimestamp' => $this->
revision->getTimestamp(),
1428 Hooks::run(
'ArticleEditUpdates', [ &
$wikiPage, &$editInfo, $this->
options[
'changed'] ] );
1431 if ( Hooks::run(
'ArticleEditUpdatesDeleteFromRecentchanges', [ &
$wikiPage ] ) ) {
1433 if ( mt_rand( 0, 9 ) == 0 ) {
1440 $dbKey =
$title->getPrefixedDBkey();
1441 $shortTitle =
$title->getDBkey();
1443 if ( !
$title->exists() ) {
1444 wfDebug( __METHOD__ .
": Page doesn't exist any more, bailing out\n" );
1450 if ( $this->
options[
'oldcountable'] ===
'no-change' ||
1454 } elseif ( $this->
options[
'created'] ) {
1456 } elseif ( $this->
options[
'oldcountable'] !==
null ) {
1458 - (int)$this->
options[
'oldcountable'];
1459 } elseif ( isset( $this->pageState[
'oldCountable'] ) ) {
1461 - (int)$this->pageState[
'oldCountable'];
1465 $edits = $this->
options[
'changed'] ? 1 : 0;
1466 $pages = $this->
options[
'created'] ? 1 : 0;
1469 [
'edits' => $edits,
'articles' => $good,
'pages' => $pages ]
1473 $mainSlot = $this->
revision->getSlot( SlotRecord::MAIN );
1474 if ( !$mainSlot->isInherited() && !$this->isContentDeleted() ) {
1475 DeferredUpdates::addUpdate(
new SearchUpdate( $id, $dbKey, $mainSlot->getContent() ) );
1481 if ( $this->
options[
'changed']
1483 && $shortTitle != $legacyUser->getTitleKey()
1484 && !( $this->revision->isMinor() && $legacyUser->isAllowed(
'nominornewtalk' ) )
1487 if ( !$recipient ) {
1488 wfDebug( __METHOD__ .
": invalid username\n" );
1493 if ( Hooks::run(
'ArticleEditUpdateNewTalk', [ &
$wikiPage, $recipient ] ) ) {
1496 $recipient->setNewtalk(
true, $legacyRevision );
1497 } elseif ( $recipient->isLoggedIn() ) {
1498 $recipient->setNewtalk(
true, $legacyRevision );
1500 wfDebug( __METHOD__ .
": don't need to notify a nonexistent user\n" );
1507 && $this->getRevisionSlotsUpdate()->isModifiedSlot( SlotRecord::MAIN )
1511 $this->messageCache->updateMessageOverride(
$title, $mainContent );
1515 if ( $this->
options[
'created'] ) {
1516 WikiPage::onArticleCreate(
$title );
1517 } elseif ( $this->
options[
'changed'] ) {
1522 $oldLegacyRevision = $oldRevision ?
new Revision( $oldRevision ) :
null;
1549 'recursive' =>
false,
1551 'transactionTicket' =>
null,
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'] );
1557 Assert::parameterType(
'integer|null',
$options[
'transactionTicket'],
1558 '$options[\'transactionTicket\']' );
1563 if ( !$triggeringUser instanceof
User ) {
1566 $causeAction = $this->
options[
'causeAction'] ??
'unknown';
1567 $causeAgent = $this->
options[
'causeAgent'] ??
'unknown';
1570 if (
$options[
'defer'] ===
false &&
$options[
'transactionTicket'] !==
null ) {
1574 $this->loadbalancerFactory->commitAndWaitForReplication(
1575 __METHOD__,
$options[
'transactionTicket']
1579 foreach ( $updates
as $update ) {
1581 $update->setCause( $causeAction, $causeAgent );
1584 $update->setRevision( $legacyRevision );
1585 $update->setTriggeringUser( $triggeringUser );
1587 if (
$options[
'defer'] ===
false ) {
1588 if (
$options[
'transactionTicket'] !==
null ) {
1589 $update->setTransactionTicket(
$options[
'transactionTicket'] );
1591 $update->doUpdate();
1593 DeferredUpdates::addUpdate( $update,
$options[
'defer'] );
1613 $timestamp = $this->
options[
'newrev'] ? $this->
revision->getTimestamp()
1615 $this->parserCache->save(
1617 $timestamp, $this->
revision->getId()
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.
Class to handle enqueueing of background jobs.
Internationalisation code.
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.
A handle for managing updates for derived page data on edit, import, purge, etc.
getSecondaryDataUpdates( $recursive=false)
MessageCache $messageCache
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.
revisionIsRedirect(RevisionRecord $rev)
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.
getContentHandler( $role)
prepareUpdate(RevisionRecord $revision, array $options=[])
Prepare derived data updates targeting the given Revision.
RevisionSlotsUpdate null $slotsUpdate
string $articleCountMethod
see $wgArticleCountMethod
RevisionStore $revisionStore
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)
LBFactory $loadbalancerFactory
getRemovedSlotRoles()
Returns the role names of the slots removed by the new revision.
getCanonicalParserOptions()
assertHasPageState( $method)
getRawContent( $role)
Returns the content of the given slot, with no audience checks.
getSlotParserOutput( $role, $generateHtml=true)
getRevisionSlotsUpdate()
Returns the RevisionSlotsUpdate for this updater.
RevisionRecord null $parentRevision
getCanonicalParserOutput()
RenderedRevision $renderedRevision
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.
RevisionRecord null $revision
RevisionRenderer $revisionRenderer
isContentPrepared()
Whether prepareUpdate() or prepareContent() have been called on this instance.
assertHasRevision( $method)
getRawSlot( $role)
Returns the slot, modified or inherited, after PST, with no audience checks applied.
$options
Stores (most of) the $options parameter of prepareUpdate().
JobQueueGroup $jobQueueGroup
setArticleCountMethod( $articleCountMethod)
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.
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...
Database independant search index updater.
Class for handling updates to the site_stats table.
static factory(array $deltas)
Represents a title within MediaWiki.
equals(Title $title)
Compare with another title.
The User object encapsulates all of the user-specific settings (user_id, name, rights,...
static newFromName( $name, $validate='valid')
Static factory method for creation from username.
static newFromIdentity(UserIdentity $identity)
Returns a User object corresponding to the given UserIdentity.
static isIP( $name)
Does the string match an anonymous IP address?
Class representing a MediaWiki article and history.
getRevision()
Get the latest revision.
isRedirect()
Tests if the article content represents a redirect.
loadPageData( $from='fromdb')
Load the object from a given source by title.
isCountable( $editInfo=false)
Determine whether a page would be suitable for being counted as an article in the site_stats table ba...
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.
see documentation in includes Linker php for Linker::makeImageLink or false for current used if you return false $parser
namespace and then decline to actually register it file or subcat img or subcat $title
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
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
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
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
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
Base interface for content objects.
Interface that deferrable updates should implement.
Interface for database access objects.
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