Commit 7e9b67b17e5 for woocommerce

commit 7e9b67b17e58bbd32516f4f3c1bb28f676376c69
Author: MILLER/F <fab@millerf.com>
Date:   Tue Aug 11 10:03:46 2026 +0200

    fix: interpolate session table names instead of %i placeholder (#66372)

    * fix: interpolate session table names instead of %i placeholder

    Session queries used the %i identifier placeholder, which a $wpdb drop-in
    running on a supported WordPress version may not implement (its
    has_cap( 'identifier_placeholders' ) returns false), silently producing
    malformed queries. The session table names are trusted developer-provided
    values, so interpolate them directly into the query string instead.

    Supersedes #66262.

    Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

    * Remove unecessary variable assignation

    ---------

    Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

diff --git a/plugins/woocommerce/changelog/fix-identifier-placeholders-sessions b/plugins/woocommerce/changelog/fix-identifier-placeholders-sessions
new file mode 100644
index 00000000000..2a23759718a
--- /dev/null
+++ b/plugins/woocommerce/changelog/fix-identifier-placeholders-sessions
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fix
+
+Interpolate trusted session table names directly instead of using the `%i` placeholder, so session queries stay valid on database layers that run on a supported WordPress version but don't implement `%i`.
diff --git a/plugins/woocommerce/includes/class-wc-session-handler.php b/plugins/woocommerce/includes/class-wc-session-handler.php
index 5e18d2168eb..5c995259363 100644
--- a/plugins/woocommerce/includes/class-wc-session-handler.php
+++ b/plugins/woocommerce/includes/class-wc-session-handler.php
@@ -567,9 +567,9 @@ class WC_Session_Handler extends WC_Session {

 			$wpdb->query(
 				$wpdb->prepare(
-					'INSERT INTO %i (`session_key`, `session_value`, `session_expiry`) VALUES (%s, %s, %d)
- 					ON DUPLICATE KEY UPDATE `session_value` = VALUES(`session_value`), `session_expiry` = VALUES(`session_expiry`)',
-					$this->_table,
+					// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- trusted table name.
+					"INSERT INTO {$this->_table} (`session_key`, `session_value`, `session_expiry`) VALUES (%s, %s, %d)
+ 					ON DUPLICATE KEY UPDATE `session_value` = VALUES(`session_value`), `session_expiry` = VALUES(`session_expiry`)",
 					$this->get_customer_id(),
 					maybe_serialize( $this->_data ),
 					$this->_session_expiration
@@ -641,10 +641,10 @@ class WC_Session_Handler extends WC_Session {
 		$batch_size            = 100;
 		$deleted_entries_total = 0;
 		do {
-			$deleted_entries_count  = (int) $wpdb->query(
+			$deleted_entries_count = (int) $wpdb->query(
 				$wpdb->prepare(
-					'DELETE FROM %i WHERE session_expiry < %d ORDER BY session_expiry LIMIT %d',
-					$this->_table,
+					// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- trusted table name.
+					"DELETE FROM {$this->_table} WHERE session_expiry < %d ORDER BY session_expiry LIMIT %d",
 					time(),
 					$batch_size
 				)
@@ -676,7 +676,8 @@ class WC_Session_Handler extends WC_Session {
 		$value = wp_cache_get( $this->get_cache_prefix() . $customer_id, WC_SESSION_CACHE_GROUP );

 		if ( false === $value ) {
-			$value = $wpdb->get_var( $wpdb->prepare( 'SELECT session_value FROM %i WHERE session_key = %s', $this->_table, $customer_id ) );
+			// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- trusted table name.
+			$value = $wpdb->get_var( $wpdb->prepare( "SELECT session_value FROM {$this->_table} WHERE session_key = %s", $customer_id ) );

 			if ( is_null( $value ) ) {
 				$value = $default_value;
@@ -765,6 +766,7 @@ class WC_Session_Handler extends WC_Session {
 	 * @return bool
 	 */
 	private function session_exists( $customer_id ) {
-		return $customer_id && null !== $GLOBALS['wpdb']->get_var( $GLOBALS['wpdb']->prepare( 'SELECT session_key FROM %i WHERE session_key = %s', $this->_table, $customer_id ) );
+		// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- trusted table name.
+		return $customer_id && null !== $GLOBALS['wpdb']->get_var( $GLOBALS['wpdb']->prepare( "SELECT session_key FROM {$this->_table} WHERE session_key = %s", $customer_id ) );
 	}
 }
diff --git a/plugins/woocommerce/src/StoreApi/SessionHandler.php b/plugins/woocommerce/src/StoreApi/SessionHandler.php
index 33c05b12139..7c510a604ac 100644
--- a/plugins/woocommerce/src/StoreApi/SessionHandler.php
+++ b/plugins/woocommerce/src/StoreApi/SessionHandler.php
@@ -136,8 +136,8 @@ final class SessionHandler extends WC_Session {

 		$value = $wpdb->get_var(
 			$wpdb->prepare(
-				'SELECT session_value FROM %i WHERE session_key = %s',
-				$this->table,
+				// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- trusted table name.
+				"SELECT session_value FROM {$this->table} WHERE session_key = %s",
 				$customer_id
 			)
 		);
@@ -195,8 +195,8 @@ final class SessionHandler extends WC_Session {

 			$wpdb->query(
 				$wpdb->prepare(
-					'INSERT INTO %i (`session_key`, `session_value`, `session_expiry`) VALUES (%s, %s, %d) ON DUPLICATE KEY UPDATE `session_value` = VALUES(`session_value`), `session_expiry` = VALUES(`session_expiry`)',
-					$this->table,
+					// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- trusted table name.
+					"INSERT INTO {$this->table} (`session_key`, `session_value`, `session_expiry`) VALUES (%s, %s, %d) ON DUPLICATE KEY UPDATE `session_value` = VALUES(`session_value`), `session_expiry` = VALUES(`session_expiry`)",
 					$this->get_customer_id(),
 					maybe_serialize( $this->_data ),
 					$this->session_expiration