PluginProbe
Ultimate Store Kit – Store Builder Addons for Elementor, WooCommerce Store Builder, EDD Store Builder / 3.1.4
Ultimate Store Kit – Store Builder Addons for Elementor, WooCommerce Store Builder, EDD Store Builder v3.1.4
3.1.4 3.0.8 3.0.9 3.1.0 3.1.2 3.1.3 3.0.7 3.0.5 3.0.4 3.0.3 3.0.2 trunk 1.5.0 1.5.1 1.5.2 1.6.1 1.6.2 1.6.3 1.6.4 2.0.0 2.0.1 2.0.2 2.0.3 2.0.4 2.0.5 All 93 releases
← All changes | modules/shiny-grid/module.php +209 -17 2.0.03.1.4 View file →
@@ -28,48 +28,240 @@
28 28 return $classes;
29 29 }
30 30
31 31 public function add_products_post_class_filter() {
32 - add_filter('post_class', [$this, 'add_product_post_class']);
32 + \add_filter('post_class', [$this, 'add_product_post_class']);
33 33 }
34 34
35 35 public function remove_products_post_class_filter() {
36 - remove_filter('post_class', [$this, 'add_product_post_class']);
36 + \remove_filter('post_class', [$this, 'add_product_post_class']);
37 37 }
38 38
39 39 public function register_wc_hooks() {
40 - wc()->frontend_includes();
40 + \wc()->frontend_includes();
41 41 }
42 42
43 + public function load_assets() {
44 + // Load WooCommerce variation scripts for product variations
45 + \wp_enqueue_script('wc-add-to-cart-variation');
46 +
47 + // Load additional scripts for quick view functionality
48 + \wp_enqueue_script('prettyPhoto');
49 + \wp_enqueue_style('woocommerce_prettyPhoto_css');
50 + \wp_localize_script('usk-shiny-grid', 'usk_ajax_config', [
51 + 'ajax_url' => \admin_url('admin-ajax.php'),
52 + 'nonce' => \wp_create_nonce('usk_add_to_cart'),
53 + ]);
54 + }
55 +
43 56 public function __construct() {
44 -
45 57 parent::__construct();
46 58
47 - if (!empty($_REQUEST['action']) && 'elementor' === $_REQUEST['action'] && is_admin()) {
48 - add_action('init', [$this, 'register_wc_hooks'], 5);
59 + // phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only check on which editor screen is loading; nothing is written.
60 + if (!empty($_REQUEST['action']) && 'elementor' === $_REQUEST['action'] && \is_admin()) {
61 + \add_action('init', [$this, 'register_wc_hooks'], 5);
49 62 }
50 63
64 + // Load variation scripts and styles
65 + \add_action('wp_enqueue_scripts', array($this, 'load_assets'));
66 +
51 67 /**
52 68 * Modal data
53 69 */
54 - add_action('wp_ajax_nopriv_ultimate_store_kit_wc_product_quick_view_content', [$this, 'ultimate_store_kit_wc_product_quick_view_content']);
55 - add_action('wp_ajax_ultimate_store_kit_wc_product_quick_view_content', [$this, 'ultimate_store_kit_wc_product_quick_view_content']);
70 + \add_action('wp_ajax_nopriv_ultimate_store_kit_wc_product_quick_view_content', [$this, 'ultimate_store_kit_wc_product_quick_view_content']);
71 + \add_action('wp_ajax_ultimate_store_kit_wc_product_quick_view_content', [$this, 'ultimate_store_kit_wc_product_quick_view_content']);
56 72
57 - add_action('ultimate_store_kit_quick_view_product_title', 'woocommerce_template_single_title');
58 - add_action('ultimate_store_kit_quick_view_product_single_rating', 'woocommerce_template_single_rating');
59 - add_action('ultimate_store_kit_quick_view_product_single_price', 'woocommerce_template_single_price');
60 - add_action('ultimate_store_kit_quick_view_product_single_excerpt', 'woocommerce_template_single_excerpt');
61 - add_action('ultimate_store_kit_quick_view_product_single_add_to_cart', 'woocommerce_template_single_add_to_cart');
62 - add_action('ultimate_store_kit_quick_view_product_single_meta', 'woocommerce_template_single_meta');
63 - add_action('ultimate_store_kit_quick_view_product_sale_flash', 'woocommerce_show_product_sale_flash');
64 - add_action('ultimate_store_kit_quick_shiny_grid_view_product_images', [$this, 'ultimate_store_kit_quick_view_product_images']);
73 + \add_action('wp_ajax_usk_add_to_cart', [$this, 'usk_add_to_cart']);
74 + \add_action('wp_ajax_nopriv_usk_add_to_cart', [$this, 'usk_add_to_cart']);
75 +
76 + \add_action('ultimate_store_kit_quick_view_product_title', 'woocommerce_template_single_title');
77 + \add_action('ultimate_store_kit_quick_view_product_single_rating', 'woocommerce_template_single_rating');
78 + \add_action('ultimate_store_kit_quick_view_product_single_price', 'woocommerce_template_single_price');
79 + \add_action('ultimate_store_kit_quick_view_product_single_excerpt', 'woocommerce_template_single_excerpt');
80 + \add_action('ultimate_store_kit_quick_view_product_single_add_to_cart', 'woocommerce_template_single_add_to_cart');
81 + \add_action('ultimate_store_kit_quick_view_product_single_meta', 'woocommerce_template_single_meta');
82 + \add_action('ultimate_store_kit_quick_view_product_sale_flash', 'woocommerce_show_product_sale_flash');
83 + \add_action('ultimate_store_kit_quick_shiny_grid_view_product_images', [$this, 'ultimate_store_kit_quick_view_product_images']);
84 +
85 + // Get available variations AJAX
86 + \add_action('wp_ajax_usk_get_available_variations', [$this, 'usk_get_available_variations']);
87 + \add_action('wp_ajax_nopriv_usk_get_available_variations', [$this, 'usk_get_available_variations']);
88 +
89 + // Filter product class
90 + \add_action('woocommerce_before_shop_loop_item', [$this, 'add_products_post_class_filter']);
91 + \add_action('woocommerce_after_shop_loop_item', [$this, 'remove_products_post_class_filter']);
92 +
93 + // Quick view support
94 + \add_action('wp_ajax_ultimate_store_kit_wc_product_quick_view', [$this, 'ultimate_store_kit_wc_product_quick_view_content']);
95 + \add_action('wp_ajax_nopriv_ultimate_store_kit_wc_product_quick_view', [$this, 'ultimate_store_kit_wc_product_quick_view_content']);
65 96 }
66 97
67 98 public function ultimate_store_kit_wc_product_quick_view_content() {
68 - $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;
69 101 ultimate_store_kit_wc_product_quick_view_content($product_id);
70 102 }
71 103
72 104 public function ultimate_store_kit_quick_view_product_images() {
73 105 ultimate_store_kit_quick_view_product_images();
106 + }
107 +
108 +
109 + public function usk_add_to_cart() {
110 + \check_ajax_referer('usk_add_to_cart', 'nonce');
111 +
112 + $product_id = isset($_POST['product_id']) ? absint($_POST['product_id']) : 0;
113 + $variation_id = isset($_POST['variation_id']) ? absint($_POST['variation_id']) : 0;
114 + $quantity = isset($_POST['quantity']) ? absint($_POST['quantity']) : 1;
115 +
116 + // Get product and validate
117 + $product = \wc_get_product($product_id);
118 + if (!$product) {
119 + \wp_send_json_error(['message' => 'Product not found']);
120 + return;
121 + }
122 +
123 + // Add to cart based on product type
124 + try {
125 + // For variable products, we need variation ID and attributes
126 + if ($product->is_type('variable') && $variation_id) {
127 + // Validate that the variation exists
128 + $variation = \wc_get_product($variation_id);
129 + if (!$variation || $variation->get_parent_id() !== $product_id) {
130 + \wp_send_json_error(['message' => 'Invalid variation']);
131 + return;
132 + }
133 +
134 + // Extract all variation attributes from the request
135 + $variation_data = [];
136 + foreach ($_POST as $key => $value) {
137 + // Check if this is an attribute
138 + if (strpos($key, 'attribute_') === 0) {
139 + $variation_data[$key] = \sanitize_text_field($value);
140 + }
141 + }
142 +
143 + // If no attributes found, try to get them from the variation
144 + if (empty($variation_data)) {
145 + $attributes = $variation->get_attributes();
146 + if (!empty($attributes)) {
147 + foreach ($attributes as $name => $value) {
148 + if (!empty($value)) {
149 + $variation_data['attribute_' . $name] = $value;
150 + }
151 + }
152 + }
153 + }
154 +
155 + // Verify we have all required variation attributes
156 + if (empty($variation_data)) {
157 + \wp_send_json_error(['message' => 'Missing variation attributes']);
158 + return;
159 + }
160 +
161 + // Add to cart with verified data
162 + $cart_item_key = \WC()->cart->add_to_cart(
163 + $product_id,
164 + $quantity,
165 + $variation_id,
166 + $variation_data
167 + );
168 + } else {
169 + // Simple product
170 + $cart_item_key = \WC()->cart->add_to_cart($product_id, $quantity);
171 + }
172 +
173 + if ($cart_item_key) {
174 + \do_action('woocommerce_ajax_added_to_cart', $product_id);
175 +
176 + // Prepare fragments for cart update
177 + $fragments = [];
178 + \ob_start();
179 + \woocommerce_mini_cart();
180 + $mini_cart = \ob_get_clean();
181 + $fragments['div.widget_shopping_cart_content'] = $mini_cart;
182 +
183 + \wp_send_json([
184 + 'success' => true,
185 + 'fragments' => $fragments,
186 + 'cart_hash' => \WC()->cart->get_cart_hash(),
187 + 'message' => 'Product added to cart'
188 + ]);
189 + } else {
190 + $cart_error = \wc_get_notices('error');
191 + $error_message = 'Failed to add to cart';
192 +
193 + if (!empty($cart_error)) {
194 + \wc_clear_notices();
195 + $error_message = \wp_strip_all_tags($cart_error[0]['notice']);
196 + }
197 +
198 + \wp_send_json_error([
199 + 'message' => $error_message
200 + ]);
201 + }
202 + } catch (\Exception $e) {
203 + \wp_send_json_error([
204 + 'message' => $e->getMessage()
205 + ]);
206 + }
207 +
208 + exit;
209 + }
210 +
211 +
212 + /**
213 + * AJAX handler to get available variations for a product
214 + */
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
220 + if (!isset($_POST['product_id'])) {
221 + \wp_send_json_error(['message' => 'Invalid product ID']);
222 + return;
223 + }
224 +
225 + $product_id = \absint($_POST['product_id']);
226 + // phpcs:enable WordPress.Security.NonceVerification.Missing
227 + $product = \wc_get_product($product_id);
228 +
229 + if (!$product || !$product->is_type('variable')) {
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);
245 + return;
246 + }
247 +
248 + // Get all available variations
249 + $available_variations = $product->get_available_variations();
250 +
251 + // Clean up variation data to only include what's needed for attribute filtering
252 + $clean_variations = [];
253 + foreach ($available_variations as $variation) {
254 + $clean_variations[] = [
255 + 'variation_id' => $variation['variation_id'],
256 + 'attributes' => $variation['attributes'],
257 + 'is_in_stock' => $variation['is_in_stock'],
258 + 'is_purchasable' => $variation['is_purchasable'],
259 + 'image' => $variation['image'],
260 + 'price_html' => $variation['price_html'],
261 + ];
262 + }
263 +
264 + \wp_send_json_success($clean_variations);
265 + exit;
74 266 }
75 267 }