diff --git a/src/wp-activate.php b/src/wp-activate.php index ca06d2d537267..8d4720115afc1 100644 --- a/src/wp-activate.php +++ b/src/wp-activate.php @@ -38,7 +38,18 @@ $redirect_url = remove_query_arg( 'key' ); if ( remove_query_arg( false ) !== $redirect_url ) { - setcookie( $activate_cookie, $key, 0, $activate_path, COOKIE_DOMAIN, is_ssl(), true ); + wp_set_cookie( + $activate_cookie, + $key, + array( + 'expires' => 0, + 'path' => $activate_path, + 'domain' => COOKIE_DOMAIN, + 'secure' => is_ssl(), + 'httponly' => true, + 'samesite' => 'Lax', + ) + ); wp_safe_redirect( $redirect_url ); exit; } else { @@ -49,7 +60,13 @@ if ( null === $result && isset( $_COOKIE[ $activate_cookie ] ) ) { $key = $_COOKIE[ $activate_cookie ]; $result = wpmu_activate_signup( $key ); - setcookie( $activate_cookie, ' ', time() - YEAR_IN_SECONDS, $activate_path, COOKIE_DOMAIN, is_ssl(), true ); + wp_unset_cookie( + $activate_cookie, + array( + 'path' => $activate_path, + 'domain' => COOKIE_DOMAIN, + ) + ); } if ( null === $result || ( is_wp_error( $result ) && 'invalid_key' === $result->get_error_code() ) ) { diff --git a/src/wp-admin/post.php b/src/wp-admin/post.php index 3cb4a9048d536..9ff1da16fba57 100644 --- a/src/wp-admin/post.php +++ b/src/wp-admin/post.php @@ -240,7 +240,18 @@ // Session cookie flag that the post was saved. if ( isset( $_COOKIE['wp-saving-post'] ) && $_COOKIE['wp-saving-post'] === $post_id . '-check' ) { - setcookie( 'wp-saving-post', $post_id . '-saved', time() + DAY_IN_SECONDS, ADMIN_COOKIE_PATH, COOKIE_DOMAIN, is_ssl() ); + wp_set_cookie( + 'wp-saving-post', + $post_id . '-saved', + array( + 'expires' => time() + DAY_IN_SECONDS, + 'path' => ADMIN_COOKIE_PATH, + 'domain' => COOKIE_DOMAIN, + 'secure' => is_ssl(), + 'httponly' => false, + 'samesite' => 'Lax', + ) + ); } redirect_post( $post_id ); // Send user on their way while we keep working. diff --git a/src/wp-includes/class-wp-recovery-mode-cookie-service.php b/src/wp-includes/class-wp-recovery-mode-cookie-service.php index a2ee34a723aa8..ebe3193b74544 100644 --- a/src/wp-includes/class-wp-recovery-mode-cookie-service.php +++ b/src/wp-includes/class-wp-recovery-mode-cookie-service.php @@ -47,10 +47,32 @@ public function set_cookie() { $expire = time() + $length; - setcookie( RECOVERY_MODE_COOKIE, $value, $expire, COOKIEPATH, COOKIE_DOMAIN, is_ssl(), true ); + wp_set_cookie( + RECOVERY_MODE_COOKIE, + $value, + array( + 'expires' => $expire, + 'path' => COOKIEPATH, + 'domain' => COOKIE_DOMAIN, + 'secure' => is_ssl(), + 'httponly' => true, + 'samesite' => 'Lax', + ) + ); if ( COOKIEPATH !== SITECOOKIEPATH ) { - setcookie( RECOVERY_MODE_COOKIE, $value, $expire, SITECOOKIEPATH, COOKIE_DOMAIN, is_ssl(), true ); + wp_set_cookie( + RECOVERY_MODE_COOKIE, + $value, + array( + 'expires' => $expire, + 'path' => SITECOOKIEPATH, + 'domain' => COOKIE_DOMAIN, + 'secure' => is_ssl(), + 'httponly' => true, + 'samesite' => 'Lax', + ) + ); } } @@ -60,8 +82,20 @@ public function set_cookie() { * @since 5.2.0 */ public function clear_cookie() { - setcookie( RECOVERY_MODE_COOKIE, ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN ); - setcookie( RECOVERY_MODE_COOKIE, ' ', time() - YEAR_IN_SECONDS, SITECOOKIEPATH, COOKIE_DOMAIN ); + wp_unset_cookie( + RECOVERY_MODE_COOKIE, + array( + 'path' => COOKIEPATH, + 'domain' => COOKIE_DOMAIN, + ) + ); + wp_unset_cookie( + RECOVERY_MODE_COOKIE, + array( + 'path' => SITECOOKIEPATH, + 'domain' => COOKIE_DOMAIN, + ) + ); } /** diff --git a/src/wp-includes/comment.php b/src/wp-includes/comment.php index 20303dc1e7e17..773643aa72249 100644 --- a/src/wp-includes/comment.php +++ b/src/wp-includes/comment.php @@ -586,10 +586,27 @@ function wp_set_comment_cookies( $comment, $user, $cookies_consent = true ) { if ( false === $cookies_consent ) { // Remove any existing cookies. - $past = time() - YEAR_IN_SECONDS; - setcookie( 'comment_author_' . COOKIEHASH, ' ', $past, COOKIEPATH, COOKIE_DOMAIN ); - setcookie( 'comment_author_email_' . COOKIEHASH, ' ', $past, COOKIEPATH, COOKIE_DOMAIN ); - setcookie( 'comment_author_url_' . COOKIEHASH, ' ', $past, COOKIEPATH, COOKIE_DOMAIN ); + wp_unset_cookie( + 'comment_author_' . COOKIEHASH, + array( + 'path' => COOKIEPATH, + 'domain' => COOKIE_DOMAIN, + ) + ); + wp_unset_cookie( + 'comment_author_email_' . COOKIEHASH, + array( + 'path' => COOKIEPATH, + 'domain' => COOKIE_DOMAIN, + ) + ); + wp_unset_cookie( + 'comment_author_url_' . COOKIEHASH, + array( + 'path' => COOKIEPATH, + 'domain' => COOKIE_DOMAIN, + ) + ); return; } @@ -606,9 +623,42 @@ function wp_set_comment_cookies( $comment, $user, $cookies_consent = true ) { $secure = ( 'https' === parse_url( home_url(), PHP_URL_SCHEME ) ); - setcookie( 'comment_author_' . COOKIEHASH, $comment->comment_author, $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN, $secure ); - setcookie( 'comment_author_email_' . COOKIEHASH, $comment->comment_author_email, $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN, $secure ); - setcookie( 'comment_author_url_' . COOKIEHASH, esc_url( $comment->comment_author_url ), $comment_cookie_lifetime, COOKIEPATH, COOKIE_DOMAIN, $secure ); + wp_set_cookie( + 'comment_author_' . COOKIEHASH, + $comment->comment_author, + array( + 'expires' => $comment_cookie_lifetime, + 'path' => COOKIEPATH, + 'domain' => COOKIE_DOMAIN, + 'secure' => $secure, + 'httponly' => false, + 'samesite' => 'Lax', + ) + ); + wp_set_cookie( + 'comment_author_email_' . COOKIEHASH, + $comment->comment_author_email, + array( + 'expires' => $comment_cookie_lifetime, + 'path' => COOKIEPATH, + 'domain' => COOKIE_DOMAIN, + 'secure' => $secure, + 'httponly' => false, + 'samesite' => 'Lax', + ) + ); + wp_set_cookie( + 'comment_author_url_' . COOKIEHASH, + esc_url( $comment->comment_author_url ), + array( + 'expires' => $comment_cookie_lifetime, + 'path' => COOKIEPATH, + 'domain' => COOKIE_DOMAIN, + 'secure' => $secure, + 'httponly' => false, + 'samesite' => 'Lax', + ) + ); } /** diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 6a7333d317b71..81f8b14830058 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -1560,6 +1560,99 @@ function nocache_headers() { } } +/** + * Defines a cookie to be sent along with the rest of the HTTP headers. + * + * Wrapper for PHP's native setcookie() that provides a filter to adjust the + * options for all cookies in one place, and a short-circuit filter to prevent + * a cookie from being sent. + * + * The options are passed to setcookie() unchanged, so its native defaults apply + * to any that are omitted. + * + * @since 7.1.0 + * + * @param string $name The name of the cookie. + * @param string $value The value of the cookie. + * @param array $options { + * Optional. Options to pass to setcookie(). Default empty array. + * + * @type int $expires The time the cookie expires, as a Unix timestamp. + * @type string $path The path on the server in which the cookie will be available on. + * @type string $domain The (sub)domain that the cookie is available to. + * @type bool $secure Indicates that the cookie should only be transmitted over a secure HTTPS connection from the client. + * @type bool $httponly When true the cookie will be made accessible only through the HTTP protocol. + * @type string $samesite Whether the cookie should be available for cross-site requests. Accepts 'Lax', 'Strict', or 'None'. + * } + * @return bool Whether the cookie was sent successfully. + * @phpstan-param array{ + * expires?: int, + * path?: string, + * domain?: string, + * secure?: bool, + * httponly?: bool, + * samesite?: 'Lax'|'Strict'|'None', + * } $options + */ +function wp_set_cookie( string $name, string $value, array $options = array() ): bool { + /** + * Filters the options used when a cookie is sent to the browser. + * + * @since 7.1.0 + * + * @param array $options The options to pass to setcookie(). + * @param string $name The name of the cookie. + * @param string $value The value of the cookie. + */ + $options = apply_filters( 'wp_set_cookie_options', $options, $name, $value ); + + /** + * Filters whether a cookie should be sent to the browser. + * + * Returning false prevents the cookie from being sent. + * + * @since 7.1.0 + * + * @param bool $send Whether to send the cookie. Default true. + * @param string $name The name of the cookie. + * @param string $value The value of the cookie. + * @param array $options The options to pass to setcookie(). + */ + if ( ! apply_filters( 'send_cookie', true, $name, $value, $options ) ) { + return false; + } + + return setcookie( $name, $value, $options ); +} + +/** + * Removes a cookie from the browser. + * + * Sends a cookie with an empty value and an expiry time in the past, which + * instructs the browser to delete it. The path and domain must match those + * used when the cookie was originally set for the removal to take effect. + * + * @since 7.1.0 + * + * @param string $name The name of the cookie. + * @param array $options Optional. Options to pass to setcookie(). See {@see wp_set_cookie()} for the full list. + * Default empty array. + * @return bool True if the cookie was removed successfully, false otherwise. + * @phpstan-param array{ + * expires?: int, + * path?: string, + * domain?: string, + * secure?: bool, + * httponly?: bool, + * samesite?: 'Lax'|'Strict'|'None', + * } $options + */ +function wp_unset_cookie( string $name, array $options = array() ): bool { + $options['expires'] = time() - YEAR_IN_SECONDS; + + return wp_set_cookie( $name, ' ', $options ); +} + /** * Sets the HTTP headers for caching for 10 days with JavaScript content type. * diff --git a/src/wp-includes/option.php b/src/wp-includes/option.php index 8bd6a1821162e..21de3053b59ea 100644 --- a/src/wp-includes/option.php +++ b/src/wp-includes/option.php @@ -1737,8 +1737,30 @@ function wp_user_settings() { // The cookie is not set in the current browser or the saved value is newer. $secure = ( 'https' === parse_url( admin_url(), PHP_URL_SCHEME ) ); - setcookie( 'wp-settings-' . $user_id, $settings, time() + YEAR_IN_SECONDS, SITECOOKIEPATH, '', $secure ); - setcookie( 'wp-settings-time-' . $user_id, time(), time() + YEAR_IN_SECONDS, SITECOOKIEPATH, '', $secure ); + wp_set_cookie( + 'wp-settings-' . $user_id, + $settings, + array( + 'expires' => time() + YEAR_IN_SECONDS, + 'path' => SITECOOKIEPATH, + 'domain' => '', + 'secure' => $secure, + 'httponly' => false, + 'samesite' => 'Lax', + ) + ); + wp_set_cookie( + 'wp-settings-time-' . $user_id, + (string) time(), + array( + 'expires' => time() + YEAR_IN_SECONDS, + 'path' => SITECOOKIEPATH, + 'domain' => '', + 'secure' => $secure, + 'httponly' => false, + 'samesite' => 'Lax', + ) + ); $_COOKIE[ 'wp-settings-' . $user_id ] = $settings; } @@ -1914,7 +1936,12 @@ function delete_all_user_settings() { } update_user_option( $user_id, 'user-settings', '', false ); - setcookie( 'wp-settings-' . $user_id, ' ', time() - YEAR_IN_SECONDS, SITECOOKIEPATH ); + wp_unset_cookie( + 'wp-settings-' . $user_id, + array( + 'path' => SITECOOKIEPATH, + ) + ); } /** diff --git a/src/wp-includes/pluggable.php b/src/wp-includes/pluggable.php index e1c43540c8cb8..b67954933b2fd 100644 --- a/src/wp-includes/pluggable.php +++ b/src/wp-includes/pluggable.php @@ -1107,7 +1107,7 @@ function wp_set_auth_cookie( $user_id, $remember = false, $secure = '', $token = * @param bool $secure Whether the cookie should only be sent over HTTPS. * @param int $user_id User ID. */ - $secure = apply_filters( 'secure_auth_cookie', $secure, $user_id ); + $secure = (bool) apply_filters( 'secure_auth_cookie', $secure, $user_id ); /** * Filters whether the logged in cookie should only be sent over HTTPS. @@ -1118,7 +1118,7 @@ function wp_set_auth_cookie( $user_id, $remember = false, $secure = '', $token = * @param int $user_id User ID. * @param bool $secure Whether the auth cookie should only be sent over HTTPS. */ - $secure_logged_in_cookie = apply_filters( 'secure_logged_in_cookie', $secure_logged_in_cookie, $user_id, $secure ); + $secure_logged_in_cookie = (bool) apply_filters( 'secure_logged_in_cookie', $secure_logged_in_cookie, $user_id, $secure ); if ( $secure ) { $auth_cookie_name = SECURE_AUTH_COOKIE; @@ -1173,6 +1173,8 @@ function wp_set_auth_cookie( $user_id, $remember = false, $secure = '', $token = /** * Allows preventing auth cookies from actually being sent to the client. * + * See also the {@see 'send_cookie'} filter. + * * @since 4.7.4 * @since 6.2.0 The `$expire`, `$expiration`, `$user_id`, `$scheme`, and `$token` parameters were added. * @@ -1190,11 +1192,55 @@ function wp_set_auth_cookie( $user_id, $remember = false, $secure = '', $token = return; } - setcookie( $auth_cookie_name, $auth_cookie, $expire, PLUGINS_COOKIE_PATH, COOKIE_DOMAIN, $secure, true ); - setcookie( $auth_cookie_name, $auth_cookie, $expire, ADMIN_COOKIE_PATH, COOKIE_DOMAIN, $secure, true ); - setcookie( LOGGED_IN_COOKIE, $logged_in_cookie, $expire, COOKIEPATH, COOKIE_DOMAIN, $secure_logged_in_cookie, true ); + wp_set_cookie( + $auth_cookie_name, + $auth_cookie, + array( + 'expires' => $expire, + 'path' => PLUGINS_COOKIE_PATH, + 'domain' => COOKIE_DOMAIN, + 'secure' => $secure, + 'httponly' => true, + 'samesite' => 'Lax', + ) + ); + wp_set_cookie( + $auth_cookie_name, + $auth_cookie, + array( + 'expires' => $expire, + 'path' => ADMIN_COOKIE_PATH, + 'domain' => COOKIE_DOMAIN, + 'secure' => $secure, + 'httponly' => true, + 'samesite' => 'Lax', + ) + ); + wp_set_cookie( + LOGGED_IN_COOKIE, + $logged_in_cookie, + array( + 'expires' => $expire, + 'path' => COOKIEPATH, + 'domain' => COOKIE_DOMAIN, + 'secure' => $secure_logged_in_cookie, + 'httponly' => true, + 'samesite' => 'Lax', + ) + ); if ( COOKIEPATH !== SITECOOKIEPATH ) { - setcookie( LOGGED_IN_COOKIE, $logged_in_cookie, $expire, SITECOOKIEPATH, COOKIE_DOMAIN, $secure_logged_in_cookie, true ); + wp_set_cookie( + LOGGED_IN_COOKIE, + $logged_in_cookie, + array( + 'expires' => $expire, + 'path' => SITECOOKIEPATH, + 'domain' => COOKIE_DOMAIN, + 'secure' => $secure_logged_in_cookie, + 'httponly' => true, + 'samesite' => 'Lax', + ) + ); } } endif; @@ -1219,31 +1265,131 @@ function wp_clear_auth_cookie() { } // Auth cookies. - setcookie( AUTH_COOKIE, ' ', time() - YEAR_IN_SECONDS, ADMIN_COOKIE_PATH, COOKIE_DOMAIN ); - setcookie( SECURE_AUTH_COOKIE, ' ', time() - YEAR_IN_SECONDS, ADMIN_COOKIE_PATH, COOKIE_DOMAIN ); - setcookie( AUTH_COOKIE, ' ', time() - YEAR_IN_SECONDS, PLUGINS_COOKIE_PATH, COOKIE_DOMAIN ); - setcookie( SECURE_AUTH_COOKIE, ' ', time() - YEAR_IN_SECONDS, PLUGINS_COOKIE_PATH, COOKIE_DOMAIN ); - setcookie( LOGGED_IN_COOKIE, ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN ); - setcookie( LOGGED_IN_COOKIE, ' ', time() - YEAR_IN_SECONDS, SITECOOKIEPATH, COOKIE_DOMAIN ); + wp_unset_cookie( + AUTH_COOKIE, + array( + 'path' => ADMIN_COOKIE_PATH, + 'domain' => COOKIE_DOMAIN, + ) + ); + wp_unset_cookie( + SECURE_AUTH_COOKIE, + array( + 'path' => ADMIN_COOKIE_PATH, + 'domain' => COOKIE_DOMAIN, + ) + ); + wp_unset_cookie( + AUTH_COOKIE, + array( + 'path' => PLUGINS_COOKIE_PATH, + 'domain' => COOKIE_DOMAIN, + ) + ); + wp_unset_cookie( + SECURE_AUTH_COOKIE, + array( + 'path' => PLUGINS_COOKIE_PATH, + 'domain' => COOKIE_DOMAIN, + ) + ); + wp_unset_cookie( + LOGGED_IN_COOKIE, + array( + 'path' => COOKIEPATH, + 'domain' => COOKIE_DOMAIN, + ) + ); + wp_unset_cookie( + LOGGED_IN_COOKIE, + array( + 'path' => SITECOOKIEPATH, + 'domain' => COOKIE_DOMAIN, + ) + ); // Settings cookies. - setcookie( 'wp-settings-' . get_current_user_id(), ' ', time() - YEAR_IN_SECONDS, SITECOOKIEPATH ); - setcookie( 'wp-settings-time-' . get_current_user_id(), ' ', time() - YEAR_IN_SECONDS, SITECOOKIEPATH ); + wp_unset_cookie( + 'wp-settings-' . get_current_user_id(), + array( + 'path' => SITECOOKIEPATH, + ) + ); + wp_unset_cookie( + 'wp-settings-time-' . get_current_user_id(), + array( + 'path' => SITECOOKIEPATH, + ) + ); // Old cookies. - setcookie( AUTH_COOKIE, ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN ); - setcookie( AUTH_COOKIE, ' ', time() - YEAR_IN_SECONDS, SITECOOKIEPATH, COOKIE_DOMAIN ); - setcookie( SECURE_AUTH_COOKIE, ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN ); - setcookie( SECURE_AUTH_COOKIE, ' ', time() - YEAR_IN_SECONDS, SITECOOKIEPATH, COOKIE_DOMAIN ); + wp_unset_cookie( + AUTH_COOKIE, + array( + 'path' => COOKIEPATH, + 'domain' => COOKIE_DOMAIN, + ) + ); + wp_unset_cookie( + AUTH_COOKIE, + array( + 'path' => SITECOOKIEPATH, + 'domain' => COOKIE_DOMAIN, + ) + ); + wp_unset_cookie( + SECURE_AUTH_COOKIE, + array( + 'path' => COOKIEPATH, + 'domain' => COOKIE_DOMAIN, + ) + ); + wp_unset_cookie( + SECURE_AUTH_COOKIE, + array( + 'path' => SITECOOKIEPATH, + 'domain' => COOKIE_DOMAIN, + ) + ); // Even older cookies. - setcookie( USER_COOKIE, ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN ); - setcookie( PASS_COOKIE, ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN ); - setcookie( USER_COOKIE, ' ', time() - YEAR_IN_SECONDS, SITECOOKIEPATH, COOKIE_DOMAIN ); - setcookie( PASS_COOKIE, ' ', time() - YEAR_IN_SECONDS, SITECOOKIEPATH, COOKIE_DOMAIN ); + wp_unset_cookie( + USER_COOKIE, + array( + 'path' => COOKIEPATH, + 'domain' => COOKIE_DOMAIN, + ) + ); + wp_unset_cookie( + PASS_COOKIE, + array( + 'path' => COOKIEPATH, + 'domain' => COOKIE_DOMAIN, + ) + ); + wp_unset_cookie( + USER_COOKIE, + array( + 'path' => SITECOOKIEPATH, + 'domain' => COOKIE_DOMAIN, + ) + ); + wp_unset_cookie( + PASS_COOKIE, + array( + 'path' => SITECOOKIEPATH, + 'domain' => COOKIE_DOMAIN, + ) + ); // Post password cookie. - setcookie( 'wp-postpass_' . COOKIEHASH, ' ', time() - YEAR_IN_SECONDS, COOKIEPATH, COOKIE_DOMAIN ); + wp_unset_cookie( + 'wp-postpass_' . COOKIEHASH, + array( + 'path' => COOKIEPATH, + 'domain' => COOKIE_DOMAIN, + ) + ); } endif; diff --git a/src/wp-login.php b/src/wp-login.php index b6cafd3c50ae7..2431e92250dcb 100644 --- a/src/wp-login.php +++ b/src/wp-login.php @@ -522,14 +522,47 @@ function wp_login_viewport_meta() { // Set a cookie now to see if they are supported by the browser. $secure = ( 'https' === parse_url( wp_login_url(), PHP_URL_SCHEME ) ); -setcookie( TEST_COOKIE, 'WP Cookie check', 0, COOKIEPATH, COOKIE_DOMAIN, $secure, true ); +wp_set_cookie( + TEST_COOKIE, + 'WP Cookie check', + array( + 'expires' => 0, + 'path' => COOKIEPATH, + 'domain' => COOKIE_DOMAIN, + 'secure' => $secure, + 'httponly' => true, + 'samesite' => 'Lax', + ) +); if ( SITECOOKIEPATH !== COOKIEPATH ) { - setcookie( TEST_COOKIE, 'WP Cookie check', 0, SITECOOKIEPATH, COOKIE_DOMAIN, $secure, true ); + wp_set_cookie( + TEST_COOKIE, + 'WP Cookie check', + array( + 'expires' => 0, + 'path' => SITECOOKIEPATH, + 'domain' => COOKIE_DOMAIN, + 'secure' => $secure, + 'httponly' => true, + 'samesite' => 'Lax', + ) + ); } if ( isset( $_GET['wp_lang'] ) ) { - setcookie( 'wp_lang', sanitize_text_field( $_GET['wp_lang'] ), 0, COOKIEPATH, COOKIE_DOMAIN, $secure, true ); + wp_set_cookie( + 'wp_lang', + sanitize_text_field( $_GET['wp_lang'] ), + array( + 'expires' => 0, + 'path' => COOKIEPATH, + 'domain' => COOKIE_DOMAIN, + 'secure' => $secure, + 'httponly' => true, + 'samesite' => 'Lax', + ) + ); } /** @@ -786,7 +819,21 @@ function wp_login_viewport_meta() { $secure = false; } - setcookie( 'wp-postpass_' . COOKIEHASH, $hasher->HashPassword( wp_unslash( $_POST['post_password'] ) ), $expire, COOKIEPATH, COOKIE_DOMAIN, $secure ); + /** @var string $value */ + $value = $hasher->HashPassword( wp_unslash( $_POST['post_password'] ) ); + + wp_set_cookie( + 'wp-postpass_' . COOKIEHASH, + $value, + array( + 'expires' => (int) $expire, + 'path' => COOKIEPATH, + 'domain' => COOKIE_DOMAIN, + 'secure' => $secure, + 'httponly' => true, + 'samesite' => 'Lax', + ) + ); wp_safe_redirect( $redirect_to ); exit; @@ -936,7 +983,18 @@ function wp_login_viewport_meta() { if ( isset( $_GET['key'] ) && isset( $_GET['login'] ) ) { $value = sprintf( '%s:%s', wp_unslash( $_GET['login'] ), wp_unslash( $_GET['key'] ) ); - setcookie( $rp_cookie, $value, 0, $rp_path, COOKIE_DOMAIN, is_ssl(), true ); + wp_set_cookie( + $rp_cookie, + $value, + array( + 'expires' => 0, + 'path' => $rp_path, + 'domain' => COOKIE_DOMAIN, + 'secure' => is_ssl(), + 'httponly' => true, + 'samesite' => 'Lax', + ) + ); wp_safe_redirect( remove_query_arg( array( 'key', 'login' ) ) ); exit; @@ -955,7 +1013,13 @@ function wp_login_viewport_meta() { } if ( ! $user || is_wp_error( $user ) ) { - setcookie( $rp_cookie, ' ', time() - YEAR_IN_SECONDS, $rp_path, COOKIE_DOMAIN, is_ssl(), true ); + wp_unset_cookie( + $rp_cookie, + array( + 'path' => $rp_path, + 'domain' => COOKIE_DOMAIN, + ) + ); if ( $user && $user->get_error_code() === 'expired_key' ) { wp_redirect( site_url( 'wp-login.php?action=lostpassword&error=expiredkey' ) ); @@ -1485,7 +1549,13 @@ function wp_login_viewport_meta() { if ( isset( $_COOKIE[ $rp_cookie ] ) && is_string( $_COOKIE[ $rp_cookie ] ) ) { $user_login = sanitize_user( strtok( wp_unslash( $_COOKIE[ $rp_cookie ] ), ':' ) ); list( $rp_path ) = explode( '?', wp_unslash( $_SERVER['REQUEST_URI'] ) ); - setcookie( $rp_cookie, ' ', time() - YEAR_IN_SECONDS, $rp_path, COOKIE_DOMAIN, is_ssl(), true ); + wp_unset_cookie( + $rp_cookie, + array( + 'path' => $rp_path, + 'domain' => COOKIE_DOMAIN, + ) + ); } login_header( __( 'Log In' ), '', $errors ); diff --git a/tests/phpunit/includes/functions.php b/tests/phpunit/includes/functions.php index d27af5c172a7a..7afe14c7b5281 100644 --- a/tests/phpunit/includes/functions.php +++ b/tests/phpunit/includes/functions.php @@ -338,9 +338,9 @@ function _wp_rest_server_class_filter() { return 'Spy_REST_Server'; } -// Skip `setcookie` calls in auth_cookie functions due to warning: +// Skip `setcookie` calls in all functions due to warning: // Cannot modify header information - headers already sent by... -tests_add_filter( 'send_auth_cookies', '__return_false' ); +tests_add_filter( 'send_cookie', '__return_false' ); /** * After the init action has been run once, trying to re-register block types can cause diff --git a/tests/phpunit/tests/functions/wpSetCookie.php b/tests/phpunit/tests/functions/wpSetCookie.php new file mode 100644 index 0000000000000..0c29a62ff98b2 --- /dev/null +++ b/tests/phpunit/tests/functions/wpSetCookie.php @@ -0,0 +1,83 @@ + + */ + private $sent_cookies = array(); + + public function set_up() { + parent::set_up(); + + $this->sent_cookies = array(); + } + + /** + * Records a cookie sent via wp_set_cookie() and prevents it from actually + * being sent, so the value and options can be asserted. + * + * @param bool $send Whether to send the cookie. + * @param string $name The name of the cookie. + * @param string $value The value of the cookie. + * @param array $options The options passed to setcookie(). + * @return false Always false, to prevent the cookie from being sent. + */ + public function filter_capture_cookie( $send, $name, $value, $options ) { + $this->sent_cookies[] = array( + 'name' => $name, + 'value' => $value, + 'options' => $options, + ); + + return false; + } + + /** + * Forces the SameSite and Secure options, used to test the options filter. + * + * @param array $options The cookie options. + * @return array The modified options. + */ + public function filter_force_strict_samesite( $options ) { + $options['samesite'] = 'Strict'; + $options['secure'] = true; + + return $options; + } + + /** + * Tests that the `wp_set_cookie_options` filter can modify the options that + * are ultimately passed to setcookie(). + * + * @ticket 37000 + */ + public function test_wp_set_cookie_options_filter_can_modify_options() { + add_filter( 'wp_set_cookie_options', array( $this, 'filter_force_strict_samesite' ) ); + add_filter( 'send_cookie', array( $this, 'filter_capture_cookie' ), 10, 4 ); + + wp_set_cookie( + 'test_cookie', + 'test_value', + array( + 'path' => '/', + 'samesite' => 'Lax', + ) + ); + + $options = $this->sent_cookies[0]['options']; + + $this->assertSame( 'Strict', $options['samesite'] ); + $this->assertTrue( $options['secure'] ); + $this->assertSame( '/', $options['path'] ); + } +} diff --git a/tests/phpunit/tests/option/wpUserSettings.php b/tests/phpunit/tests/option/wpUserSettings.php index 44cffc730ec49..0f913098db875 100644 --- a/tests/phpunit/tests/option/wpUserSettings.php +++ b/tests/phpunit/tests/option/wpUserSettings.php @@ -9,28 +9,64 @@ class Tests_Option_wpUserSettings extends WP_UnitTestCase { /** - * Tests that PHP 8.1 "passing null to non-nullable" deprecation notice - * is not thrown for the `$domain` parameter of setcookie() calls in the function. + * Cookies captured via the `send_cookie` filter, keyed by cookie name. * - * The notice that we should not see: - * `Deprecated: setcookie(): Passing null to parameter #5 ($domain) of type string is deprecated`. + * @var array + */ + private $sent_cookies = array(); + + public function set_up() { + parent::set_up(); + + $this->sent_cookies = array(); + } + + /** + * Records a cookie sent via wp_set_cookie() and prevents it from actually + * being sent, so the value and options can be asserted without setcookie() + * attempting to modify headers that have already been sent under PHPUnit. * - * Note: This does not test the actual functioning of wp_user_settings(). - * It just and only tests for/against the deprecation notice. + * @param bool $send Whether to send the cookie. + * @param string $name The name of the cookie. + * @param string $value The value of the cookie. + * @param array $options The options passed to setcookie(). + * @return false Always false, to prevent the cookie from being sent. + */ + public function filter_send_cookie( $send, $name, $value, $options ) { + $this->sent_cookies[ $name ] = array( + 'value' => $value, + 'options' => $options, + ); + + return false; + } + + /** + * Tests that the user settings cookies are sent with the stored settings value + * and the expected cookie options. * * @ticket 54914 */ - public function test_wp_user_settings_does_not_throw_deprecation_notice_for_setcookie() { + public function test_wp_user_settings_sends_the_settings_cookies() { set_current_screen( 'edit.php' ); - wp_set_current_user( self::factory()->user->create() ); + $user_id = self::factory()->user->create(); + wp_set_current_user( $user_id ); // Verify that the function's starting conditions are satisfied. $this->assertTrue( is_admin() ); $this->assertGreaterThan( 0, get_current_user_id() ); - // `Cannot modify header information - headers already sent by...` from setcookie(). - $this->expectWarning(); + update_user_option( $user_id, 'user-settings', 'foo=bar' ); + + add_filter( 'send_cookie', array( $this, 'filter_send_cookie' ), 10, 4 ); wp_user_settings(); + + // The settings cookie is sent with the stored settings value. + $this->assertArrayHasKey( 'wp-settings-' . $user_id, $this->sent_cookies ); + $this->assertSame( 'foo=bar', $this->sent_cookies[ 'wp-settings-' . $user_id ]['value'] ); + + // The companion timestamp cookie is also sent. + $this->assertArrayHasKey( 'wp-settings-time-' . $user_id, $this->sent_cookies ); } }