PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.83
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.83
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 51.1.46 51.1.47 51.1.49 All 37 releases
king-addons / includes / wishlist / Wishlist_Frontend.php

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

483 lines 18.8 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 ]);
147 }
148
149 /**
150 * Return wishlist count via AJAX.
151 *
152 * @return void
153 */
154 public function handle_count(): void
155 {
156 $nonce = $_GET['nonce'] ?? '';
157 if (!wp_verify_nonce(sanitize_text_field($nonce), 'king_addons_wishlist')) {
158 wp_send_json_error(['message' => esc_html__('Invalid nonce.', 'king-addons')], 400);
159 }
160
161 $wishlist_id = isset($_GET['wishlist_id']) ? sanitize_title(wp_unslash($_GET['wishlist_id'])) : null;
162 wp_send_json_success([
163 'count' => $this->service->get_count($wishlist_id),
164 ]);
165 }
166
167 /**
168 * Handle AJAX request to update product note (Pro feature).
169 *
170 * @return void
171 */
172 public function handle_update_note(): void
173 {
174 $nonce = $_POST['nonce'] ?? '';
175 if (!wp_verify_nonce(sanitize_text_field($nonce), 'king_addons_wishlist')) {
176 wp_send_json_error(['message' => esc_html__('Invalid nonce.', 'king-addons')], 400);
177 }
178
179 if (!is_user_logged_in()) {
180 wp_send_json_error(['message' => esc_html__('Please sign in to add notes.', 'king-addons')], 403);
181 }
182
183 // Pro check
184 if (!function_exists('king_addons_freemius') || !king_addons_freemius()->can_use_premium_code__premium_only()) {
185 wp_send_json_error(['message' => esc_html__('Notes require Pro version.', 'king-addons')], 403);
186 }
187
188 $product_id = absint($_POST['product_id'] ?? 0);
189 $variation_id = absint($_POST['variation_id'] ?? 0);
190 $note = sanitize_textarea_field(wp_unslash($_POST['note'] ?? ''));
191 $wishlist_id = isset($_POST['wishlist_id']) ? sanitize_title(wp_unslash($_POST['wishlist_id'])) : null;
192
193 if (!$product_id) {
194 wp_send_json_error(['message' => esc_html__('Product ID required.', 'king-addons')], 400);
195 }
196
197 $result = $this->service->update_item_note($product_id, $variation_id, $note, $wishlist_id);
198
199 if (is_wp_error($result)) {
200 wp_send_json_error(['message' => $result->get_error_message()], 400);
201 }
202
203 wp_send_json_success(['message' => esc_html__('Note saved.', 'king-addons')]);
204 }
205
206 /**
207 * Handle AJAX request to remove item from wishlist.
208 *
209 * @return void
210 */
211 public function handle_remove(): void
212 {
213 $nonce = $_POST['nonce'] ?? '';
214 if (!wp_verify_nonce(sanitize_text_field($nonce), 'king_addons_wishlist')) {
215 wp_send_json_error(['message' => esc_html__('Invalid nonce.', 'king-addons')], 400);
216 }
217
218 $product_id = absint($_POST['product_id'] ?? 0);
219 $variation_id = absint($_POST['variation_id'] ?? 0);
220 $wishlist_id = isset($_POST['wishlist_id']) ? sanitize_title(wp_unslash($_POST['wishlist_id'])) : null;
221
222 if (!$product_id) {
223 wp_send_json_error(['message' => esc_html__('Product ID required.', 'king-addons')], 400);
224 }
225
226 $result = $this->service->remove_item($product_id, $variation_id, $wishlist_id);
227
228 wp_send_json_success([
229 'count' => $result['count'],
230 'message' => esc_html__('Removed from wishlist.', 'king-addons'),
231 ]);
232 }
233
234 /**
235 * Shortcode renderer for wishlist button.
236 *
237 * @param array<string, mixed> $atts Shortcode attributes.
238 * @return string Rendered HTML.
239 */
240 public function shortcode_button(array $atts): string
241 {
242 if (!Wishlist_Settings::is_enabled()) {
243 return '';
244 }
245
246 $atts = shortcode_atts(
247 [
248 'product_id' => get_the_ID(),
249 'variation_id' => 0,
250 'wishlist_id' => '',
251 ],
252 $atts
253 );
254
255 return $this->renderer->render_button([
256 'product_id' => absint($atts['product_id']),
257 'variation_id' => absint($atts['variation_id']),
258 'wishlist_id' => $atts['wishlist_id'],
259 ]);
260 }
261
262 /**
263 * Shortcode renderer for wishlist counter.
264 *
265 * @param array<string, mixed> $atts Shortcode attributes.
266 * @return string Rendered HTML.
267 */
268 public function shortcode_counter(array $atts): string
269 {
270 if (!Wishlist_Settings::is_enabled()) {
271 return '';
272 }
273
274 $atts = shortcode_atts(
275 [
276 'wishlist_id' => '',
277 ],
278 $atts
279 );
280
281 return $this->renderer->render_counter([
282 'wishlist_id' => $atts['wishlist_id'],
283 ]);
284 }
285
286 /**
287 * Render wishlist page/list shortcode.
288 *
289 * @param array<string, mixed> $atts Shortcode attributes.
290 * @return string Rendered HTML.
291 */
292 public function shortcode_page(array $atts): string
293 {
294 if (!Wishlist_Settings::is_enabled()) {
295 return '';
296 }
297
298 if (!Wishlist_Settings::guests_allowed() && !is_user_logged_in()) {
299 return '<div class="king-addons-wishlist__notice">' . esc_html(Wishlist_Settings::get('guest_block_text')) . '</div>';
300 }
301
302 $atts = shortcode_atts(
303 [
304 'wishlist_id' => '',
305 ],
306 $atts
307 );
308
309 $wishlist_id = $atts['wishlist_id'] ?: $this->service->get_active_wishlist_id();
310 $items = $this->service->get_items($wishlist_id);
311 $columns = Wishlist_Settings::get('wishlist_columns', Wishlist_Settings::defaults()['wishlist_columns']);
312 $is_pro = function_exists('king_addons_freemius') && king_addons_freemius()->can_use_premium_code__premium_only();
313 $column_labels = [
314 'image' => esc_html__('Image', 'king-addons'),
315 'title' => esc_html__('Title', 'king-addons'),
316 'price' => esc_html__('Price', 'king-addons'),
317 'stock' => esc_html__('Stock', 'king-addons'),
318 'notes' => esc_html__('Notes', 'king-addons'),
319 'add_to_cart' => esc_html__('Add to cart', 'king-addons'),
320 'remove' => esc_html__('Remove', 'king-addons'),
321 ];
322 // Only show notes column if Pro and user is logged in
323 if (!$is_pro || !is_user_logged_in()) {
324 $columns = array_filter($columns, function ($col) {
325 return $col !== 'notes';
326 });
327 }
328
329 ob_start();
330 ?>
331 <div class="king-addons-wishlist-page" data-wishlist-id="<?php echo esc_attr($wishlist_id); ?>">
332 <table class="king-addons-wishlist-table">
333 <thead>
334 <tr>
335 <?php foreach ($columns as $column) : ?>
336 <th class="king-addons-wishlist-table__heading king-addons-wishlist-table__heading--<?php echo esc_attr($column); ?>">
337 <?php echo esc_html($column_labels[$column] ?? ucfirst(str_replace('_', ' ', $column))); ?>
338 </th>
339 <?php endforeach; ?>
340 </tr>
341 </thead>
342 <tbody>
343 <?php if (empty($items)) : ?>
344 <tr>
345 <td colspan="<?php echo esc_attr(count($columns)); ?>" class="king-addons-wishlist-table__empty">
346 <?php esc_html_e('No products in wishlist yet.', 'king-addons'); ?>
347 </td>
348 </tr>
349 <?php else : ?>
350 <?php foreach ($items as $item) : ?>
351 <?php
352 $product = function_exists('wc_get_product') ? wc_get_product($item->variation_id ?: $item->product_id) : null;
353 if (!$product) {
354 continue;
355 }
356 ?>
357 <tr class="king-addons-wishlist-table__row" data-product-id="<?php echo esc_attr($item->product_id); ?>">
358 <?php foreach ($columns as $column) : ?>
359 <td class="king-addons-wishlist-table__cell king-addons-wishlist-table__cell--<?php echo esc_attr($column); ?>">
360 <?php echo wp_kses_post($this->render_table_cell($column, $product, $item)); ?>
361 </td>
362 <?php endforeach; ?>
363 </tr>
364 <?php endforeach; ?>
365 <?php endif; ?>
366 </tbody>
367 </table>
368 </div>
369 <?php
370 return ob_get_clean();
371 }
372
373 /**
374 * Render a table cell content.
375 *
376 * @param string $column Column key.
377 * @param \WC_Product $product Product instance.
378 * @param object $item Wishlist item row.
379 * @return string Rendered HTML.
380 */
381 private function render_table_cell(string $column, \WC_Product $product, object $item): string
382 {
383 switch ($column) {
384 case 'image':
385 return '<a href="' . esc_url($product->get_permalink()) . '" class="king-addons-wishlist-table__thumb">' . $product->get_image('thumbnail') . '</a>';
386 case 'title':
387 return '<a href="' . esc_url($product->get_permalink()) . '" class="king-addons-wishlist-table__title">' . esc_html($product->get_name()) . '</a>';
388 case 'price':
389 return '<span class="king-addons-wishlist-table__price">' . wp_kses_post($product->get_price_html()) . '</span>';
390 case 'stock':
391 return '<span class="king-addons-wishlist-table__stock">' . esc_html($product->is_in_stock() ? __('In stock', 'king-addons') : __('Out of stock', 'king-addons')) . '</span>';
392 case 'notes':
393 return $this->render_notes_cell($item);
394 case 'add_to_cart':
395 return $this->render_add_to_cart_button($product);
396 case 'remove':
397 $remove_label = esc_html__('Remove', 'king-addons');
398 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>';
399 default:
400 return '';
401 }
402 }
403
404 /**
405 * Render Add to Cart button without relying on Woo shortcode.
406 *
407 * @param \WC_Product $product Product instance.
408 * @return string
409 */
410 private function render_add_to_cart_button(\WC_Product $product): string
411 {
412 if (!$product->is_purchasable() || !$product->is_in_stock()) {
413 return '<span class="king-addons-wishlist-table__add-to-cart is-disabled">' . esc_html__('Unavailable', 'king-addons') . '</span>';
414 }
415
416 $classes = [
417 'king-addons-wishlist-table__add-to-cart',
418 'button',
419 'product_type_' . $product->get_type(),
420 ];
421
422 if ($product->supports('ajax_add_to_cart')) {
423 $classes[] = 'ajax_add_to_cart';
424 $classes[] = 'add_to_cart_button';
425 }
426
427 $attributes = [
428 'href' => $product->add_to_cart_url(),
429 'data-quantity' => 1,
430 'data-product_id' => $product->get_id(),
431 'data-product_sku' => $product->get_sku(),
432 'rel' => 'nofollow',
433 'class' => implode(' ', array_filter($classes)),
434 'aria-label' => wp_strip_all_tags($product->add_to_cart_description()),
435 ];
436
437 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>';
438 }
439
440 /**
441 * Render the notes cell with inline editing (Pro feature).
442 *
443 * @param object $item Wishlist item row.
444 * @return string Rendered HTML.
445 */
446 private function render_notes_cell(object $item): string
447 {
448 $meta = [];
449 if (!empty($item->meta)) {
450 $decoded = json_decode($item->meta, true);
451 if (is_array($decoded)) {
452 $meta = $decoded;
453 }
454 }
455 $note = $meta['note'] ?? '';
456
457 ob_start();
458 ?>
459 <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); ?>">
460 <div class="king-addons-wishlist-notes__display" <?php echo empty($note) ? 'style="display:none;"' : ''; ?>>
461 <span class="king-addons-wishlist-notes__text"><?php echo esc_html($note); ?></span>
462 <button type="button" class="king-addons-wishlist-notes__edit-btn" title="<?php esc_attr_e('Edit note', 'king-addons'); ?>">
463 <span class="dashicons dashicons-edit"></span>
464 </button>
465 </div>
466 <div class="king-addons-wishlist-notes__form" <?php echo !empty($note) ? 'style="display:none;"' : ''; ?>>
467 <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>
468 <div class="king-addons-wishlist-notes__actions">
469 <button type="button" class="king-addons-wishlist-notes__save button button-small"><?php esc_html_e('Save', 'king-addons'); ?></button>
470 <?php if (!empty($note)) : ?>
471 <button type="button" class="king-addons-wishlist-notes__cancel button button-small"><?php esc_html_e('Cancel', 'king-addons'); ?></button>
472 <?php endif; ?>
473 </div>
474 </div>
475 </div>
476 <?php
477 return ob_get_clean();
478 }
479 }
480
481
482
483