PluginProbe
bBlocks – Essential Gutenberg Blocks & Patterns Collection / 2.1.7
bBlocks – Essential Gutenberg Blocks & Patterns Collection v2.1.7
2.1.7 2.1.6 2.1.5 2.1.4 2.1.3 2.1.2 2.1.1 2.1.0 2.0.43 2.0.42 2.0.41 2.0.40 2.0.39 2.0.38 trunk 1.0 1.1 1.2 1.3 1.4 1.5 1.5.1 1.5.2 1.5.3 1.5.4 All 107 releases
b-blocks / includes / blocks / woo-add-to-cart / WooAddToCart.php

WooAddToCart.php in bBlocks – Essential Gutenberg Blocks & Patterns Collection 2.1.7, at includes/blocks/woo-add-to-cart/WooAddToCart.php

156 lines 4.4 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Woo Add to Cart — shared server logic.
4 *
5 * Provides attribute sanitization helpers and a hardened single-product
6 * add-to-cart AJAX endpoint (`bb_atc_add_to_cart`) used by the frontend
7 * `view.js`.
8 *
9 * Security model for `bb_atc_add_to_cart`:
10 * - Nonce verified on every request via check_ajax_referer().
11 * - product_id sanitized with absint() and validated against wc_get_product().
12 * - quantity sanitized with absint() and clamped to 1..99.
13 * - Only purchasable, in-stock, simple (non-variable) products are added.
14 * - All responses use wp_send_json_success / wp_send_json_error.
15 *
16 * @package bBlocks
17 */
18
19 namespace BBlocks\Inc\Blocks;
20
21 if ( ! defined( 'ABSPATH' ) ) {
22 exit;
23 }
24
25 class WooAddToCart {
26
27 /**
28 * Hook the AJAX endpoint (public + logged-in).
29 */
30 public function __construct() {
31 add_action( 'wp_ajax_bb_atc_add_to_cart', [ $this, 'ajaxAddToCart' ] );
32 add_action( 'wp_ajax_nopriv_bb_atc_add_to_cart', [ $this, 'ajaxAddToCart' ] );
33 }
34
35 /**
36 * Sanitize a CSS color value (hex, rgb/hsl, var(), or a CSS keyword).
37 *
38 * @param mixed $color Raw color.
39 * @param string $fallback Fallback when invalid.
40 * @return string
41 */
42 public static function sanitizeColor( $color, $fallback = '' ) {
43 $color = trim( (string) $color );
44 if ( '' === $color ) {
45 return $fallback;
46 }
47 if ( preg_match( '/^#([0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/', $color ) ) {
48 return $color;
49 }
50 if ( preg_match( '/^(rgb|rgba|hsl|hsla)\s*\([0-9\s,%.\/]+\)$/i', $color ) ) {
51 return $color;
52 }
53 if ( preg_match( '/^var\(\s*--[a-zA-Z0-9\-_]+\s*(,\s*[a-zA-Z0-9 #%.,\-_\/]+)?\s*\)$/', $color ) ) {
54 return $color;
55 }
56 if ( preg_match( '/^[a-zA-Z]{1,30}$/', $color ) ) {
57 return $color;
58 }
59 return $fallback;
60 }
61
62 /**
63 * Sanitize a CSS length in px (digits + "px"). Falls back when invalid.
64 *
65 * @param mixed $value Raw value (e.g. "12px" or 12).
66 * @param int $min Minimum px.
67 * @param int $max Maximum px.
68 * @param string $fallback Fallback value (e.g. "12px").
69 * @return string
70 */
71 public static function sanitizePx( $value, $min, $max, $fallback ) {
72 $digits = preg_replace( '/[^0-9]/', '', (string) $value );
73 if ( '' === $digits ) {
74 return $fallback;
75 }
76 $n = (int) $digits;
77 if ( $n < $min ) {
78 $n = $min;
79 }
80 if ( $n > $max ) {
81 $n = $max;
82 }
83 return $n . 'px';
84 }
85
86 /**
87 * Clamp a value to an integer range.
88 *
89 * @param mixed $value Raw value.
90 * @param int $min Minimum.
91 * @param int $max Maximum.
92 * @param int $fallback Fallback when non-numeric.
93 * @return int
94 */
95 public static function clampInt( $value, $min, $max, $fallback ) {
96 if ( ! is_numeric( $value ) ) {
97 return (int) $fallback;
98 }
99 $value = (int) $value;
100 if ( $value < $min ) {
101 return (int) $min;
102 }
103 if ( $value > $max ) {
104 return (int) $max;
105 }
106 return $value;
107 }
108
109 /**
110 * Handle the `bb_atc_add_to_cart` AJAX request for simple products.
111 *
112 * Returns JSON: { added, productName, cartCount, cartUrl } or an error.
113 */
114 public function ajaxAddToCart() {
115 check_ajax_referer( 'bb_atc_add_to_cart', 'nonce' );
116
117 if ( ! function_exists( 'WC' ) || ! WC()->cart ) {
118 wp_send_json_error( [ 'message' => __( 'WooCommerce is not available.', 'b-blocks' ) ] );
119 }
120
121 $productId = isset( $_POST['product_id'] ) ? absint( wp_unslash( $_POST['product_id'] ) ) : 0;
122 if ( $productId < 1 ) {
123 wp_send_json_error( [ 'message' => __( 'Invalid product.', 'b-blocks' ) ] );
124 }
125
126 $quantity = isset( $_POST['quantity'] ) ? absint( wp_unslash( $_POST['quantity'] ) ) : 1;
127 $quantity = self::clampInt( $quantity, 1, 99, 1 );
128
129 $product = wc_get_product( $productId );
130 if ( ! $product || ! is_a( $product, 'WC_Product' ) ) {
131 wp_send_json_error( [ 'message' => __( 'Product not found.', 'b-blocks' ) ] );
132 }
133
134 if ( ! $product->is_type( 'simple' ) || ! $product->is_purchasable() || ! $product->is_in_stock() ) {
135 wp_send_json_error( [ 'message' => __( 'This product cannot be added to the cart.', 'b-blocks' ) ] );
136 }
137
138 $added = WC()->cart->add_to_cart( $productId, $quantity );
139
140 if ( ! $added ) {
141 wp_send_json_error( [ 'message' => __( 'Could not add the product to the cart.', 'b-blocks' ) ] );
142 }
143
144 wp_send_json_success(
145 [
146 'added' => true,
147 'productName' => wp_strip_all_tags( $product->get_name() ),
148 'cartCount' => WC()->cart->get_cart_contents_count(),
149 'cartUrl' => function_exists( 'wc_get_cart_url' ) ? wc_get_cart_url() : '',
150 ]
151 );
152 }
153 }
154
155 new WooAddToCart();
156