PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.87
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.87
51.1.87 51.1.86 51.1.84 51.1.85 51.1.83 51.1.82 51.1.81 51.1.79 51.1.78 51.1.77 51.1.76 51.1.74 51.1.75 51.1.65 51.1.64 51.1.63 trunk 51.1.14 51.1.2 51.1.35 51.1.36 51.1.37 51.1.38 51.1.39 51.1.44 All 41 releases
king-addons / includes / widgets / Woo_Product_Variations / Woo_Product_Variations.php

Woo_Product_Variations.php in King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder 51.1.87, at includes/widgets/Woo_Product_Variations/Woo_Product_Variations.php

364 lines 12.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Woo Product Variations / Swatches widget.
4 *
5 * @package King_Addons
6 */
7
8 namespace King_Addons;
9
10 use Elementor\Controls_Manager;
11 use King_Addons\Woo_Builder\Context as Woo_Context;
12 use WC_Product_Variable;
13
14 if (!defined('ABSPATH')) {
15 exit;
16 }
17
18 /**
19 * Renders variation attributes selector.
20 */
21 class Woo_Product_Variations extends Abstract_Single_Widget
22 {
23 /**
24 * Widget slug.
25 *
26 * @return string
27 */
28 public function get_name(): string
29 {
30 return 'woo_product_variations';
31 }
32
33 /**
34 * Widget title.
35 *
36 * @return string
37 */
38 public function get_title(): string
39 {
40 return esc_html__('Product Variations', 'king-addons');
41 }
42
43 /**
44 * Widget icon.
45 *
46 * @return string
47 */
48 public function get_icon(): string
49 {
50 return 'king-addons-icon king-addons-woo-product-variations';
51 }
52
53 /**
54 * Widget categories.
55 *
56 * @return array<int,string>
57 */
58 public function get_categories(): array
59 {
60 return ['king-addons-woo-builder'];
61 }
62
63 /**
64 * Style dependencies.
65 *
66 * @return array<int,string>
67 */
68 public function get_style_depends(): array
69 {
70 return [KING_ADDONS_ASSETS_UNIQUE_KEY . '-woo-product-variations-style'];
71 }
72
73 /**
74 * Script dependencies.
75 *
76 * @return array<int,string>
77 */
78 public function get_script_depends(): array
79 {
80 return [
81 'wc-add-to-cart-variation',
82 KING_ADDONS_ASSETS_UNIQUE_KEY . '-woo-product-variations-script',
83 ];
84 }
85
86 /**
87 * Elementor strips <script type="text/template"> from widget HTML, so print
88 * WooCommerce variation templates in the footer for wp.template().
89 */
90 public static function print_variation_templates(): void
91 {
92 static $printed = false;
93 if ($printed || !function_exists('wc_get_template')) {
94 return;
95 }
96 $printed = true;
97 wc_get_template('single-product/add-to-cart/variation.php');
98 }
99
100 protected function register_controls(): void
101 {
102 $this->start_controls_section(
103 'section_content',
104 [
105 'label' => esc_html__('Content', 'king-addons'),
106 'tab' => Controls_Manager::TAB_CONTENT,
107 ]
108 );
109
110 $this->add_control(
111 'swatches',
112 [
113 'label' => esc_html__('Enable swatches', 'king-addons'),
114 'type' => Controls_Manager::SWITCHER,
115 'return_value' => 'yes',
116 ]
117 );
118
119 $this->add_control(
120 'swatch_source',
121 [
122 'label' => esc_html__('Swatch source', 'king-addons'),
123 'type' => Controls_Manager::SELECT,
124 'options' => [
125 'auto' => esc_html__('Term meta (color/image) fallback to text', 'king-addons'),
126 'text' => esc_html__('Text only', 'king-addons'),
127 ],
128 'default' => 'auto',
129 'condition' => [
130 'swatches' => 'yes',
131 ],
132 ]
133 );
134
135 $this->add_control(
136 'show_selected',
137 [
138 'label' => esc_html__('Show selected value', 'king-addons'),
139 'type' => Controls_Manager::SWITCHER,
140 'return_value' => 'yes',
141 'default' => 'yes',
142 ]
143 );
144
145 $this->add_control(
146 'unavailable_behavior',
147 [
148 'label' => esc_html__('Unavailable behavior', 'king-addons'),
149 'type' => Controls_Manager::SELECT,
150 'options' => [
151 'disable' => esc_html__('Disable', 'king-addons'),
152 'hide' => esc_html__('Hide', 'king-addons'),
153 ],
154 'default' => 'disable',
155 ]
156 );
157
158 $this->add_control(
159 'swatch_shape',
160 [
161 'label' => esc_html__('Swatch shape', 'king-addons'),
162 'type' => Controls_Manager::SELECT,
163 'options' => [
164 'square' => esc_html__('Square', 'king-addons'),
165 'round' => esc_html__('Round', 'king-addons'),
166 ],
167 'default' => 'square',
168 'condition' => [
169 'swatches' => 'yes',
170 ],
171 ]
172 );
173
174 $this->add_responsive_control(
175 'swatch_size',
176 [
177 'label' => esc_html__('Swatch size (px)', 'king-addons'),
178 'type' => Controls_Manager::SLIDER,
179 'range' => [
180 'px' => ['min' => 16, 'max' => 80],
181 ],
182 'selectors' => [
183 '{{WRAPPER}} .ka-woo-variations__swatch' => 'width: {{SIZE}}{{UNIT}}; height: {{SIZE}}{{UNIT}};',
184 ],
185 'condition' => [
186 'swatches' => 'yes',
187 ],
188 ]
189 );
190
191 $this->add_responsive_control(
192 'swatch_spacing',
193 [
194 'label' => esc_html__('Swatch spacing (px)', 'king-addons'),
195 'type' => Controls_Manager::SLIDER,
196 'range' => [
197 'px' => ['min' => 0, 'max' => 24],
198 ],
199 'selectors' => [
200 '{{WRAPPER}} .ka-woo-variations__swatches' => 'gap: {{SIZE}}{{UNIT}};',
201 ],
202 'condition' => [
203 'swatches' => 'yes',
204 ],
205 ]
206 );
207
208 $this->end_controls_section();
209 }
210
211 /**
212 * Find any published variable product, for editor preview only.
213 *
214 * @return WC_Product_Variable|null
215 */
216 protected function find_variable_product_for_preview(): ?WC_Product_Variable
217 {
218 if (!function_exists('wc_get_products')) {
219 return null;
220 }
221
222 $found = wc_get_products([
223 'status' => 'publish',
224 'type' => 'variable',
225 'limit' => 1,
226 'orderby' => 'date',
227 'order' => 'DESC',
228 ]);
229
230 $candidate = $found[0] ?? null;
231
232 return $candidate instanceof WC_Product_Variable ? $candidate : null;
233 }
234
235 protected function render(): void
236 {
237 if (
238 !class_exists('WooCommerce') ||
239 !function_exists('wc_attribute_taxonomy_name') ||
240 !function_exists('wc_get_product_terms') ||
241 !function_exists('wc_attribute_label') ||
242 !function_exists('wc_get_template')
243 ) {
244 return;
245 }
246
247 $product = $this->get_product();
248 if (!$product) {
249 $this->render_missing_product_notice();
250 return;
251 }
252
253 $is_editor = class_exists(Woo_Context::class) && Woo_Context::is_editor();
254
255 if (!$product instanceof WC_Product_Variable && $is_editor) {
256 // The builder picks the preview product on its own and the author
257 // cannot choose it, so a store whose newest product is simple would
258 // show this widget as broken. Borrow a variable product to preview.
259 $preview = $this->find_variable_product_for_preview();
260 if ($preview instanceof WC_Product_Variable) {
261 $GLOBALS['product'] = $preview;
262 $product = $preview;
263 }
264 }
265
266 if (!$product instanceof WC_Product_Variable) {
267 if ($is_editor) {
268 echo '<div class="king-addons-woo-builder-notice">' . esc_html__('This widget needs a variable product. Add one to preview it here.', 'king-addons') . '</div>'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
269 }
270 return;
271 }
272
273 $settings = $this->get_settings_for_display();
274
275 // Swatches ship in the free tier: both freemium competitors give them away,
276 // and it is the first thing compared side by side.
277 $use_swatches = !empty($settings['swatches']);
278 $unavailable_behavior = $settings['unavailable_behavior'] ?? 'disable';
279 $unavailable_behavior = in_array($unavailable_behavior, ['disable', 'hide'], true) ? $unavailable_behavior : 'disable';
280 $show_selected = !empty($settings['show_selected']);
281 $shape = $settings['swatch_shape'] ?? 'square';
282
283 // Build the swatch map from the product's attributes and terms.
284 $swatch_map = [];
285 if ($use_swatches) {
286 $attributes = $product->get_variation_attributes();
287 foreach ($attributes as $attr_name => $options) {
288 // get_variation_attributes() hands back the taxonomy name as-is
289 // for global attributes (pa_color) and the plain label for custom
290 // ones (Size). Only the first kind has terms to read colours from.
291 $taxonomy = taxonomy_exists($attr_name) ? $attr_name : '';
292 $swatches = [];
293 if ('' !== $taxonomy) {
294 $terms = wc_get_product_terms($product->get_id(), $taxonomy, ['fields' => 'all']);
295 foreach ($terms as $term) {
296 $color_raw = get_term_meta($term->term_id, 'color', true);
297 $color = '';
298 if (is_string($color_raw)) {
299 $hex = sanitize_hex_color($color_raw);
300 $color = $hex ? $hex : '';
301 }
302 $thumb_id = get_term_meta($term->term_id, 'thumbnail_id', true);
303 $image = $thumb_id ? esc_url_raw(wp_get_attachment_image_url($thumb_id, 'thumbnail')) : '';
304 $swatches[$term->slug] = [
305 'label' => sanitize_text_field($term->name),
306 'color' => $color,
307 'image' => $image,
308 ];
309 }
310 } else {
311 foreach ($options as $opt) {
312 $swatches[$opt] = [
313 // The attribute name already labels the row; repeating
314 // it inside every swatch reads as "Size Small".
315 'label' => sanitize_text_field($opt),
316 'color' => '',
317 'image' => '',
318 ];
319 }
320 }
321 // Key the map the way WooCommerce names the select it belongs to
322 // (wc_dropdown_variation_attribute_options uses
323 // "attribute_" . sanitize_title($name)); the script looks the
324 // swatches up by that name.
325 $swatch_map['attribute_' . sanitize_title($attr_name)] = $swatches;
326 }
327 }
328
329 $data = [
330 'swatches' => $use_swatches ? 'yes' : 'no',
331 'unavailable' => $unavailable_behavior,
332 'show_selected' => $show_selected ? 'yes' : 'no',
333 'shape' => $shape,
334 'map' => $swatch_map,
335 'source' => in_array(($settings['swatch_source'] ?? 'auto'), ['auto', 'text'], true) ? ($settings['swatch_source'] ?? 'auto') : 'auto',
336 ];
337
338 $classes = 'ka-woo-variations';
339 if (Woo_Context::template_has_widget('woo_product_add_to_cart', 'single_product')) {
340 $classes .= ' ka-woo-variations--atc-external';
341 }
342
343 echo '<div class="' . esc_attr($classes) . '" id="ka-satc-form-anchor" data-swatches="' . ($use_swatches ? 'yes' : 'no') . '" data-unavailable="' . esc_attr($unavailable_behavior) . '" data-swatches-map="' . esc_attr(wp_json_encode($data)) . '">';
344 wc_get_template('single-product/add-to-cart/variable.php', [
345 'available_variations' => $product->get_available_variations(),
346 'attributes' => $product->get_variation_attributes(),
347 'selected_attributes' => $product->get_default_attributes(),
348 ]);
349 echo '</div>';
350
351 wp_enqueue_script('wc-add-to-cart-variation');
352 if (!has_action('wp_footer', [self::class, 'print_variation_templates'])) {
353 add_action('wp_footer', [self::class, 'print_variation_templates'], 5);
354 }
355 }
356 }
357
358
359
360
361
362
363
364