PluginProbe
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder / 51.1.84
King Addons for Elementor – 100+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce Builder, Mega Menu, Popup Builder v51.1.84
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 51.1.45 All 40 releases
king-addons / includes / widgets / Woo_Cart_Table / Woo_Cart_Table.php

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

526 lines 16.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Woo Cart Table widget.
4 *
5 * @package King_Addons
6 */
7
8 namespace King_Addons;
9
10 use Elementor\Controls_Manager;
11 use Elementor\Group_Control_Border;
12 use Elementor\Group_Control_Box_Shadow;
13 use Elementor\Group_Control_Typography;
14
15 if (!defined('ABSPATH')) {
16 exit;
17 }
18
19 /**
20 * Renders the WooCommerce cart table.
21 */
22 class Woo_Cart_Table extends Abstract_Cart_Widget
23 {
24 /**
25 * Widget slug.
26 *
27 * @return string
28 */
29 public function get_name(): string
30 {
31 return 'woo_cart_table';
32 }
33
34 /**
35 * Widget title.
36 *
37 * @return string
38 */
39 public function get_title(): string
40 {
41 return esc_html__('Cart Table', 'king-addons');
42 }
43
44 /**
45 * Widget icon.
46 *
47 * @return string
48 */
49 public function get_icon(): string
50 {
51 return 'king-addons-icon king-addons-woo-cart-table';
52 }
53
54 /**
55 * Categories.
56 *
57 * @return array<int, string>
58 */
59 public function get_categories(): array
60 {
61 return ['king-addons-woo-builder'];
62 }
63
64 /**
65 * Style dependencies.
66 *
67 * @return array<int, string>
68 */
69 public function get_style_depends(): array
70 {
71 return [KING_ADDONS_ASSETS_UNIQUE_KEY . '-woo-cart-table-style'];
72 }
73
74 /**
75 * Script dependencies.
76 *
77 * @return array<int, string>
78 */
79 public function get_script_depends(): array
80 {
81 return [
82 KING_ADDONS_ASSETS_UNIQUE_KEY . '-woo-cart-table-script',
83 ];
84 }
85
86 /**
87 * Register controls.
88 *
89 * @return void
90 */
91 protected function register_controls(): void
92 {
93 $this->start_controls_section(
94 'section_content',
95 [
96 'label' => esc_html__('Content', 'king-addons'),
97 'tab' => Controls_Manager::TAB_CONTENT,
98 ]
99 );
100
101 $this->add_control(
102 'show_collaterals',
103 [
104 'label' => sprintf(__('Include Totals & Cross-sells (Pro) %s', 'king-addons'), '<i class="eicon-pro-icon"></i>'),
105 'type' => Controls_Manager::SWITCHER,
106 'return_value' => 'yes',
107 ]
108 );
109
110 $this->add_control(
111 'heading_empty',
112 [
113 'label' => esc_html__('Empty title', 'king-addons'),
114 'type' => Controls_Manager::TEXT,
115 'dynamic' => ['active' => true],
116 'default' => __('Your cart is empty', 'king-addons'),
117 ]
118 );
119
120 $this->add_control(
121 'message_empty',
122 [
123 'label' => esc_html__('Empty message', 'king-addons'),
124 'type' => Controls_Manager::TEXTAREA,
125 'dynamic' => ['active' => true],
126 'default' => __('Looks like you have not added anything to your cart yet.', 'king-addons'),
127 ]
128 );
129
130 $this->add_control(
131 'primary_btn_text',
132 [
133 'label' => esc_html__('Primary button text', 'king-addons'),
134 'type' => Controls_Manager::TEXT,
135 'dynamic' => ['active' => true],
136 'default' => __('Return to shop', 'king-addons'),
137 ]
138 );
139
140 $this->add_control(
141 'primary_btn_url',
142 [
143 'label' => esc_html__('Primary button URL', 'king-addons'),
144 'type' => Controls_Manager::URL,
145 'dynamic' => ['active' => true],
146 'placeholder' => 'https://',
147 ]
148 );
149
150 $this->add_control(
151 'secondary_btn_text',
152 [
153 'label' => sprintf(__('Secondary button text %s', 'king-addons'), '<i class="eicon-pro-icon"></i>'),
154 'type' => Controls_Manager::TEXT,
155 'dynamic' => ['active' => true],
156 'default' => __('View offers', 'king-addons'),
157 ]
158 );
159
160 $this->add_control(
161 'secondary_btn_url',
162 [
163 'label' => sprintf(__('Secondary button URL %s', 'king-addons'), '<i class="eicon-pro-icon"></i>'),
164 'type' => Controls_Manager::URL,
165 'dynamic' => ['active' => true],
166 'placeholder' => 'https://',
167 ]
168 );
169
170 $this->end_controls_section();
171
172 $this->start_controls_section(
173 'section_style_table',
174 [
175 'label' => esc_html__('Table', 'king-addons'),
176 'tab' => Controls_Manager::TAB_STYLE,
177 ]
178 );
179
180 $this->add_group_control(
181 Group_Control_Typography::get_type(),
182 [
183 'name' => 'table_typography',
184 'selector' => '{{WRAPPER}} .woocommerce-cart-form',
185 ]
186 );
187
188 $this->add_control(
189 'table_text_color',
190 [
191 'label' => esc_html__('Text Color', 'king-addons'),
192 'type' => Controls_Manager::COLOR,
193 'selectors' => [
194 '{{WRAPPER}} .woocommerce-cart-form' => 'color: {{VALUE}};',
195 ],
196 ]
197 );
198
199 $this->add_group_control(
200 Group_Control_Border::get_type(),
201 [
202 'name' => 'table_border',
203 'selector' => '{{WRAPPER}} .woocommerce-cart-form table',
204 ]
205 );
206
207 $this->add_group_control(
208 Group_Control_Box_Shadow::get_type(),
209 [
210 'name' => 'table_shadow',
211 'selector' => '{{WRAPPER}} .woocommerce-cart-form table',
212 ]
213 );
214
215 $this->end_controls_section();
216 }
217
218 /**
219 * Render widget output.
220 *
221 * @return void
222 */
223 protected function render(): void
224 {
225 $settings = $this->get_settings_for_display();
226
227 if (!$this->should_render()) {
228 $this->render_missing_cart_notice();
229 return;
230 }
231
232 if (!function_exists('WC') || !WC()->cart) {
233 return;
234 }
235
236 if (WC()->cart->is_empty()) {
237 $this->render_empty_cart($settings);
238 return;
239 }
240 $can_pro = king_addons_can_use_pro();
241 $include_collaterals = (!empty($settings['show_collaterals']) && $can_pro);
242
243 $had_totals = has_action('woocommerce_cart_collaterals', 'woocommerce_cart_totals');
244 $had_cross = has_action('woocommerce_cart_collaterals', 'woocommerce_cross_sell_display');
245
246 if (!$include_collaterals) {
247 if ($had_totals) {
248 remove_action('woocommerce_cart_collaterals', 'woocommerce_cart_totals', 10);
249 }
250 if ($had_cross) {
251 remove_action('woocommerce_cart_collaterals', 'woocommerce_cross_sell_display', 10);
252 }
253 }
254
255 $ajax_nonce = wp_create_nonce('ka_cart');
256
257 // The empty-cart block after an AJAX removal used to be a hardcoded
258 // minimal version, so emptying the cart in place looked nothing like
259 // loading an empty cart. Hand the configured copy to the script.
260 $shop_id = wc_get_page_id('shop');
261 $shop_url = $shop_id > 0 ? get_permalink($shop_id) : home_url('/');
262 $empty_attrs = ' data-empty-title="' . esc_attr((string) ($settings['heading_empty'] ?? '')) . '"'
263 . ' data-empty-message="' . esc_attr((string) ($settings['message_empty'] ?? '')) . '"'
264 . ' data-empty-primary-text="' . esc_attr((string) ($settings['primary_btn_text'] ?? '')) . '"'
265 . ' data-empty-primary-url="' . esc_url(!empty($settings['primary_btn_url']['url']) ? $settings['primary_btn_url']['url'] : $shop_url) . '"'
266 . ' data-empty-secondary-text="' . esc_attr($can_pro ? (string) ($settings['secondary_btn_text'] ?? '') : '') . '"'
267 . ' data-empty-secondary-url="' . esc_url(($can_pro && !empty($settings['secondary_btn_url']['url'])) ? $settings['secondary_btn_url']['url'] : '') . '"';
268
269 echo '<div class="ka-cart-table" data-ka-cart="1" data-ajax-url="' . esc_url(admin_url('admin-ajax.php')) . '" data-nonce="' . esc_attr($ajax_nonce) . '"' . $empty_attrs . '>';
270 woocommerce_output_all_notices();
271 wc_get_template('cart/cart.php');
272 echo '</div>';
273
274 if (!$include_collaterals) {
275 if ($had_totals) {
276 add_action('woocommerce_cart_collaterals', 'woocommerce_cart_totals', 10);
277 }
278 if ($had_cross) {
279 add_action('woocommerce_cart_collaterals', 'woocommerce_cross_sell_display', 10);
280 }
281 }
282 }
283
284 /**
285 * Render simple empty cart notice.
286 *
287 * @return void
288 */
289 /**
290 * Render empty cart block.
291 *
292 * @param array<string,mixed> $settings Widget settings.
293 *
294 * @return void
295 */
296 protected function render_empty_cart(array $settings = []): void
297 {
298 $shop_id = wc_get_page_id('shop');
299 $shop_url = $shop_id > 0 ? get_permalink($shop_id) : home_url('/');
300 $can_pro = king_addons_can_use_pro();
301
302 $heading = $settings['heading_empty'] ?? __('Your cart is empty', 'king-addons');
303 $message = $settings['message_empty'] ?? __('Looks like you have not added anything to your cart yet.', 'king-addons');
304 $primary_text = $settings['primary_btn_text'] ?? __('Return to shop', 'king-addons');
305 $primary_url = !empty($settings['primary_btn_url']['url']) ? $settings['primary_btn_url']['url'] : $shop_url;
306 $secondary_text = (!empty($settings['secondary_btn_text']) && $can_pro) ? $settings['secondary_btn_text'] : '';
307 $secondary_url = (!empty($settings['secondary_btn_url']['url']) && $can_pro) ? $settings['secondary_btn_url']['url'] : '';
308
309 echo '<div class="ka-cart-empty">';
310 echo '<div class="ka-cart-empty__icon" aria-hidden="true">🛒</div>';
311 if ($heading) {
312 echo '<h3 class="ka-cart-empty__title">' . esc_html($heading) . '</h3>';
313 }
314 if ($message) {
315 echo '<p class="ka-cart-empty__text">' . esc_html($message) . '</p>';
316 }
317 echo '<div class="ka-cart-empty__actions">';
318 if ($primary_text && $primary_url) {
319 echo '<a class="ka-cart-empty__btn ka-cart-empty__btn--primary" href="' . esc_url($primary_url) . '">' . esc_html($primary_text) . '</a>';
320 }
321 if ($secondary_text && $secondary_url) {
322 echo '<a class="ka-cart-empty__btn ka-cart-empty__btn--ghost" href="' . esc_url($secondary_url) . '">' . esc_html($secondary_text) . '</a>';
323 }
324 echo '</div>';
325 /**
326 * Hook to inject additional empty cart content (e.g., Elementor template).
327 */
328 do_action('king_addons/cart/empty_after');
329 echo '</div>';
330 }
331
332 /**
333 * AJAX: Update quantity or remove item.
334 *
335 * @return void
336 */
337 public static function ajax_update(): void
338 {
339 check_ajax_referer('ka_cart', 'nonce');
340
341 if (!WC()->cart) {
342 wp_send_json_error(['message' => esc_html__('Cart unavailable.', 'king-addons')]);
343 }
344
345 $op = sanitize_text_field($_POST['op'] ?? '');
346 $key = sanitize_text_field($_POST['cart_item_key'] ?? '');
347
348 if (!$key) {
349 wp_send_json_error(['message' => esc_html__('Missing cart item.', 'king-addons')]);
350 }
351
352 if ('remove' === $op) {
353 WC()->cart->remove_cart_item($key);
354 } elseif ('qty' === $op) {
355 $qty = isset($_POST['qty']) ? (float) wc_clean(wp_unslash($_POST['qty'])) : 0;
356 WC()->cart->set_quantity($key, $qty, true);
357 } else {
358 wp_send_json_error(['message' => esc_html__('Unknown operation.', 'king-addons')]);
359 }
360
361 WC()->cart->calculate_totals();
362
363 wp_send_json_success(self::build_fragments());
364 }
365
366 /**
367 * AJAX: Apply/remove coupon.
368 *
369 * @return void
370 */
371 public static function ajax_coupon(): void
372 {
373 check_ajax_referer('ka_cart', 'nonce');
374
375 if (!WC()->cart) {
376 wp_send_json_error(['message' => esc_html__('Cart unavailable.', 'king-addons')]);
377 }
378
379 $code = isset($_POST['coupon']) ? wc_format_coupon_code(wp_unslash($_POST['coupon'])) : '';
380 $mode = sanitize_text_field($_POST['mode'] ?? 'apply');
381
382 if (!$code) {
383 wp_send_json_error(['message' => esc_html__('Coupon is empty.', 'king-addons')]);
384 }
385
386 if ('remove' === $mode) {
387 WC()->cart->remove_coupon($code);
388 } else {
389 WC()->cart->apply_coupon($code);
390 }
391
392 WC()->cart->calculate_totals();
393
394 wp_send_json_success(self::build_fragments());
395 }
396
397 /**
398 * Build fragments for AJAX responses.
399 *
400 * @return array<string, mixed>
401 */
402 private static function build_fragments(): array
403 {
404 $empty = WC()->cart->is_empty();
405
406 $cart_html = $empty ? self::render_empty_cart_html() : self::render_cart_html();
407 $totals_html = self::render_totals_html($empty);
408 $cross_html = self::render_cross_sells_html($empty);
409 $notices = function_exists('wc_print_notices') ? wc_print_notices(true) : '';
410
411 return [
412 'cart_html' => $cart_html,
413 'totals_html' => $totals_html,
414 'cross_sells_html' => $cross_html,
415 'notices' => $notices,
416 'empty' => $empty,
417 'total' => WC()->cart->get_cart_contents_count(),
418 'cart_hash' => WC()->cart->get_cart_hash(),
419 ];
420 }
421
422 /**
423 * Render cart HTML.
424 *
425 * @return string
426 */
427 private static function render_cart_html(): string
428 {
429 ob_start();
430 woocommerce_output_all_notices();
431 wc_get_template('cart/cart.php');
432 return (string) ob_get_clean();
433 }
434
435 /**
436 * Render totals HTML.
437 *
438 * @param bool $empty Is cart empty.
439 *
440 * @return string
441 */
442 private static function render_totals_html(bool $empty): string
443 {
444 if ($empty) {
445 return '';
446 }
447
448 return Woo_Cart_Totals::render_totals_block_html();
449 }
450
451 /**
452 * Render cross-sells HTML.
453 *
454 * @param bool $empty Is cart empty.
455 *
456 * @return string
457 */
458 private static function render_cross_sells_html(bool $empty): string
459 {
460 if ($empty) {
461 return '';
462 }
463
464 ob_start();
465 woocommerce_cross_sell_display();
466 return (string) ob_get_clean();
467 }
468
469 /**
470 * Render empty cart markup for AJAX.
471 *
472 * @return string
473 */
474 private static function render_empty_cart_html(): string
475 {
476 $shop_id = wc_get_page_id('shop');
477 $shop_url = $shop_id > 0 ? get_permalink($shop_id) : home_url('/');
478
479 // The copy comes from the widget, passed along by the script, so the
480 // markup matches what a full page load of an empty cart produces.
481 // phpcs:disable WordPress.Security.NonceVerification.Missing -- verified by the caller.
482 $heading = isset($_POST['empty_title']) ? sanitize_text_field(wp_unslash($_POST['empty_title'])) : __('Your cart is empty', 'king-addons');
483 $message = isset($_POST['empty_message']) ? sanitize_text_field(wp_unslash($_POST['empty_message'])) : '';
484 $primary_text = isset($_POST['empty_primary_text']) ? sanitize_text_field(wp_unslash($_POST['empty_primary_text'])) : __('Return to shop', 'king-addons');
485 $primary_url = isset($_POST['empty_primary_url']) ? esc_url_raw(wp_unslash($_POST['empty_primary_url'])) : '';
486 $secondary_text = isset($_POST['empty_secondary_text']) ? sanitize_text_field(wp_unslash($_POST['empty_secondary_text'])) : '';
487 $secondary_url = isset($_POST['empty_secondary_url']) ? esc_url_raw(wp_unslash($_POST['empty_secondary_url'])) : '';
488 // phpcs:enable WordPress.Security.NonceVerification.Missing
489
490 if ('' === $primary_url) {
491 $primary_url = $shop_url;
492 }
493
494 ob_start();
495 echo '<div class="ka-cart-empty">';
496 echo '<div class="ka-cart-empty__icon" aria-hidden="true">🛒</div>';
497 if ($heading) {
498 echo '<h3 class="ka-cart-empty__title">' . esc_html($heading) . '</h3>';
499 }
500 if ($message) {
501 echo '<p class="ka-cart-empty__text">' . esc_html($message) . '</p>';
502 }
503 echo '<div class="ka-cart-empty__actions">';
504 if ($primary_text && $primary_url) {
505 echo '<a class="ka-cart-empty__btn ka-cart-empty__btn--primary" href="' . esc_url($primary_url) . '">' . esc_html($primary_text) . '</a>';
506 }
507 if ($secondary_text && $secondary_url) {
508 echo '<a class="ka-cart-empty__btn ka-cart-empty__btn--ghost" href="' . esc_url($secondary_url) . '">' . esc_html($secondary_text) . '</a>';
509 }
510 echo '</div>';
511 do_action('king_addons/cart/empty_after');
512 echo '</div>';
513 return (string) ob_get_clean();
514 }
515 }
516
517 // Registration lives in Woo_Builder: admin-ajax.php never loads Elementor
518 // widget files, so hooking from here meant the quantity, remove and coupon
519 // requests all came back as "0".
520
521
522
523
524
525
526