PluginProbe
Ultimate Store Kit / 3.1.0
Ultimate Store Kit v3.1.0
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 2.0.6 All 92 releases
ultimate-store-kit / modules / shiny-grid / module.php

module.php in Ultimate Store Kit 3.1.0, at modules/shiny-grid/module.php

268 lines 10.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace UltimateStoreKit\Modules\ShinyGrid;
4
5 use UltimateStoreKit\Base\Ultimate_Store_Kit_Module_Base;
6
7 if (!defined('ABSPATH')) {
8 exit;
9 } // Exit if accessed directly
10
11 class Module extends Ultimate_Store_Kit_Module_Base {
12
13 public static function is_active() {
14 return class_exists('woocommerce');
15 }
16
17 public function get_name() {
18 return 'usk-shiny-grid';
19 }
20
21 public function get_widgets() {
22 return ['Shiny_Grid'];
23 }
24
25 public function add_product_post_class($classes) {
26 $classes[] = 'product';
27
28 return $classes;
29 }
30
31 public function add_products_post_class_filter() {
32 \add_filter('post_class', [$this, 'add_product_post_class']);
33 }
34
35 public function remove_products_post_class_filter() {
36 \remove_filter('post_class', [$this, 'add_product_post_class']);
37 }
38
39 public function register_wc_hooks() {
40 \wc()->frontend_includes();
41 }
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
56 public function __construct() {
57 parent::__construct();
58
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);
62 }
63
64 // Load variation scripts and styles
65 \add_action('wp_enqueue_scripts', array($this, 'load_assets'));
66
67 /**
68 * Modal data
69 */
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']);
72
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']);
96 }
97
98 public function ultimate_store_kit_wc_product_quick_view_content() {
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;
101 ultimate_store_kit_wc_product_quick_view_content($product_id);
102 }
103
104 public function ultimate_store_kit_quick_view_product_images() {
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;
266 }
267 }
268