[MCR] Move getSecondaryDataUpdates to the page level
[lhc/web/wiklou.git] / includes / page / WikiPage.php
index 147c9f3..bf21a56 100644 (file)
@@ -23,6 +23,7 @@
 use MediaWiki\Edit\PreparedEdit;
 use MediaWiki\Logger\LoggerFactory;
 use MediaWiki\MediaWikiServices;
+use MediaWiki\Revision\RevisionRenderer;
 use MediaWiki\Storage\DerivedPageDataUpdater;
 use MediaWiki\Storage\PageUpdater;
 use MediaWiki\Storage\RevisionRecord;
@@ -223,6 +224,13 @@ class WikiPage implements Page, IDBAccessObject {
                return MediaWikiServices::getInstance()->getRevisionStore();
        }
 
+       /**
+        * @return RevisionRenderer
+        */
+       private function getRevisionRenderer() {
+               return MediaWikiServices::getInstance()->getRevisionRenderer();
+       }
+
        /**
         * @return ParserCache
         */
@@ -761,6 +769,18 @@ class WikiPage implements Page, IDBAccessObject {
                return null;
        }
 
+       /**
+        * Get the latest revision
+        * @return RevisionRecord|null
+        */
+       public function getRevisionRecord() {
+               $this->loadLastEdit();
+               if ( $this->mLastRevision ) {
+                       return $this->mLastRevision->getRevisionRecord();
+               }
+               return null;
+       }
+
        /**
         * Get the content of the current revision. No side-effects...
         *
@@ -931,6 +951,7 @@ class WikiPage implements Page, IDBAccessObject {
                                // links.
                                $hasLinks = (bool)count( $editInfo->output->getLinks() );
                        } else {
+                               // NOTE: keep in sync with revisionRenderer::getLinkCount
                                $hasLinks = (bool)wfGetDB( DB_REPLICA )->selectField( 'pagelinks', 1,
                                        [ 'pl_from' => $this->getId() ], __METHOD__ );
                        }
@@ -1155,6 +1176,8 @@ class WikiPage implements Page, IDBAccessObject {
         * The parser cache will be used if possible. Cache misses that result
         * in parser runs are debounced with PoolCounter.
         *
+        * XXX merge this with updateParserCache()?
+        *
         * @since 1.19
         * @param ParserOptions $parserOptions ParserOptions to use for the parse operation
         * @param null|int $oldid Revision ID to get the text from, passing null or 0 will
@@ -1630,11 +1653,12 @@ class WikiPage implements Page, IDBAccessObject {
                $derivedDataUpdater = new DerivedPageDataUpdater(
                        $this, // NOTE: eventually, PageUpdater should not know about WikiPage
                        $this->getRevisionStore(),
+                       $this->getRevisionRenderer(),
                        $this->getParserCache(),
                        JobQueueGroup::singleton(),
                        MessageCache::singleton(),
                        MediaWikiServices::getInstance()->getContentLanguage(),
-                       LoggerFactory::getInstance( 'SaveParse' )
+                       MediaWikiServices::getInstance()->getDBLoadBalancerFactory()
                );
 
                $derivedDataUpdater->setRcWatchCategoryMembership( $wgRCWatchCategoryMembership );
@@ -1948,7 +1972,13 @@ class WikiPage implements Page, IDBAccessObject {
                        $updater->prepareContent( $user, $slots, $useCache );
 
                        if ( $revision ) {
-                               $updater->prepareUpdate( $revision );
+                               $updater->prepareUpdate(
+                                       $revision,
+                                       [
+                                               'causeAction' => 'prepare-edit',
+                                               'causeAgent' => $user->getName(),
+                                       ]
+                               );
                        }
                }
 
@@ -1961,7 +1991,7 @@ class WikiPage implements Page, IDBAccessObject {
         * Purges pages that include this page if the text was changed here.
         * Every 100th edit, prune the recent changes table.
         *
-        * @deprecated since 1.32, use PageUpdater::doEditUpdates instead.
+        * @deprecated since 1.32, use PageUpdater::doUpdates instead.
         *
         * @param Revision $revision
         * @param User $user User object that did the revision
@@ -1977,8 +2007,17 @@ class WikiPage implements Page, IDBAccessObject {
         *   - null: if created is false, don't update the article count; if created
         *     is true, do update the article count
         *   - 'no-change': don't update the article count, ever
+        *  - causeAction: an arbitrary string identifying the reason for the update.
+        *    See DataUpdate::getCauseAction(). (default 'edit-page')
+        *  - causeAgent: name of the user who caused the update. See DataUpdate::getCauseAgent().
+        *    (string, defaults to the passed user)
         */
        public function doEditUpdates( Revision $revision, User $user, array $options = [] ) {
+               $options += [
+                       'causeAction' => 'edit-page',
+                       'causeAgent' => $user->getName(),
+               ];
+
                $revision = $revision->getRevisionRecord();
 
                $updater = $this->getDerivedDataUpdater( $user, $revision );
@@ -1988,6 +2027,76 @@ class WikiPage implements Page, IDBAccessObject {
                $updater->doUpdates();
        }
 
+       /**
+        * Update the parser cache.
+        *
+        * @note This is a temporary workaround until there is a proper data updater class.
+        *   It will become deprecated soon.
+        *
+        * @param array $options
+        *   - causeAction: an arbitrary string identifying the reason for the update.
+        *     See DataUpdate::getCauseAction(). (default 'edit-page')
+        *   - causeAgent: name of the user who caused the update (string, defaults to the
+        *     user who created the revision)
+        * @since 1.32
+        */
+       public function updateParserCache( array $options = [] ) {
+               $revision = $this->getRevisionRecord();
+               if ( !$revision || !$revision->getId() ) {
+                       LoggerFactory::getInstance( 'wikipage' )->info(
+                               __METHOD__ . 'called with ' . ( $revision ? 'unsaved' : 'no' ) . ' revision'
+                       );
+                       return;
+               }
+               $user = User::newFromIdentity( $revision->getUser( RevisionRecord::RAW ) );
+
+               $updater = $this->getDerivedDataUpdater( $user, $revision );
+               $updater->prepareUpdate( $revision, $options );
+               $updater->doParserCacheUpdate();
+       }
+
+       /**
+        * Do secondary data updates (such as updating link tables).
+        * Secondary data updates are only a small part of the updates needed after saving
+        * a new revision; normally PageUpdater::doUpdates should be used instead (which includes
+        * secondary data updates). This method is provided for partial purges.
+        *
+        * @note This is a temporary workaround until there is a proper data updater class.
+        *   It will become deprecated soon.
+        *
+        * @param array $options
+        *   - recursive (bool, default true): whether to do a recursive update (update pages that
+        *     depend on this page, e.g. transclude it). This will set the $recursive parameter of
+        *     Content::getSecondaryDataUpdates. Typically this should be true unless the update
+        *     was something that did not really change the page, such as a null edit.
+        *   - triggeringUser: The user triggering the update (UserIdentity, defaults to the
+        *     user who created the revision)
+        *   - causeAction: an arbitrary string identifying the reason for the update.
+        *     See DataUpdate::getCauseAction(). (default 'unknown')
+        *   - causeAgent: name of the user who caused the update (string, default 'unknown')
+        *   - defer: one of the DeferredUpdates constants, or false to run immediately (default: false).
+        *     Note that even when this is set to false, some updates might still get deferred (as
+        *     some update might directly add child updates to DeferredUpdates).
+        *   - transactionTicket: a transaction ticket from LBFactory::getEmptyTransactionTicket(),
+        *     only when defer is false (default: null)
+        * @since 1.32
+        */
+       public function doSecondaryDataUpdates( array $options = [] ) {
+               $options['recursive'] = $options['recursive'] ?? true;
+               $revision = $this->getRevisionRecord();
+               if ( !$revision || !$revision->getId() ) {
+                       LoggerFactory::getInstance( 'wikipage' )->info(
+                               __METHOD__ . 'called with ' . ( $revision ? 'unsaved' : 'no' ) . ' revision'
+                       );
+                       return;
+               }
+               $user = User::newFromIdentity( $revision->getUser( RevisionRecord::RAW ) );
+
+               $updater = $this->getDerivedDataUpdater( $user, $revision );
+               $updater->prepareUpdate( $revision, $options );
+               $updater->doSecondaryDataUpdates( $options );
+       }
+
        /**
         * Update the article's restriction field, and leave a log entry.
         * This works for protection both existing and non-existing pages.
@@ -3152,6 +3261,9 @@ class WikiPage implements Page, IDBAccessObject {
 
                // Image redirects
                RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $title );
+
+               // Purge cross-wiki cache entities referencing this page
+               self::purgeInterwikiCheckKey( $title );
        }
 
        /**
@@ -3190,14 +3302,41 @@ class WikiPage implements Page, IDBAccessObject {
                // Clear file cache for this page only
                HTMLFileCache::clearFileCache( $title );
 
+               // Purge ?action=info cache
                $revid = $revision ? $revision->getId() : null;
                DeferredUpdates::addCallableUpdate( function () use ( $title, $revid ) {
                        InfoAction::invalidateCache( $title, $revid );
                } );
+
+               // Purge cross-wiki cache entities referencing this page
+               self::purgeInterwikiCheckKey( $title );
        }
 
        /**#@-*/
 
+       /**
+        * Purge the check key for cross-wiki cache entries referencing this page
+        *
+        * @param Title $title
+        */
+       private static function purgeInterwikiCheckKey( Title $title ) {
+               global $wgEnableScaryTranscluding;
+
+               if ( !$wgEnableScaryTranscluding ) {
+                       return; // @todo: perhaps this wiki is only used as a *source* for content?
+               }
+
+               DeferredUpdates::addCallableUpdate( function () use ( $title ) {
+                       $cache = MediaWikiServices::getInstance()->getMainWANObjectCache();
+                       $cache->resetCheckKey(
+                               // Do not include the namespace since there can be multiple aliases to it
+                               // due to different namespace text definitions on different wikis. This only
+                               // means that some cache invalidations happen that are not strictly needed.
+                               $cache->makeGlobalKey( 'interwiki-page', wfWikiID(), $title->getDBkey() )
+                       );
+               } );
+       }
+
        /**
         * Returns a list of categories this page is a member of.
         * Results will include hidden categories