34use InvalidArgumentException;
61use Wikimedia\Assert\Assert;
160 'oldrevision' =>
null,
161 'oldcountable' =>
null,
162 'oldredirect' =>
null,
163 'triggeringUser' =>
null,
166 'causeAction' =>
null,
167 'causeAgent' =>
null,
242 'knows-current' =>
true,
243 'has-content' =>
true,
244 'has-revision' =>
true,
247 'knows-current' =>
true,
248 'has-content' =>
true,
249 'has-revision' =>
true,
252 'has-content' =>
true,
253 'has-revision' =>
true,
256 'has-revision' =>
true,
312 $this->stage = $newStage;
327 if ( empty( self::$transitions[$this->stage][$newStage] ) ) {
328 throw new LogicException(
"Cannot transition from {$this->stage} to $newStage" );
361 throw new InvalidArgumentException(
'$parentId should match the parent of $revision' );
377 if ( $this->pageState
385 if ( $this->pageState
386 && $parentId !==
null
387 && $this->pageState[
'oldId'] !== $parentId
393 if ( $this->slotsUpdate
431 return $this->wikiPage->getTitle();
452 return $this->pageState[
'oldId'] > 0;
467 if ( $this->parentRevision ) {
471 if ( !$this->pageState[
'oldId'] ) {
477 $oldId = $this->
revision->getParentId();
478 $flags = $this->
useMaster() ? RevisionStore::READ_LATEST : 0;
479 $this->parentRevision = $oldId
480 ? $this->revisionStore->getRevisionById( $oldId, $flags )
507 if ( $this->pageState ) {
508 return $this->pageState[
'oldRevision'];
520 $current =
$rev ?
$rev->getRevisionRecord() :
null;
523 'oldRevision' => $current,
524 'oldId' =>
$rev ?
$rev->getId() : 0,
531 return $this->pageState[
'oldRevision'];
559 return $this->wikiPage->getId();
570 return $this->
revision->isDeleted( RevisionRecord::DELETED_TEXT );
587 return $this->
getSlots()->getSlot( $role );
599 return $this->
getRawSlot( $role )->getContent();
609 return $this->
getRawSlot( $role )->getModel();
618 return ContentHandler::getForModelID( $this->
getContentModel( $role ) );
623 return $this->wikiPage->wasLoadedFrom( self::READ_LATEST );
632 if ( !$this->
getTitle()->isContentPage() ) {
648 if ( $this->articleCountMethod ===
'link' ) {
658 $roleHandler = $this->slotRoleRegistry->getRoleHandler( $role );
659 if ( $roleHandler->supportsArticleCount() ) {
662 if (
$content->isCountable( $hasLinks ) ) {
679 return $mainContent->isRedirect();
689 $mainContent =
$rev->getContent( SlotRecord::MAIN, RevisionRecord::RAW );
691 return $mainContent->isRedirect();
723 if ( $this->slotsUpdate ) {
724 if ( !$this->
user ) {
725 throw new LogicException(
726 'Unexpected state: $this->slotsUpdate was initialized, '
727 .
'but $this->user was not.'
732 throw new LogicException(
'Can\'t call prepareContent() again for different user! '
737 if ( !$this->slotsUpdate->hasSameUpdates(
$slotsUpdate ) ) {
738 throw new LogicException(
739 'Can\'t call prepareContent() again with different slot content!'
753 $this->slotsOutput = [];
754 $this->canonicalParserOutput =
null;
757 $stashedEdit =
false;
766 $userPopts = ParserOptions::newFromUserAndLang(
$user, $this->contLang );
767 Hooks::run(
'ArticlePrepareTextForEdit', [
$wikiPage, $userPopts ] );
784 $oldCallback = $userPopts->getCurrentRevisionCallback();
785 $userPopts->setCurrentRevisionCallback(
788 $legacyRevision = new Revision( $this->revision );
789 return $legacyRevision;
791 return call_user_func( $oldCallback, $parserTitle, $parser );
796 $pstContentSlots = $this->
revision->getSlots();
801 if ( $slot->isInherited() ) {
805 } elseif ( $role === SlotRecord::MAIN && $stashedEdit ) {
807 $pstSlot = SlotRecord::newUnsaved( $role, $stashedEdit->pstContent );
811 $pstSlot = SlotRecord::newUnsaved( $role, $pstContent );
814 $pstContentSlots->setSlot( $pstSlot );
818 $pstContentSlots->removeSlot( $role );
827 if ( !$this->
options[
'changed'] ) {
847 $renderHints = [
'use-master' => $this->
useMaster(),
'audience' => RevisionRecord::RAW ];
849 if ( $stashedEdit ) {
851 $output = $stashedEdit->output;
854 $output->setCacheTime( $stashedEdit->timestamp );
856 $renderHints[
'known-revision-output'] =
$output;
861 $this->renderedRevision = $this->revisionRenderer->getRenderedRevision(
899 if ( !$this->pageState ) {
900 throw new LogicException(
901 'Must call grabCurrentRevision() or prepareContent() '
902 .
'or prepareUpdate() before calling ' . $method
909 throw new LogicException(
910 'Must call prepareContent() or prepareUpdate() before calling ' . $method
917 throw new LogicException(
918 'Must call prepareUpdate() before calling ' . $method
930 return $this->
options[
'created'];
944 return $this->
options[
'changed'];
955 if ( $this->pageState[
'oldIsRedirect'] ===
null ) {
957 $rev = $this->pageState[
'oldRevision'];
961 $this->pageState[
'oldIsRedirect'] =
false;
965 return $this->pageState[
'oldIsRedirect'];
989 if ( !$this->slotsUpdate ) {
993 $old ? $old->getSlots() :
null
1076 '$options["oldrevision"]',
1077 'must be a RevisionRecord (or Revision)'
1080 !isset(
$options[
'triggeringUser'] )
1082 '$options["triggeringUser"]',
1083 'must be a UserIdentity'
1087 throw new InvalidArgumentException(
1088 'Revision must have an ID set for it to be used with prepareUpdate()!'
1096 throw new LogicException(
1097 'Trying to re-use DerivedPageDataUpdater with revision '
1099 .
', but it\'s already bound to revision '
1100 . $this->revision->getId()
1108 throw new LogicException(
1109 'The Revision provided has mismatching content!'
1117 $oldId = $this->pageState[
'oldId'] ?? 0;
1118 $this->
options[
'newrev'] = ( $revision->
getId() !== $oldId );
1119 } elseif ( isset( $this->
options[
'oldrevision'] ) ) {
1121 $oldRev = $this->
options[
'oldrevision'];
1122 $oldId = $oldRev->getId();
1123 $this->
options[
'newrev'] = ( $revision->
getId() !== $oldId );
1128 if ( $oldId !==
null ) {
1137 $this->
options[
'changed'] =
true;
1140 $this->
options[
'changed'] =
false;
1143 throw new LogicException(
1144 'The Revision mismatches old revision ID: '
1145 .
'Old ID is ' . $oldId
1156 if ( $this->
user !==
null && $this->
options[
'changed'] && $this->slotsUpdate ) {
1159 throw new LogicException(
1160 'The Revision provided has a mismatching actor: expected '
1161 . $this->
user->getName()
1170 if ( !$this->pageState ) {
1171 $this->pageState = [
1172 'oldIsRedirect' => isset( $this->
options[
'oldredirect'] )
1173 && is_bool( $this->
options[
'oldredirect'] )
1174 ? $this->
options[
'oldredirect']
1176 'oldCountable' => isset( $this->
options[
'oldcountable'] )
1177 && is_bool( $this->
options[
'oldcountable'] )
1178 ? $this->
options[
'oldcountable']
1182 if ( $this->
options[
'changed'] ) {
1186 if ( isset( $this->
options[
'oldrevision'] ) ) {
1188 $this->pageState[
'oldRevision'] =
$rev instanceof
Revision
1195 $this->pageState[
'oldRevision'] =
$revision;
1201 ( $this->pageState[
'oldId'] === 0 ) );
1209 if ( !$this->
user ) {
1214 if ( $this->renderedRevision ) {
1215 $this->renderedRevision->updateRevision(
$revision );
1220 $this->renderedRevision = $this->revisionRenderer->getRenderedRevision(
1224 [
'use-master' => $this->
useMaster(),
'audience' => RevisionRecord::RAW ]
1247 $preparedEdit->pstContent = $this->
revision->getContent( SlotRecord::MAIN );
1248 $preparedEdit->newContent =
1251 : $this->
revision->getContent( SlotRecord::MAIN );
1252 $preparedEdit->oldContent =
null;
1254 $preparedEdit->timestamp = $preparedEdit->output->getCacheTime();
1255 $preparedEdit->format = $preparedEdit->pstContent->getDefaultFormat();
1257 return $preparedEdit;
1268 [
'generate-html' => $generateHtml ]
1313 foreach ( $this->
getSlots()->getSlotRoles()
as $role ) {
1318 $updates =
$handler->getSecondaryDataUpdates(
1324 $allUpdates = array_merge( $allUpdates, $updates );
1329 $legacyUpdates =
$content->getSecondaryDataUpdates(
1337 $legacyUpdates = array_filter( $legacyUpdates,
function ( $update ) {
1341 $allUpdates = array_merge( $allUpdates, $legacyUpdates );
1359 $content = $parentSlot->getContent();
1362 $updates =
$handler->getDeletionUpdates(
1366 $allUpdates = array_merge( $allUpdates, $updates );
1372 $legacyUpdates = array_filter( $legacyUpdates,
function ( $update ) {
1376 $allUpdates = array_merge( $allUpdates, $legacyUpdates );
1381 'RevisionDataUpdates',
1412 'recursive' => $this->
options[
'changed'],
1413 'defer' => DeferredUpdates::POSTSEND,
1417 if ( $this->rcWatchCategoryMembership
1420 && !$this->
options[
'restored']
1425 $this->jobQueueGroup->lazyPush(
1435 Hooks::run(
'ArticleEditUpdates', [ &
$wikiPage, &$editInfo, $this->
options[
'changed'] ] );
1438 if ( Hooks::run(
'ArticleEditUpdatesDeleteFromRecentchanges', [ &
$wikiPage ] ) ) {
1440 if ( mt_rand( 0, 9 ) == 0 ) {
1447 $dbKey =
$title->getPrefixedDBkey();
1448 $shortTitle =
$title->getDBkey();
1450 if ( !
$title->exists() ) {
1451 wfDebug( __METHOD__ .
": Page doesn't exist any more, bailing out\n" );
1457 if ( $this->
options[
'oldcountable'] ===
'no-change' ||
1461 } elseif ( $this->
options[
'created'] ) {
1463 } elseif ( $this->
options[
'oldcountable'] !==
null ) {
1465 - (int)$this->
options[
'oldcountable'];
1466 } elseif ( isset( $this->pageState[
'oldCountable'] ) ) {
1468 - (int)$this->pageState[
'oldCountable'];
1472 $edits = $this->
options[
'changed'] ? 1 : 0;
1473 $pages = $this->
options[
'created'] ? 1 : 0;
1476 [
'edits' => $edits,
'articles' => $good,
'pages' => $pages ]
1480 $mainSlot = $this->
revision->getSlot( SlotRecord::MAIN );
1481 if ( !$mainSlot->isInherited() && !$this->isContentDeleted() ) {
1482 DeferredUpdates::addUpdate(
new SearchUpdate( $id, $dbKey, $mainSlot->getContent() ) );
1488 if ( $this->
options[
'changed']
1490 && $shortTitle != $legacyUser->getTitleKey()
1491 && !( $this->revision->isMinor() && $legacyUser->isAllowed(
'nominornewtalk' ) )
1494 if ( !$recipient ) {
1495 wfDebug( __METHOD__ .
": invalid username\n" );
1500 if ( Hooks::run(
'ArticleEditUpdateNewTalk', [ &
$wikiPage, $recipient ] ) ) {
1503 $recipient->setNewtalk(
true, $legacyRevision );
1504 } elseif ( $recipient->isLoggedIn() ) {
1505 $recipient->setNewtalk(
true, $legacyRevision );
1507 wfDebug( __METHOD__ .
": don't need to notify a nonexistent user\n" );
1514 && $this->getRevisionSlotsUpdate()->isModifiedSlot( SlotRecord::MAIN )
1518 $this->messageCache->updateMessageOverride(
$title, $mainContent );
1522 if ( $this->
options[
'created'] ) {
1523 WikiPage::onArticleCreate(
$title );
1524 } elseif ( $this->
options[
'changed'] ) {
1529 $oldLegacyRevision = $oldRevision ?
new Revision( $oldRevision ) :
null;
1556 'recursive' =>
false,
1558 'transactionTicket' =>
null,
1560 $deferValues = [
false, DeferredUpdates::PRESEND, DeferredUpdates::POSTSEND ];
1561 if ( !in_array(
$options[
'defer'], $deferValues,
true ) ) {
1562 throw new InvalidArgumentException(
'invalid value for defer: ' .
$options[
'defer'] );
1564 Assert::parameterType(
'integer|null',
$options[
'transactionTicket'],
1565 '$options[\'transactionTicket\']' );
1570 if ( !$triggeringUser instanceof
User ) {
1573 $causeAction = $this->
options[
'causeAction'] ??
'unknown';
1574 $causeAgent = $this->
options[
'causeAgent'] ??
'unknown';
1577 if (
$options[
'defer'] ===
false &&
$options[
'transactionTicket'] !==
null ) {
1581 $this->loadbalancerFactory->commitAndWaitForReplication(
1582 __METHOD__,
$options[
'transactionTicket']
1586 foreach ( $updates
as $update ) {
1588 $update->setCause( $causeAction, $causeAgent );
1591 $update->setRevision( $legacyRevision );
1592 $update->setTriggeringUser( $triggeringUser );
1595 if (
$options[
'defer'] ===
false ) {
1597 $update->setTransactionTicket(
$options[
'transactionTicket'] );
1599 $update->doUpdate();
1601 DeferredUpdates::addUpdate( $update,
$options[
'defer'] );
1621 $timestamp = $this->
options[
'newrev'] ? $this->
revision->getTimestamp()
1623 $this->parserCache->save(
1625 $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.
static newSpec(Title $title, $revisionTimestamp)
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.
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.
__construct(WikiPage $wikiPage, RevisionStore $revisionStore, RevisionRenderer $revisionRenderer, SlotRoleRegistry $slotRoleRegistry, ParserCache $parserCache, JobQueueGroup $jobQueueGroup, MessageCache $messageCache, Language $contLang, LBFactory $loadbalancerFactory)
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.
SlotRoleRegistry $slotRoleRegistry
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, $domain)
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
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not it can be in the form of< username >< more info > e g for bot passwords intended to be added to log contexts Fields it might only if the login was with a bot password it is not rendered in wiki pages or galleries in category pages allow injecting custom HTML after the section Any uses of the hook need to handle escaping see BaseTemplate::getToolbox and BaseTemplate::makeListItem for details on the format of individual items inside of this array or by returning and letting standard HTTP rendering take place modifiable or by returning false and taking over the output modifiable modifiable after all normalizations have been except for the $wgMaxImageArea check set to true or false to override the $wgMaxImageArea check result gives extension the possibility to transform it themselves $handler
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
this hook is for auditing only or null if authentication failed before getting that far or null if we can t even determine that When $user is not null
either a unescaped string or a HtmlArmor object after in associative array form externallinks $linksUpdate
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