= 70300 ) { $options = array( 'expires' => (int) $expires, 'path' => (string) $path, 'domain' => (string) $domain, 'secure' => $secure, 'httponly' => $httponly, 'samesite' => 'Lax', ); return \setcookie( $w3tc_name, (string) $w3tc_value, $options ); } return \setcookie( $w3tc_name, (string) $w3tc_value, (int) $expires, (string) $path . '; samesite=Lax', (string) $domain, $secure, $httponly ); } /** * Clears a w3tc_* cookie by setting it to an empty value with an * expiry one year in the past, with the same security flags as * `set()`. * * @since 2.10.0 * * @param string $w3tc_name Cookie name. * @param string|null $path Override path; defaults to `COOKIEPATH ?: '/'`. * @param string|null $domain Override domain; defaults to `COOKIE_DOMAIN ?: ''`. * * @return bool Result of `setcookie()`. */ public static function clear( $w3tc_name, $path = null, $domain = null ) { return self::set( $w3tc_name, '', \time() - 31536000, $path, $domain ); } /** * Returns the HMAC-derived cookie-name suffix for a given WP role. * * Mirror of the legacy `md5( NONCE_KEY . $role )` but HMAC-SHA256 * (preimage- and collision-resistant) instead of MD5, truncated to * 32 hex chars to preserve the legacy length (rejected-cookie lists, * regex patterns, and cache-key fragments that assume a 32-char * suffix continue to work). * * CRITICAL — context independence. This name is derived in two * execution contexts that MUST agree: * * - WRITE: at login, in `Generic_Plugin::check_login_action()`, * with full WordPress loaded. * - READ: in the page-cache path * (`PgCache_ContentGrabber::_can_read_cache()`), invoked inline * from `advanced-cache.php` — which `wp-settings.php` loads BEFORE * `pluggable.php` defines `wp_salt()`. * * An earlier version keyed the HMAC off `wp_salt('auth')` with a * different constant fallback when `wp_salt()` was missing. The two * contexts then produced different names, so the bypass cookie set at * login was never matched on read and role-based page-cache exclusion * silently failed — a logged-in privileged user's page got cached and * could be served to others. We therefore derive the salt ONLY from * the wp-config secret constants, which are present in both contexts. * `AUTH_KEY . AUTH_SALT` is exactly what `wp_salt('auth')` returns on * a configured install, so cookies minted by the previous * (wp_salt-based) code keep matching. * * Truncation note: 128 bits of HMAC output is sufficient for the * "is this cookie for role X?" check — there are <100 plausible WP * roles, so collision probability is negligible and preimage * resistance is what matters here. * * @since 2.10.0 * * @param string $role WordPress role slug. * * @return string 32-char lowercase hex. */ public static function role_cookie_name( $role ) { $salt = ( defined( 'AUTH_KEY' ) ? (string) AUTH_KEY : '' ) . ( defined( 'AUTH_SALT' ) ? (string) AUTH_SALT : '' ); return \substr( \hash_hmac( 'sha256', (string) $role, $salt ), 0, 32 ); } /** * Returns the legacy MD5 cookie-name suffix for a given WP role. * * Used ONLY by the read path during the one-release back-compat * window so an upgrade doesn't immediately log every cached user * back in. Writers must always use `role_cookie_name()` so that * new cookies carry the HMAC name; readers walk both forms. * * Drop this method (and the dual-read in PgCache_ContentGrabber) * in the release AFTER the one that lands this PR. * * @since 2.10.0 * * @param string $role WordPress role slug. * * @return string 32-char lowercase hex. */ public static function role_cookie_name_legacy( $role ) { $nonce_key = defined( 'NONCE_KEY' ) ? (string) NONCE_KEY : ''; return \md5( $nonce_key . (string) $role ); } }