PluginProbe
Ultimate Store Kit – Store Builder Addons for Elementor, WooCommerce Store Builder, EDD Store Builder / 3.0.3
Ultimate Store Kit – Store Builder Addons for Elementor, WooCommerce Store Builder, EDD Store Builder v3.0.3
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
ultimate-store-kit / templates / shiny-grid.php

shiny-grid.php in Ultimate Store Kit – Store Builder Addons for Elementor, WooCommerce Store Builder, EDD Store Builder 3.0.3, at templates/shiny-grid.php

519 lines 21.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Shiny Grid Template Class
5 *
6 * Handles rendering of product items in the Shiny Grid layout
7 */
8
9 namespace UltimateStoreKit\Templates;
10
11 use UltimateStoreKit\Traits\Global_Widget_Template;
12 use UltimateStoreKit\Classes\Utils;
13
14 class USK_Shiny_Grid_Template {
15 use Global_Widget_Template;
16
17 /**
18 * Widget settings and name
19 */
20 protected $target_widget_settings = [];
21 protected $target_widget_name = '';
22
23 /**
24 * Constructor
25 */
26 public function __construct($settings = [], $widget_name = '') {
27 $this->target_widget_settings = $settings;
28 $this->target_widget_name = $widget_name;
29 }
30
31 /**
32 * Render a single product item
33 */
34 public function render_shiny_grid_item($product, $settings) {
35 if (!$product) {
36 return;
37 }
38
39 $product_id = $product->get_id();
40 $rating_count = $product->get_rating_count();
41 $average = $product->get_average_rating();
42 $categories = wc_get_product_category_list($product_id, ' ');
43
44 $show_rating = isset($settings['show_rating']) ? $settings['show_rating'] : true;
45 $classes = $this->target_widget_name === 'shiny-grid' ? 'usk-item' : 'usk-item swiper-slide';
46 $classes .= $show_rating ? ' usk-have-rating' : '';
47 ?>
48 <div class="<?php echo esc_attr($classes); ?>" data-product-id="<?php echo esc_attr($product_id); ?>">
49 <div class="usk-item-box">
50 <?php $this->render_product_image($product); ?>
51 <div class="usk-content">
52 <?php if (isset($settings['show_variation']) && $settings['show_variation'] === 'yes'): ?>
53 <?php $this->render_product_variation($product, $settings);
54 ?>
55 <?php endif; ?>
56 <div class="usk-content-inner">
57 <?php if ($categories && (isset($settings['show_category']) ? $settings['show_category'] : true)): ?>
58 <div class="usk-category"><?php echo wp_kses_post($categories); ?></div>
59 <?php endif; ?>
60
61 <?php if (isset($settings['show_title']) ? $settings['show_title'] : true) :
62 printf(
63 '<%1$s class="title"><a href="%2$s" class="usk-title" aria-label="%4$s">%3$s</a></%1$s>',
64 esc_attr(Utils::get_valid_html_tag(isset($settings['title_tags']) ? $settings['title_tags'] : 'h3')),
65 esc_url($product->get_permalink()),
66 esc_html($product->get_title()),
67 esc_attr(sprintf('View details for %s', $product->get_title()))
68 );
69 endif; ?>
70
71 <?php if (
72 (isset($settings['show_excerpt']) ? $settings['show_excerpt'] : true)
73 && isset($settings['layout_style']) && $settings['layout_style'] === 'list'
74 ): ?>
75 <div class="usk-desc">
76 <span class="desc">
77 <?php echo wp_kses_post(wp_trim_words($product->get_short_description(), $settings['excerpt_limit'], '')); ?>
78 </span>
79 </div>
80 <?php endif; ?>
81
82 <?php if (isset($settings['show_price']) ? $settings['show_price'] : true && $product->get_price_html()): ?>
83 <div class="usk-price">
84 <?php $this->print_price_output($product->get_price_html()); ?>
85 </div>
86 <?php endif; ?>
87
88 <?php if (isset($settings['show_rating']) ? $settings['show_rating'] : true): ?>
89 <div class="usk-rating">
90 <span><?php echo wp_kses_post($this->register_global_template_wc_rating($average, $rating_count)); ?></span>
91 </div>
92 <?php endif; ?>
93 </div>
94
95 </div>
96 </div>
97 </div>
98 <?php
99 }
100
101 /**
102 * Render add to cart button
103 */
104 public function __render_add_to_cart_button($product) {
105 if (!$product) {
106 return;
107 }
108
109 // Check if product is variable and has default attributes
110 $has_default_attributes = false;
111 if ($product->is_type('variable')) {
112 $default_attributes = $product->get_default_attributes();
113 if (!empty($default_attributes)) {
114 $has_default_attributes = true;
115 }
116 }
117
118 // Set up button classes
119 $button_classes = [
120 'usk-button',
121 'product_type_' . $product->get_type(),
122 ];
123
124 // Add appropriate classes for purchasable products
125 if ($product->is_purchasable() && $product->is_in_stock() && (!$product->is_type('variable') || $has_default_attributes)) {
126 $button_classes[] = 'add_to_cart_button';
127
128 if ($product->supports('ajax_add_to_cart')) {
129 $button_classes[] = 'ajax_add_to_cart';
130 }
131 }
132
133 // Set up button attributes
134 $button_attributes = [
135 'data-product_id' => $product->get_id(),
136 'data-product_sku' => $product->get_sku(),
137 'aria-label' => $product->add_to_cart_description(),
138 // 'rel' => 'nofollow',
139 ];
140
141 // Add default attributes data for variable products
142 if ($product->is_type('variable') && $has_default_attributes) {
143 $button_attributes['data-default_attributes'] = htmlspecialchars(
144 \wp_json_encode($product->get_default_attributes()),
145 ENT_QUOTES,
146 'UTF-8'
147 );
148
149 $variation_id = $this->get_variation_id_from_attributes($product, $product->get_default_attributes());
150 if ($variation_id) {
151 $button_attributes['data-variation_id'] = $variation_id;
152
153 // Add each default attribute as separate data attributes for AJAX support
154 foreach ($default_attributes as $attribute_name => $attribute_value) {
155 $button_attributes['data-attribute_' . $attribute_name] = $attribute_value;
156 }
157
158 // If we have a default variation, use product_type_variation instead of variable
159 if (in_array('product_type_variable', $button_classes)) {
160 $key = array_search('product_type_variable', $button_classes);
161 if ($key !== false) {
162 $button_classes[$key] = 'product_type_variation';
163 }
164 }
165 }
166 }
167
168 // Create button args
169 $args = [
170 'quantity' => 1,
171 'class' => implode(' ', $button_classes),
172 'attributes' => $button_attributes,
173 ];
174
175 $args = \apply_filters('woocommerce_loop_add_to_cart_args', $args, $product);
176
177 // Clean up aria-label
178 if (isset($args['attributes']['aria-label'])) {
179 $args['attributes']['aria-label'] = \wp_strip_all_tags($args['attributes']['aria-label']);
180 }
181
182 // Determine button text
183 if ($product->is_type('variable')) {
184 $button_text = $has_default_attributes ? \__('Add to cart', 'ultimate-store-kit') : \__('Select options', 'ultimate-store-kit');
185 } else {
186 $button_text = $product->add_to_cart_text();
187 }
188
189 // Get the URL
190 if ($product->is_type('variable')) {
191 $url = 'javascript:void(0)';
192 } else {
193 $url = $product->add_to_cart_url();
194 }
195
196 // Make sure variable products use correct classes
197 if ($product->is_type('variable') && in_array('product_type_variable', $button_classes)) {
198 $key = array_search('product_type_variable', $button_classes);
199 if ($key !== false) {
200 $button_classes[$key] = 'product_type_variation';
201 }
202 }
203
204 // Output the button
205 echo \apply_filters(
206 'woocommerce_loop_add_to_cart_link',
207 sprintf(
208 '<a href="%s" data-quantity="%s" class="%s" %s>%s <i class="button-icon usk-icon-arrow-right-8"></i></a>',
209 \esc_url($url),
210 \esc_attr(isset($args['quantity']) ? $args['quantity'] : 1),
211 \esc_attr(isset($args['class']) ? $args['class'] : 'button'),
212 isset($args['attributes']) ? \wc_implode_html_attributes($args['attributes']) : '',
213 \esc_html($button_text)
214 ),
215 $product,
216 $args
217 );
218 }
219 public function render_add_to_cart_button($product, $settings) {
220 if ($product) {
221 $defaults = [
222 'quantity' => 1,
223 'class' => implode(
224 ' ',
225 array_filter(
226 [
227 'usk-button',
228 'product_type_' . $product->get_type(),
229 $product->is_purchasable() && $product->is_in_stock() ? 'add_to_cart_button' : '',
230 $product->supports('ajax_add_to_cart') && $product->is_purchasable() && $product->is_in_stock() ? 'ajax_add_to_cart' : '',
231 ]
232 )
233 ),
234 'attributes' => [
235 'data-product_id' => $product->get_id(),
236 'data-product_sku' => $product->get_sku(),
237 'aria-label' => $product->add_to_cart_description(),
238 'rel' => 'nofollow',
239 ],
240 ];
241 $args = apply_filters('woocommerce_loop_add_to_cart_args', wp_parse_args($defaults), $product);
242 if (isset($args['attributes']['aria-label'])) {
243 $args['attributes']['aria-label'] = wp_strip_all_tags($args['attributes']['aria-label']);
244 }
245
246 if (isset($settings['show_cart']) ? $settings['show_cart'] : true) {
247 echo wp_kses_post(apply_filters(
248 'woocommerce_loop_add_to_cart_link', // WPCS: XSS ok.
249 sprintf(
250 '<a href="%s" data-quantity="%s" class="%s" %s>%s <i class="button-icon usk-icon-arrow-right-8"></i></a>',
251 esc_url($product->add_to_cart_url()),
252 esc_attr(isset($args['quantity']) ? $args['quantity'] : 1),
253 esc_attr(isset($args['class']) ? $args['class'] : 'button'),
254 isset($args['attributes']) ? wc_implode_html_attributes($args['attributes']) : '',
255 esc_html($product->add_to_cart_text())
256 ),
257 $product,
258 $args
259 ));
260 }
261 };
262 }
263
264 /**
265 * Get variation ID from product attributes
266 * Uses WooCommerce data store for reliable variation finding
267 */
268 private function get_variation_id_from_attributes($product, $attributes) {
269 if (!$product || !$product->is_type('variable')) {
270 return null;
271 }
272
273 // Format attributes for WooCommerce
274 $formatted_attributes = [];
275 foreach ($attributes as $attribute_name => $attribute_value) {
276 $formatted_attributes['attribute_' . $attribute_name] = $attribute_value;
277 }
278
279 // Use WooCommerce's data store to find matching variation
280 $data_store = \WC_Data_Store::load('product');
281 $variation_id = $data_store->find_matching_product_variation($product, $formatted_attributes);
282
283 if ($variation_id) {
284 return $variation_id;
285 }
286
287 return null;
288 }
289
290 /**
291 * Render product image with hover effect and action buttons
292 */
293 public function render_product_image($product) {
294 if (!$product) {
295 return;
296 }
297
298 $tooltip_position = 'left';
299 $settings = $this->target_widget_settings;
300 $image_size = isset($settings['image_size']) ? $settings['image_size'] : 'full';
301
302 // Get main product image
303 $product_image = wp_get_attachment_image_url(get_post_thumbnail_id(), $image_size);
304
305 // if product image is empty, use placeholder
306 if (empty($product_image)) {
307 $product_image = wc_placeholder_img_src('full');
308 }
309 // Get gallery image for hover effect
310 $gallery_image_link = $product_image; // Default to main image
311 $gallery_thumbs = $product->get_gallery_image_ids();
312
313 if ($gallery_thumbs && !empty($gallery_thumbs[0])) {
314 $gallery_image_link = wp_get_attachment_image_url($gallery_thumbs[0], $image_size);
315 }
316 ?>
317 <div class="usk-image">
318 <a href="<?php echo esc_url(get_permalink()); ?>">
319 <img
320 class="img"
321 src="<?php echo esc_url($product_image); ?>"
322 alt="<?php echo esc_html(get_the_title()); ?>">
323 </a>
324 <?php $this->render_add_to_cart_button($product, $settings); ?>
325
326 <div class="usk-shoping">
327 <?php $this->register_global_template_add_to_wishlist($tooltip_position, $settings); ?>
328 <?php $this->register_global_template_add_to_compare($tooltip_position, $settings); ?>
329 <?php $this->register_global_template_quick_view($product->get_id(), $tooltip_position, $settings); ?>
330 </div>
331 <div class="usk-badge-label-wrapper">
332 <div class="usk-badge-label-content usk-flex usk-flex-column">
333 <?php $this->register_global_template_badge_label($settings); ?>
334 </div>
335
336 <!-- display product variation -->
337 </div>
338 </div>
339 <?php
340 }
341
342 /**
343 * Print price HTML with allowed tags
344 */
345 public function print_price_output($output) {
346 $allowed_tags = [
347 'del' => ['aria-hidden' => []],
348 'span' => ['class' => []],
349 'bdi' => [],
350 'ins' => [],
351 ];
352
353 if (isset($output)) {
354 echo wp_kses($output, $allowed_tags);
355 }
356 }
357
358 /**
359 * Check if swatches support is available
360 */
361 private function has_swatches_support() {
362 return class_exists('UltimateStoreKitPro\\VariationSwatches\\Swatches');
363 }
364
365 /**
366 * Render product variation options (colors, sizes)
367 * Displays variation swatches on product grid items
368 */
369 public function render_product_variation($product, $settings) {
370 if (!$product || !$product->is_type('variable')) {
371 return;
372 }
373
374 $variations = $product->get_available_variations();
375 if (empty($variations)) {
376 return;
377 }
378
379 // Check if sequential mode is enabled
380 $sequential = \apply_filters('usk_sequential_variations', isset($settings['show_variation_sequential']) && $settings['show_variation_sequential'] === 'yes');
381
382 // If Pro version with swatches is active, use that functionality
383 if ($this->has_swatches_support() && function_exists('apply_filters')) {
384 $this->render_swatches_variation($product, $variations, $sequential);
385 return;
386 }
387
388 // Otherwise use the simple variation buttons
389 $this->render_simple_variations($product, $variations, $sequential);
390 }
391
392 /**
393 * Render simple variation buttons for the free version
394 */
395 private function render_simple_variations($product, $variations, $sequential = false) {
396 $product_id = $product->get_id();
397 $attributes = $product->get_variation_attributes();
398
399 if (empty($attributes)) {
400 return;
401 }
402
403 // Add sequential data attribute if enabled
404 $sequential_attr = $sequential ? ' data-sequential="true"' : '';
405
406 echo '<div class="usk-variations-container" data-product-id="' . esc_attr($product_id) . '" data-variations-reset="true"' . $sequential_attr . '>';
407
408 foreach ($attributes as $attribute_name => $options) {
409 if (empty($options)) {
410 continue;
411 }
412
413 // Get attribute label
414 $attribute_label = wc_attribute_label($attribute_name);
415 $attribute_slug = sanitize_title($attribute_name);
416
417 echo '<div class="usk-variation-group">';
418 echo '<div class="usk-variation-options">';
419
420 foreach ($options as $option) {
421 $classes = 'usk-variation-button';
422 $option_name = apply_filters('woocommerce_variation_option_name', $option);
423
424 // Check if this is a color attribute
425 $is_color = (strpos(strtolower($attribute_slug), 'color') !== false ||
426 strpos(strtolower($attribute_label), 'color') !== false);
427
428 if ($is_color) {
429 // For color attributes, use background color and minimal text
430 // Translators: %s is the name of the color option (e.g., "Red", "Blue").
431 $aria_label = sprintf(__('Select %s', 'ultimate-store-kit'), esc_attr($option_name));
432 echo '<button type="button" class="' . esc_attr($classes . ' usk-color-button') . '"
433 data-attribute="' . esc_attr($attribute_slug) . '"
434 data-value="' . esc_attr($option) . '"
435 style="background-color: ' . esc_attr($option) . '"
436 aria-label="' . esc_attr($aria_label) . '"><span class="usk-tooltip-text">' . esc_html($option_name) . '</span>
437 </button>';
438 } else {
439 // For non-color attributes, display as regular text buttons
440 // Translators: %s is the name of the variation option (e.g., "Large", "Cotton").
441 $aria_label = sprintf(__('Select %s', 'ultimate-store-kit'), esc_attr($option_name));
442 echo '<button type="button" class="' . esc_attr($classes) . '"
443 data-attribute="' . esc_attr($attribute_slug) . '"
444 data-value="' . esc_attr($option) . '"
445 aria-label="' . esc_attr($aria_label) . '">' . esc_html($option_name) . '<span class="usk-tooltip-text">' . esc_html($option_name) . '</span>
446 </button>';
447 }
448 }
449
450 echo '</div>'; // Close .usk-variation-options
451 echo '</div>'; // Close .usk-variation-group
452 }
453
454 echo '</div>'; // Close .usk-variations-container
455 }
456
457 /**
458 * Render variation swatches using the Pro version's swatches functionality
459 */
460 private function render_swatches_variation($product, $variations, $sequential = false) {
461 $product_id = $product->get_id();
462 $attributes = $product->get_variation_attributes();
463
464 if (empty($attributes)) {
465 return;
466 }
467
468 // Add sequential data attribute if enabled
469 $sequential_attr = $sequential ? ' data-sequential="true"' : '';
470
471 echo '<div class="usk-variations-container usk-pro-swatches" data-product-id="' . esc_attr($product_id) . '" data-variations-reset="true"' . $sequential_attr . '>';
472
473 // Loop through each product attribute
474 foreach ($attributes as $attribute_name => $options) {
475 if (empty($options)) {
476 continue;
477 }
478
479 echo '<div class="usk-variation-group">';
480
481 // Build the args for the swatches
482 $args = array(
483 'options' => $options,
484 'product' => $product,
485 'attribute' => $attribute_name,
486 'name' => 'attribute_' . sanitize_title($attribute_name),
487 'selected' => isset($_REQUEST['attribute_' . sanitize_title($attribute_name)])
488 ? wc_clean(wp_unslash($_REQUEST['attribute_' . sanitize_title($attribute_name)]))
489 : $product->get_variation_default_attribute($attribute_name)
490 );
491
492 // Create a placeholder for the dropdown - this will be replaced with swatches
493 $dropdown_html = '<select id="' . esc_attr($args['name']) . '" class="' . esc_attr($args['name']) . '" name="' . esc_attr($args['name']) . '" data-attribute_name="' . esc_attr($args['name']) . '" data-show_option_none="yes" style="display:none;">';
494 $dropdown_html .= '<option value="">' . esc_html__('Choose an option', 'ultimate-store-kit') . '</option>';
495
496 if (!empty($options)) {
497 foreach ($options as $option) {
498 $dropdown_html .= '<option value="' . esc_attr($option) . '" ' . selected($args['selected'], $option, false) . '>' . esc_html(apply_filters('woocommerce_variation_option_name', $option, null, $attribute_name, $product)) . '</option>';
499 }
500 }
501
502 $dropdown_html .= '</select>';
503
504 // Apply the filter to transform the dropdown to swatches
505 if (class_exists('UltimateStoreKitPro\\VariationSwatches\\Swatches')) {
506 $swatches = \UltimateStoreKitPro\VariationSwatches\Swatches::instance();
507 $swatches_html = $swatches->swatches_html($dropdown_html, $args);
508 echo $swatches_html;
509 } else {
510 echo $dropdown_html;
511 }
512
513 echo '</div>'; // Close .usk-variation-group
514 }
515
516 echo '</div>'; // Close .usk-variations-container
517 }
518 }
519