| @@ -55,8 +55,9 @@ | ||
| 55 | 55 | |
| 56 | 56 | public function __construct() { |
| 57 | 57 | parent::__construct(); |
| 58 | 58 | |
| 59 | + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only check on which editor screen is loading; nothing is written. | |
| 59 | 60 | if (!empty($_REQUEST['action']) && 'elementor' === $_REQUEST['action'] && \is_admin()) { |
| 60 | 61 | \add_action('init', [$this, 'register_wc_hooks'], 5); |
| 61 | 62 | } |
| 62 | 63 | |
| @@ -94,9 +95,10 @@ | ||
| 94 | 95 | \add_action('wp_ajax_nopriv_ultimate_store_kit_wc_product_quick_view', [$this, 'ultimate_store_kit_wc_product_quick_view_content']); |
| 95 | 96 | } |
| 96 | 97 | |
| 97 | 98 | public function ultimate_store_kit_wc_product_quick_view_content() { |
| 98 | - $product_id = isset($_POST['product_id']) ? sanitize_text_field($_POST['product_id']) : ''; | |
| 99 | + // phpcs:ignore WordPress.Security.NonceVerification.Missing -- Read-only public endpoint; ultimate_store_kit_wc_product_quick_view_content() rejects any product the visitor could not already view. | |
| 100 | + $product_id = isset($_POST['product_id']) ? absint(wp_unslash($_POST['product_id'])) : 0; | |
| 99 | 101 | ultimate_store_kit_wc_product_quick_view_content($product_id); |
| 100 | 102 | } |
| 101 | 103 | |
| 102 | 104 | public function ultimate_store_kit_quick_view_product_images() { |
| @@ -189,9 +191,9 @@ | ||
| 189 | 191 | $error_message = 'Failed to add to cart'; |
| 190 | 192 | |
| 191 | 193 | if (!empty($cart_error)) { |
| 192 | 194 | \wc_clear_notices(); |
| 193 | - $error_message = \strip_tags($cart_error[0]['notice']); | |
| 195 | + $error_message = \wp_strip_all_tags($cart_error[0]['notice']); | |
| 194 | 196 | } |
| 195 | 197 | |
| 196 | 198 | \wp_send_json_error([ |
| 197 | 199 | 'message' => $error_message |
| @@ -210,8 +212,12 @@ | ||
| 210 | 212 | /** |
| 211 | 213 | * AJAX handler to get available variations for a product |
| 212 | 214 | */ |
| 213 | 215 | public function usk_get_available_variations() { |
| 216 | + // Read-only endpoint returning the variation data already rendered on the | |
| 217 | + // shop page; gated on the product below rather than on a nonce, which | |
| 218 | + // cannot survive full-page caching for logged-out visitors. | |
| 219 | + // phpcs:disable WordPress.Security.NonceVerification.Missing | |
| 214 | 220 | if (!isset($_POST['product_id'])) { |
| 215 | 221 | \wp_send_json_error(['message' => 'Invalid product ID']); |
| 216 | 222 | return; |
| 217 | 223 | } |
| @@ -216,12 +222,27 @@ | ||
| 216 | 222 | return; |
| 217 | 223 | } |
| 218 | 224 | |
| 219 | 225 | $product_id = \absint($_POST['product_id']); |
| 226 | + // phpcs:enable WordPress.Security.NonceVerification.Missing | |
| 220 | 227 | $product = \wc_get_product($product_id); |
| 221 | 228 | |
| 222 | 229 | if (!$product || !$product->is_type('variable')) { |
| 223 | 230 | \wp_send_json_error(['message' => 'Not a variable product']); |
| 231 | + return; | |
| 232 | + } | |
| 233 | + | |
| 234 | + // Only expose variations of products the requester is allowed to see. | |
| 235 | + // wc_get_product() ignores post status, so private/draft/pending products | |
| 236 | + // would otherwise be readable by anyone hitting this endpoint. | |
| 237 | + if ('publish' !== $product->get_status() && !\current_user_can('read_post', $product_id)) { | |
| 238 | + \wp_send_json_error(['message' => 'Product not available'], 404); | |
| 239 | + return; | |
| 240 | + } | |
| 241 | + | |
| 242 | + // Never leak variation data (prices, SKUs, stock) out of a protected product. | |
| 243 | + if (\post_password_required($product_id)) { | |
| 244 | + \wp_send_json_error(['message' => 'Product not available'], 403); | |
| 224 | 245 | return; |
| 225 | 246 | } |
| 226 | 247 | |
| 227 | 248 | // Get all available variations |