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 / wishlist / Wishlist_Frontend.php

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

486 lines 19.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace King_Addons\Wishlist;
4
5 use WP_Error;
6
7 if (!defined('ABSPATH')) {
8 exit;
9 }
10
11 /**
12 * Handles front-end hooks, assets, AJAX, and shortcodes.
13 */
14 class Wishlist_Frontend
15 {
16 private Wishlist_Service $service;
17 private Wishlist_Renderer $renderer;
18
19 /**
20 * @param Wishlist_Service $service Wishlist service instance.
21 * @param Wishlist_Renderer $renderer Renderer instance.
22 */
23 public function __construct(Wishlist_Service $service, Wishlist_Renderer $renderer)
24 {
25 $this->service = $service;
26 $this->renderer = $renderer;
27 }
28
29 /**
30 * Register scripts, ajax handlers, and shortcodes.
31 *
32 * @return void
33 */
34 public function hooks(): void
35 {
36 add_action('wp_enqueue_scripts', [$this, 'enqueue_assets']);
37
38 add_action('wp_ajax_nopriv_king_addons_wishlist_toggle', [$this, 'handle_toggle']);
39 add_action('wp_ajax_king_addons_wishlist_toggle', [$this, 'handle_toggle']);
40
41 add_action('wp_ajax_nopriv_king_addons_wishlist_count', [$this, 'handle_count']);
42 add_action('wp_ajax_king_addons_wishlist_count', [$this, 'handle_count']);
43
44 add_action('wp_ajax_king_addons_wishlist_update_note', [$this, 'handle_update_note']);
45 add_action('wp_ajax_king_addons_wishlist_remove', [$this, 'handle_remove']);
46
47 add_shortcode('ka_wishlist_button', [$this, 'shortcode_button']);
48 add_shortcode('ka_wishlist_counter', [$this, 'shortcode_counter']);
49 add_shortcode('ka_wishlist_page', [$this, 'shortcode_page']);
50 }
51
52 /**
53 * Enqueue wishlist assets and pass localized data.
54 *
55 * @return void
56 */
57 public function enqueue_assets(): void
58 {
59 if (!Wishlist_Settings::is_enabled()) {
60 return;
61 }
62
63 $style_path = KING_ADDONS_PATH . 'includes/wishlist/assets/style.css';
64 $script_path = KING_ADDONS_PATH . 'includes/wishlist/assets/script.js';
65 $style_ver = file_exists($style_path) ? (string) filemtime($style_path) : KING_ADDONS_VERSION;
66 $script_ver = file_exists($script_path) ? (string) filemtime($script_path) : KING_ADDONS_VERSION;
67
68 $style_deps = [];
69 if (wp_style_is('font-awesome-5-all', 'registered')) {
70 wp_enqueue_style('font-awesome-5-all');
71 $style_deps[] = 'font-awesome-5-all';
72 }
73 if (wp_style_is('elementor-icons', 'registered')) {
74 wp_enqueue_style('elementor-icons');
75 $style_deps[] = 'elementor-icons';
76 }
77
78 wp_enqueue_style(
79 'king-addons-wishlist',
80 KING_ADDONS_URL . 'includes/wishlist/assets/style.css',
81 $style_deps,
82 $style_ver
83 );
84
85 wp_enqueue_script(
86 'king-addons-wishlist',
87 KING_ADDONS_URL . 'includes/wishlist/assets/script.js',
88 ['jquery'],
89 $script_ver,
90 true
91 );
92
93 wp_localize_script(
94 'king-addons-wishlist',
95 'kingAddonsWishlist',
96 [
97 'ajaxUrl' => admin_url('admin-ajax.php'),
98 'nonce' => wp_create_nonce('king_addons_wishlist'),
99 'labels' => [
100 'added' => Wishlist_Settings::get('button_added_text'),
101 'add' => Wishlist_Settings::get('button_add_text'),
102 'error' => esc_html__('Unable to update wishlist. Please try again.', 'king-addons'),
103 'empty' => esc_html__('No products in wishlist yet.', 'king-addons'),
104 ],
105 ]
106 );
107 }
108
109 /**
110 * Handle AJAX toggle request.
111 *
112 * @return void
113 */
114 public function handle_toggle(): void
115 {
116 $nonce = $_POST['nonce'] ?? '';
117 if (!wp_verify_nonce(sanitize_text_field($nonce), 'king_addons_wishlist')) {
118 wp_send_json_error(['message' => esc_html__('Invalid nonce.', 'king-addons')], 400);
119 }
120
121 $product_id = absint($_POST['product_id'] ?? 0);
122 $variation_id = absint($_POST['variation_id'] ?? 0);
123 $wishlist_id = isset($_POST['wishlist_id']) ? sanitize_title(wp_unslash($_POST['wishlist_id'])) : null;
124
125 if (!$product_id) {
126 wp_send_json_error(['message' => esc_html__('Product id is required.', 'king-addons')], 400);
127 }
128
129 if (!function_exists('wc_get_product') || !wc_get_product($product_id)) {
130 wp_send_json_error(['message' => esc_html__('Product id is required.', 'king-addons')], 400);
131 }
132
133 if (!Wishlist_Settings::guests_allowed() && !is_user_logged_in()) {
134 wp_send_json_error(['message' => esc_html__('Please sign in to use wishlist.', 'king-addons')], 403);
135 }
136
137 $result = $this->service->toggle_item($product_id, $variation_id, 1, $wishlist_id);
138 if ($result instanceof WP_Error) {
139 wp_send_json_error(['message' => $result->get_error_message()], 400);
140 }
141
142 wp_send_json_success([
143 'wishlist_id' => $result['wishlist_id'],
144 'count' => $result['count'],
145 'state' => !empty($result['success']) ? ($this->service->has_item($product_id, $variation_id, $wishlist_id) ? 'added' : 'default') : 'default',
146 'page_html' => $this->shortcode_page([
147 'wishlist_id' => $result['wishlist_id'] ?? '',
148 ]),
149 ]);
150 }
151
152 /**
153 * Return wishlist count via AJAX.
154 *
155 * @return void
156 */
157 public function handle_count(): void
158 {
159 $nonce = $_GET['nonce'] ?? '';
160 if (!wp_verify_nonce(sanitize_text_field($nonce), 'king_addons_wishlist')) {
161 wp_send_json_error(['message' => esc_html__('Invalid nonce.', 'king-addons')], 400);
162 }
163
164 $wishlist_id = isset($_GET['wishlist_id']) ? sanitize_title(wp_unslash($_GET['wishlist_id'])) : null;
165 wp_send_json_success([
166 'count' => $this->service->get_count($wishlist_id),
167 ]);
168 }
169
170 /**
171 * Handle AJAX request to update product note (Pro feature).
172 *
173 * @return void
174 */
175 public function handle_update_note(): void
176 {
177 $nonce = $_POST['nonce'] ?? '';
178 if (!wp_verify_nonce(sanitize_text_field($nonce), 'king_addons_wishlist')) {
179 wp_send_json_error(['message' => esc_html__('Invalid nonce.', 'king-addons')], 400);
180 }
181
182 if (!is_user_logged_in()) {
183 wp_send_json_error(['message' => esc_html__('Please sign in to add notes.', 'king-addons')], 403);
184 }
185
186 // Pro check
187 if (!function_exists('king_addons_freemius') || !king_addons_freemius()->can_use_premium_code__premium_only()) {
188 wp_send_json_error(['message' => esc_html__('Notes require Pro version.', 'king-addons')], 403);
189 }
190
191 $product_id = absint($_POST['product_id'] ?? 0);
192 $variation_id = absint($_POST['variation_id'] ?? 0);
193 $note = sanitize_textarea_field(wp_unslash($_POST['note'] ?? ''));
194 $wishlist_id = isset($_POST['wishlist_id']) ? sanitize_title(wp_unslash($_POST['wishlist_id'])) : null;
195
196 if (!$product_id) {
197 wp_send_json_error(['message' => esc_html__('Product ID required.', 'king-addons')], 400);
198 }
199
200 $result = $this->service->update_item_note($product_id, $variation_id, $note, $wishlist_id);
201
202 if (is_wp_error($result)) {
203 wp_send_json_error(['message' => $result->get_error_message()], 400);
204 }
205
206 wp_send_json_success(['message' => esc_html__('Note saved.', 'king-addons')]);
207 }
208
209 /**
210 * Handle AJAX request to remove item from wishlist.
211 *
212 * @return void
213 */
214 public function handle_remove(): void
215 {
216 $nonce = $_POST['nonce'] ?? '';
217 if (!wp_verify_nonce(sanitize_text_field($nonce), 'king_addons_wishlist')) {
218 wp_send_json_error(['message' => esc_html__('Invalid nonce.', 'king-addons')], 400);
219 }
220
221 $product_id = absint($_POST['product_id'] ?? 0);
222 $variation_id = absint($_POST['variation_id'] ?? 0);
223 $wishlist_id = isset($_POST['wishlist_id']) ? sanitize_title(wp_unslash($_POST['wishlist_id'])) : null;
224
225 if (!$product_id) {
226 wp_send_json_error(['message' => esc_html__('Product ID required.', 'king-addons')], 400);
227 }
228
229 $result = $this->service->remove_item($product_id, $variation_id, $wishlist_id);
230
231 wp_send_json_success([
232 'count' => $result['count'],
233 'message' => esc_html__('Removed from wishlist.', 'king-addons'),
234 ]);
235 }
236
237 /**
238 * Shortcode renderer for wishlist button.
239 *
240 * @param array<string, mixed> $atts Shortcode attributes.
241 * @return string Rendered HTML.
242 */
243 public function shortcode_button(array $atts): string
244 {
245 if (!Wishlist_Settings::is_enabled()) {
246 return '';
247 }
248
249 $atts = shortcode_atts(
250 [
251 'product_id' => get_the_ID(),
252 'variation_id' => 0,
253 'wishlist_id' => '',
254 ],
255 $atts
256 );
257
258 return $this->renderer->render_button([
259 'product_id' => absint($atts['product_id']),
260 'variation_id' => absint($atts['variation_id']),
261 'wishlist_id' => $atts['wishlist_id'],
262 ]);
263 }
264
265 /**
266 * Shortcode renderer for wishlist counter.
267 *
268 * @param array<string, mixed> $atts Shortcode attributes.
269 * @return string Rendered HTML.
270 */
271 public function shortcode_counter(array $atts): string
272 {
273 if (!Wishlist_Settings::is_enabled()) {
274 return '';
275 }
276
277 $atts = shortcode_atts(
278 [
279 'wishlist_id' => '',
280 ],
281 $atts
282 );
283
284 return $this->renderer->render_counter([
285 'wishlist_id' => $atts['wishlist_id'],
286 ]);
287 }
288
289 /**
290 * Render wishlist page/list shortcode.
291 *
292 * @param array<string, mixed> $atts Shortcode attributes.
293 * @return string Rendered HTML.
294 */
295 public function shortcode_page(array $atts): string
296 {
297 if (!Wishlist_Settings::is_enabled()) {
298 return '';
299 }
300
301 if (!Wishlist_Settings::guests_allowed() && !is_user_logged_in()) {
302 return '<div class="king-addons-wishlist__notice">' . esc_html(Wishlist_Settings::get('guest_block_text')) . '</div>';
303 }
304
305 $atts = shortcode_atts(
306 [
307 'wishlist_id' => '',
308 ],
309 $atts
310 );
311
312 $wishlist_id = $atts['wishlist_id'] ?: $this->service->get_active_wishlist_id();
313 $items = $this->service->get_items($wishlist_id);
314 $columns = Wishlist_Settings::get('wishlist_columns', Wishlist_Settings::defaults()['wishlist_columns']);
315 $is_pro = function_exists('king_addons_freemius') && king_addons_freemius()->can_use_premium_code__premium_only();
316 $column_labels = [
317 'image' => esc_html__('Image', 'king-addons'),
318 'title' => esc_html__('Title', 'king-addons'),
319 'price' => esc_html__('Price', 'king-addons'),
320 'stock' => esc_html__('Stock', 'king-addons'),
321 'notes' => esc_html__('Notes', 'king-addons'),
322 'add_to_cart' => esc_html__('Add to cart', 'king-addons'),
323 'remove' => esc_html__('Remove', 'king-addons'),
324 ];
325 // Only show notes column if Pro and user is logged in
326 if (!$is_pro || !is_user_logged_in()) {
327 $columns = array_filter($columns, function ($col) {
328 return $col !== 'notes';
329 });
330 }
331
332 ob_start();
333 ?>
334 <div class="king-addons-wishlist-page" data-wishlist-id="<?php echo esc_attr($wishlist_id); ?>">
335 <table class="king-addons-wishlist-table">
336 <thead>
337 <tr>
338 <?php foreach ($columns as $column) : ?>
339 <th class="king-addons-wishlist-table__heading king-addons-wishlist-table__heading--<?php echo esc_attr($column); ?>">
340 <?php echo esc_html($column_labels[$column] ?? ucfirst(str_replace('_', ' ', $column))); ?>
341 </th>
342 <?php endforeach; ?>
343 </tr>
344 </thead>
345 <tbody>
346 <?php if (empty($items)) : ?>
347 <tr>
348 <td colspan="<?php echo esc_attr(count($columns)); ?>" class="king-addons-wishlist-table__empty">
349 <?php esc_html_e('No products in wishlist yet.', 'king-addons'); ?>
350 </td>
351 </tr>
352 <?php else : ?>
353 <?php foreach ($items as $item) : ?>
354 <?php
355 $product = function_exists('wc_get_product') ? wc_get_product($item->variation_id ?: $item->product_id) : null;
356 if (!$product) {
357 continue;
358 }
359 ?>
360 <tr class="king-addons-wishlist-table__row" data-product-id="<?php echo esc_attr($item->product_id); ?>">
361 <?php foreach ($columns as $column) : ?>
362 <td class="king-addons-wishlist-table__cell king-addons-wishlist-table__cell--<?php echo esc_attr($column); ?>">
363 <?php echo wp_kses_post($this->render_table_cell($column, $product, $item)); ?>
364 </td>
365 <?php endforeach; ?>
366 </tr>
367 <?php endforeach; ?>
368 <?php endif; ?>
369 </tbody>
370 </table>
371 </div>
372 <?php
373 return ob_get_clean();
374 }
375
376 /**
377 * Render a table cell content.
378 *
379 * @param string $column Column key.
380 * @param \WC_Product $product Product instance.
381 * @param object $item Wishlist item row.
382 * @return string Rendered HTML.
383 */
384 private function render_table_cell(string $column, \WC_Product $product, object $item): string
385 {
386 switch ($column) {
387 case 'image':
388 return '<a href="' . esc_url($product->get_permalink()) . '" class="king-addons-wishlist-table__thumb">' . $product->get_image('thumbnail') . '</a>';
389 case 'title':
390 return '<a href="' . esc_url($product->get_permalink()) . '" class="king-addons-wishlist-table__title">' . esc_html($product->get_name()) . '</a>';
391 case 'price':
392 return '<span class="king-addons-wishlist-table__price">' . wp_kses_post($product->get_price_html()) . '</span>';
393 case 'stock':
394 return '<span class="king-addons-wishlist-table__stock">' . esc_html($product->is_in_stock() ? __('In stock', 'king-addons') : __('Out of stock', 'king-addons')) . '</span>';
395 case 'notes':
396 return $this->render_notes_cell($item);
397 case 'add_to_cart':
398 return $this->render_add_to_cart_button($product);
399 case 'remove':
400 $remove_label = esc_html__('Remove', 'king-addons');
401 return '<button type="button" class="king-addons-wishlist-button king-addons-wishlist-button--table-remove" data-product-id="' . esc_attr($item->product_id) . '" data-variation-id="' . esc_attr($item->variation_id) . '" data-wishlist-id="' . esc_attr($item->wishlist_id) . '" data-label-default="' . esc_attr($remove_label) . '" data-label-added="' . esc_attr($remove_label) . '"><span class="king-addons-wishlist-button__label">' . $remove_label . '</span></button>';
402 default:
403 return '';
404 }
405 }
406
407 /**
408 * Render Add to Cart button without relying on Woo shortcode.
409 *
410 * @param \WC_Product $product Product instance.
411 * @return string
412 */
413 private function render_add_to_cart_button(\WC_Product $product): string
414 {
415 if (!$product->is_purchasable() || !$product->is_in_stock()) {
416 return '<span class="king-addons-wishlist-table__add-to-cart is-disabled">' . esc_html__('Unavailable', 'king-addons') . '</span>';
417 }
418
419 $classes = [
420 'king-addons-wishlist-table__add-to-cart',
421 'button',
422 'product_type_' . $product->get_type(),
423 ];
424
425 if ($product->supports('ajax_add_to_cart')) {
426 $classes[] = 'ajax_add_to_cart';
427 $classes[] = 'add_to_cart_button';
428 }
429
430 $attributes = [
431 'href' => $product->add_to_cart_url(),
432 'data-quantity' => 1,
433 'data-product_id' => $product->get_id(),
434 'data-product_sku' => $product->get_sku(),
435 'rel' => 'nofollow',
436 'class' => implode(' ', array_filter($classes)),
437 'aria-label' => wp_strip_all_tags($product->add_to_cart_description()),
438 ];
439
440 return '<a ' . wc_implode_html_attributes($attributes) . '><span class="king-addons-wishlist-table__add-to-cart-label">' . esc_html($product->add_to_cart_text()) . '</span></a>';
441 }
442
443 /**
444 * Render the notes cell with inline editing (Pro feature).
445 *
446 * @param object $item Wishlist item row.
447 * @return string Rendered HTML.
448 */
449 private function render_notes_cell(object $item): string
450 {
451 $meta = [];
452 if (!empty($item->meta)) {
453 $decoded = json_decode($item->meta, true);
454 if (is_array($decoded)) {
455 $meta = $decoded;
456 }
457 }
458 $note = $meta['note'] ?? '';
459
460 ob_start();
461 ?>
462 <div class="king-addons-wishlist-notes" data-product-id="<?php echo esc_attr($item->product_id); ?>" data-variation-id="<?php echo esc_attr($item->variation_id); ?>" data-wishlist-id="<?php echo esc_attr($item->wishlist_id); ?>">
463 <div class="king-addons-wishlist-notes__display" <?php echo empty($note) ? 'style="display:none;"' : ''; ?>>
464 <span class="king-addons-wishlist-notes__text"><?php echo esc_html($note); ?></span>
465 <button type="button" class="king-addons-wishlist-notes__edit-btn" title="<?php esc_attr_e('Edit note', 'king-addons'); ?>">
466 <span class="dashicons dashicons-edit"></span>
467 </button>
468 </div>
469 <div class="king-addons-wishlist-notes__form" <?php echo !empty($note) ? 'style="display:none;"' : ''; ?>>
470 <textarea class="king-addons-wishlist-notes__input" rows="2" maxlength="200" placeholder="<?php esc_attr_e('Add a note...', 'king-addons'); ?>"><?php echo esc_textarea($note); ?></textarea>
471 <div class="king-addons-wishlist-notes__actions">
472 <button type="button" class="king-addons-wishlist-notes__save button button-small"><?php esc_html_e('Save', 'king-addons'); ?></button>
473 <?php if (!empty($note)) : ?>
474 <button type="button" class="king-addons-wishlist-notes__cancel button button-small"><?php esc_html_e('Cancel', 'king-addons'); ?></button>
475 <?php endif; ?>
476 </div>
477 </div>
478 </div>
479 <?php
480 return ob_get_clean();
481 }
482 }
483
484
485
486