+ /**
+ * Do secondary data updates (such as updating link tables).
+ *
+ * MCR note: this method is temporarily exposed via WikiPage::doSecondaryDataUpdates.
+ *
+ * @param array $options
+ * - recursive: make the update recursive, i.e. also update pages which transclude the
+ * current page or otherwise depend on it (default: false)
+ * - defer: one of the DeferredUpdates constants, or false to run immediately after waiting
+ * for replication of the changes from the SecondaryDataUpdates hooks (default: false)
+ * - transactionTicket: a transaction ticket from LBFactory::getEmptyTransactionTicket(),
+ * only when defer is false (default: null)
+ * @since 1.32
+ */
+ public function doSecondaryDataUpdates( array $options = [] ) {
+ $this->assertHasRevision( __METHOD__ );
+ $options += [
+ 'recursive' => false,
+ 'defer' => false,
+ 'transactionTicket' => null,
+ ];
+ $deferValues = [ false, DeferredUpdates::PRESEND, DeferredUpdates::POSTSEND ];
+ if ( !in_array( $options['defer'], $deferValues, true ) ) {
+ throw new InvalidArgumentException( 'invalid value for defer: ' . $options['defer'] );
+ }
+ Assert::parameterType( 'integer|null', $options['transactionTicket'],
+ '$options[\'transactionTicket\']' );
+
+ $updates = $this->getSecondaryDataUpdates( $options['recursive'] );
+
+ $triggeringUser = $this->options['triggeringUser'] ?? $this->user;
+ if ( !$triggeringUser instanceof User ) {
+ $triggeringUser = User::newFromIdentity( $triggeringUser );
+ }
+ $causeAction = $this->options['causeAction'] ?? 'unknown';
+ $causeAgent = $this->options['causeAgent'] ?? 'unknown';
+ $legacyRevision = new Revision( $this->revision );
+
+ if ( $options['defer'] === false && $options['transactionTicket'] !== null ) {
+ // For legacy hook handlers doing updates via LinksUpdateConstructed, make sure
+ // any pending writes they made get flushed before the doUpdate() calls below.
+ // This avoids snapshot-clearing errors in LinksUpdate::acquirePageLock().
+ $this->loadbalancerFactory->commitAndWaitForReplication(
+ __METHOD__, $options['transactionTicket']
+ );
+ }
+
+ foreach ( $updates as $update ) {
+ $update->setCause( $causeAction, $causeAgent );
+ if ( $update instanceof LinksUpdate ) {
+ $update->setRevision( $legacyRevision );
+ $update->setTriggeringUser( $triggeringUser );
+ }
+ if ( $options['defer'] === false ) {
+ if ( $options['transactionTicket'] !== null ) {
+ $update->setTransactionTicket( $options['transactionTicket'] );
+ }
+ $update->doUpdate();
+ } else {
+ DeferredUpdates::addUpdate( $update, $options['defer'] );
+ }
+ }
+ }
+
+ public function doParserCacheUpdate() {
+ $this->assertHasRevision( __METHOD__ );
+
+ $wikiPage = $this->getWikiPage(); // TODO: ParserCache should accept a RevisionRecord instead
+
+ // NOTE: this may trigger the first parsing of the new content after an edit (when not
+ // using pre-generated stashed output).
+ // XXX: we may want to use the PoolCounter here. This would perhaps allow the initial parse
+ // to be performed post-send. The client could already follow a HTTP redirect to the
+ // page view, but would then have to wait for a response until rendering is complete.
+ $output = $this->getCanonicalParserOutput();
+
+ // Save it to the parser cache. Use the revision timestamp in the case of a
+ // freshly saved edit, as that matches page_touched and a mismatch would trigger an
+ // unnecessary reparse.
+ $timestamp = $this->options['changed'] ? $this->revision->getTimestamp()
+ : $output->getTimestamp();
+ $this->parserCache->save(
+ $output, $wikiPage, $this->getCanonicalParserOptions(),
+ $timestamp, $this->revision->getId()
+ );
+ }
+