PluginProbe
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder / 51.1.78
King Addons for Elementor – 80+ Elementor Widgets, 4 000+ Elementor Templates, WooCommerce, Mega Menu, Popup Builder v51.1.78
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.78, at includes/wishlist/Wishlist_Frontend.php

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