Automattic\WooCommerce\Internal\EmailEditor\WCTransactionalEmails

WCEmailTemplateAutoApplier::apply_to_postpublic staticWC 10.8.0

Apply the current core template render to a single woo_email post and stamp sync meta.

Two callers, two modes:

  • Auto-applier (default): $opts['require_uncustomized'] === true. Rejects with
    `WP_Error` when the post has no stored hash, has been edited since stamping,
    or belongs to a non-sync-enabled email.
    • Reset endpoint: $opts['require_uncustomized'] === false. Unconditional rewrite.
      Non-sync-enabled emails receive a content-only reset and the return shape carries
      `null` for the four sync fields (BC contract with the pre-RSM-139 reset endpoint).

The four meta writes are skipped entirely if wp_update_post fails, so a WP_Error return leaves the post and existing meta untouched. Matches the pre-RSM-139 reset endpoint shape (see PR #64355 review on 2fa660b3b9).

Method of the class: WCEmailTemplateAutoApplier{}

No Hooks.

Returns

Array|\WP_Error. On success, an array with keys content, version, source_hash, synced_at, status. On failure, a WP_Error.

Usage

$result = WCEmailTemplateAutoApplier::apply_to_post( $email, $post_id, $opts );
$email(WC_Email) (required)
The transactional email instance.
$post_id(int) (required)
The post ID.
$opts(array)
Options. Recognised keys:
- require_uncustomized (bool, default true): see above.
Default: array()

Changelog

Since 10.8.0 Introduced.

WCEmailTemplateAutoApplier::apply_to_post() code WC 10.9.1

public static function apply_to_post( \WC_Email $email, int $post_id, array $opts = array() ) {
	$require_uncustomized = ! isset( $opts['require_uncustomized'] ) || (bool) $opts['require_uncustomized'];

	$sync_config = WCEmailTemplateSyncRegistry::get_email_sync_config( (string) $email->id );

	if ( $require_uncustomized && null === $sync_config ) {
		return new \WP_Error(
			'not_sync_enabled',
			sprintf(
				/* translators: %s: email ID */
				__( 'Email "%s" is not registered for template sync.', 'woocommerce' ),
				(string) $email->id
			)
		);
	}

	$post = get_post( $post_id );
	if ( ! $post instanceof \WP_Post || \Automattic\WooCommerce\Internal\EmailEditor\Integration::EMAIL_POST_TYPE !== $post->post_type ) {
		return new \WP_Error(
			'post_not_found',
			sprintf(
				/* translators: %d: post ID */
				__( 'No woo_email post found for ID %d.', 'woocommerce' ),
				$post_id
			)
		);
	}

	$stored_source_hash = '';
	if ( $require_uncustomized ) {
		$stored_source_hash = (string) get_post_meta( $post_id, WCEmailTemplateDivergenceDetector::SOURCE_HASH_META_KEY, true );
		if ( '' === $stored_source_hash || ! self::is_sha1_hash( $stored_source_hash ) ) {
			return new \WP_Error(
				'no_stored_hash',
				sprintf(
					/* translators: %d: post ID */
					__( 'Post %d has no stored source hash; cannot safely auto-apply.', 'woocommerce' ),
					$post_id
				)
			);
		}
	}//end if

	$canonical   = WCTransactionalEmailPostsGenerator::compute_canonical_post_content( $email );
	$source_hash = null;
	$synced_at   = null;
	$status      = null;
	$version     = null;

	self::$is_auto_applying = true;
	try {
		// Re-hash post_content immediately before the write to minimise the
		// TOCTOU gap between the snapshot and wp_update_post. The first $post
		// load above is too early — `compute_canonical_post_content` runs in
		// between and yields the window where a merchant save could otherwise
		// be silently overwritten.
		if ( $require_uncustomized ) {
			$latest_post = get_post( $post_id );
			if ( ! $latest_post instanceof \WP_Post
				|| sha1( (string) $latest_post->post_content ) !== $stored_source_hash
			) {
				return new \WP_Error(
					'post_modified_since_stamp',
					sprintf(
						/* translators: %d: post ID */
						__( 'Post %d has been modified since the last sync stamp; skipping auto-apply.', 'woocommerce' ),
						$post_id
					)
				);
			}
		}

		$updated = wp_update_post(
			array(
				'ID'           => $post_id,
				'post_content' => $canonical,
			),
			true
		);

		if ( is_wp_error( $updated ) ) {
			return $updated;
		}

		// Read back the persisted post_content. The `content_save_pre` filter
		// chain can mutate `$canonical` between the in-memory string and what
		// lands in the DB, so both the returned `content` field and the
		// stamped source hash must reflect what the database actually holds.
		// See the same note in `WCEmailTemplateSelectiveApplier::apply_selectively()`.
		$saved_post = get_post( $post_id );
		$saved_body = $saved_post instanceof \WP_Post ? (string) $saved_post->post_content : $canonical;
		$canonical  = $saved_body;

		if ( null !== $sync_config ) {
			$source_hash = sha1( $canonical );
			$synced_at   = gmdate( 'Y-m-d H:i:s' );
			$version     = (string) $sync_config['version'];

			update_post_meta( $post_id, WCEmailTemplateDivergenceDetector::VERSION_META_KEY, $version );
			update_post_meta( $post_id, WCEmailTemplateDivergenceDetector::SOURCE_HASH_META_KEY, $source_hash );
			update_post_meta( $post_id, WCEmailTemplateDivergenceDetector::LAST_SYNCED_AT_META_KEY, $synced_at );
			update_post_meta( $post_id, WCEmailTemplateDivergenceDetector::LAST_CORE_RENDER_META_KEY, $canonical );

			// Status comes from the classifier so all writers stay consistent.
			// In this path we always write canonical, so the classifier returns
			// IN_SYNC, but going through the same helper as the selective applier
			// avoids drift if a future partial-apply path is added here.
			$status = WCEmailTemplateDivergenceDetector::reclassify( $post_id );
		}//end if
	} finally {
		self::$is_auto_applying = false;
	}//end try

	// Fire `_update_applied` for the auto-applier path. Static extensions:
	// the auto-applier only acts on `core_updated_uncustomized` posts, so
	// `had_customizations` is always false and `auto_resolved` is always true.
	// Gate on `$require_uncustomized`: this method is also reused by the
	// reset endpoint (with `require_uncustomized = false`) — the reset
	// surface is not in RSM-145's event taxonomy and must not be tagged
	// as `applied_from='auto'`.
	if ( $require_uncustomized ) {
		WCEmailTemplateSyncTracker::record_auto_applied( $post_id );
	}

	return array(
		'content'     => $canonical,
		'version'     => $version,
		'source_hash' => $source_hash,
		'synced_at'   => $synced_at,
		'status'      => $status,
	);
}