service = $service; $this->renderer = $renderer; } /** * Register scripts, ajax handlers, and shortcodes. * * @return void */ public function hooks(): void { add_action('wp_enqueue_scripts', [$this, 'enqueue_assets']); add_action('wp_ajax_nopriv_king_addons_wishlist_toggle', [$this, 'handle_toggle']); add_action('wp_ajax_king_addons_wishlist_toggle', [$this, 'handle_toggle']); add_action('wp_ajax_nopriv_king_addons_wishlist_count', [$this, 'handle_count']); add_action('wp_ajax_king_addons_wishlist_count', [$this, 'handle_count']); add_action('wp_ajax_king_addons_wishlist_update_note', [$this, 'handle_update_note']); add_action('wp_ajax_king_addons_wishlist_remove', [$this, 'handle_remove']); add_shortcode('ka_wishlist_button', [$this, 'shortcode_button']); add_shortcode('ka_wishlist_counter', [$this, 'shortcode_counter']); add_shortcode('ka_wishlist_page', [$this, 'shortcode_page']); } /** * Enqueue wishlist assets and pass localized data. * * @return void */ public function enqueue_assets(): void { if (!Wishlist_Settings::is_enabled()) { return; } $style_path = KING_ADDONS_PATH . 'includes/wishlist/assets/style.css'; $script_path = KING_ADDONS_PATH . 'includes/wishlist/assets/script.js'; $style_ver = file_exists($style_path) ? (string) filemtime($style_path) : KING_ADDONS_VERSION; $script_ver = file_exists($script_path) ? (string) filemtime($script_path) : KING_ADDONS_VERSION; $style_deps = []; if (wp_style_is('font-awesome-5-all', 'registered')) { wp_enqueue_style('font-awesome-5-all'); $style_deps[] = 'font-awesome-5-all'; } if (wp_style_is('elementor-icons', 'registered')) { wp_enqueue_style('elementor-icons'); $style_deps[] = 'elementor-icons'; } wp_enqueue_style( 'king-addons-wishlist', KING_ADDONS_URL . 'includes/wishlist/assets/style.css', $style_deps, $style_ver ); wp_enqueue_script( 'king-addons-wishlist', KING_ADDONS_URL . 'includes/wishlist/assets/script.js', ['jquery'], $script_ver, true ); wp_localize_script( 'king-addons-wishlist', 'kingAddonsWishlist', [ 'ajaxUrl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('king_addons_wishlist'), 'labels' => [ 'added' => Wishlist_Settings::get('button_added_text'), 'add' => Wishlist_Settings::get('button_add_text'), 'error' => esc_html__('Unable to update wishlist. Please try again.', 'king-addons'), 'empty' => esc_html__('No products in wishlist yet.', 'king-addons'), ], ] ); } /** * Handle AJAX toggle request. * * @return void */ public function handle_toggle(): void { $nonce = $_POST['nonce'] ?? ''; if (!wp_verify_nonce(sanitize_text_field($nonce), 'king_addons_wishlist')) { wp_send_json_error(['message' => esc_html__('Invalid nonce.', 'king-addons')], 400); } $product_id = absint($_POST['product_id'] ?? 0); $variation_id = absint($_POST['variation_id'] ?? 0); $wishlist_id = isset($_POST['wishlist_id']) ? sanitize_title(wp_unslash($_POST['wishlist_id'])) : null; if (!$product_id) { wp_send_json_error(['message' => esc_html__('Product id is required.', 'king-addons')], 400); } if (!function_exists('wc_get_product') || !wc_get_product($product_id)) { wp_send_json_error(['message' => esc_html__('Product id is required.', 'king-addons')], 400); } if (!Wishlist_Settings::guests_allowed() && !is_user_logged_in()) { wp_send_json_error(['message' => esc_html__('Please sign in to use wishlist.', 'king-addons')], 403); } $result = $this->service->toggle_item($product_id, $variation_id, 1, $wishlist_id); if ($result instanceof WP_Error) { wp_send_json_error(['message' => $result->get_error_message()], 400); } wp_send_json_success([ 'wishlist_id' => $result['wishlist_id'], 'count' => $result['count'], 'state' => !empty($result['success']) ? ($this->service->has_item($product_id, $variation_id, $wishlist_id) ? 'added' : 'default') : 'default', ]); } /** * Return wishlist count via AJAX. * * @return void */ public function handle_count(): void { $nonce = $_GET['nonce'] ?? ''; if (!wp_verify_nonce(sanitize_text_field($nonce), 'king_addons_wishlist')) { wp_send_json_error(['message' => esc_html__('Invalid nonce.', 'king-addons')], 400); } $wishlist_id = isset($_GET['wishlist_id']) ? sanitize_title(wp_unslash($_GET['wishlist_id'])) : null; wp_send_json_success([ 'count' => $this->service->get_count($wishlist_id), ]); } /** * Handle AJAX request to update product note (Pro feature). * * @return void */ public function handle_update_note(): void { $nonce = $_POST['nonce'] ?? ''; if (!wp_verify_nonce(sanitize_text_field($nonce), 'king_addons_wishlist')) { wp_send_json_error(['message' => esc_html__('Invalid nonce.', 'king-addons')], 400); } if (!is_user_logged_in()) { wp_send_json_error(['message' => esc_html__('Please sign in to add notes.', 'king-addons')], 403); } // Pro check if (!function_exists('king_addons_freemius') || !king_addons_freemius()->can_use_premium_code__premium_only()) { wp_send_json_error(['message' => esc_html__('Notes require Pro version.', 'king-addons')], 403); } $product_id = absint($_POST['product_id'] ?? 0); $variation_id = absint($_POST['variation_id'] ?? 0); $note = sanitize_textarea_field(wp_unslash($_POST['note'] ?? '')); $wishlist_id = isset($_POST['wishlist_id']) ? sanitize_title(wp_unslash($_POST['wishlist_id'])) : null; if (!$product_id) { wp_send_json_error(['message' => esc_html__('Product ID required.', 'king-addons')], 400); } $result = $this->service->update_item_note($product_id, $variation_id, $note, $wishlist_id); if (is_wp_error($result)) { wp_send_json_error(['message' => $result->get_error_message()], 400); } wp_send_json_success(['message' => esc_html__('Note saved.', 'king-addons')]); } /** * Handle AJAX request to remove item from wishlist. * * @return void */ public function handle_remove(): void { $nonce = $_POST['nonce'] ?? ''; if (!wp_verify_nonce(sanitize_text_field($nonce), 'king_addons_wishlist')) { wp_send_json_error(['message' => esc_html__('Invalid nonce.', 'king-addons')], 400); } $product_id = absint($_POST['product_id'] ?? 0); $variation_id = absint($_POST['variation_id'] ?? 0); $wishlist_id = isset($_POST['wishlist_id']) ? sanitize_title(wp_unslash($_POST['wishlist_id'])) : null; if (!$product_id) { wp_send_json_error(['message' => esc_html__('Product ID required.', 'king-addons')], 400); } $result = $this->service->remove_item($product_id, $variation_id, $wishlist_id); wp_send_json_success([ 'count' => $result['count'], 'message' => esc_html__('Removed from wishlist.', 'king-addons'), ]); } /** * Shortcode renderer for wishlist button. * * @param array $atts Shortcode attributes. * @return string Rendered HTML. */ public function shortcode_button(array $atts): string { if (!Wishlist_Settings::is_enabled()) { return ''; } $atts = shortcode_atts( [ 'product_id' => get_the_ID(), 'variation_id' => 0, 'wishlist_id' => '', ], $atts ); return $this->renderer->render_button([ 'product_id' => absint($atts['product_id']), 'variation_id' => absint($atts['variation_id']), 'wishlist_id' => $atts['wishlist_id'], ]); } /** * Shortcode renderer for wishlist counter. * * @param array $atts Shortcode attributes. * @return string Rendered HTML. */ public function shortcode_counter(array $atts): string { if (!Wishlist_Settings::is_enabled()) { return ''; } $atts = shortcode_atts( [ 'wishlist_id' => '', ], $atts ); return $this->renderer->render_counter([ 'wishlist_id' => $atts['wishlist_id'], ]); } /** * Render wishlist page/list shortcode. * * @param array $atts Shortcode attributes. * @return string Rendered HTML. */ public function shortcode_page(array $atts): string { if (!Wishlist_Settings::is_enabled()) { return ''; } if (!Wishlist_Settings::guests_allowed() && !is_user_logged_in()) { return '
' . esc_html(Wishlist_Settings::get('guest_block_text')) . '
'; } $atts = shortcode_atts( [ 'wishlist_id' => '', ], $atts ); $wishlist_id = $atts['wishlist_id'] ?: $this->service->get_active_wishlist_id(); $items = $this->service->get_items($wishlist_id); $columns = Wishlist_Settings::get('wishlist_columns', Wishlist_Settings::defaults()['wishlist_columns']); $is_pro = function_exists('king_addons_freemius') && king_addons_freemius()->can_use_premium_code__premium_only(); $column_labels = [ 'image' => esc_html__('Image', 'king-addons'), 'title' => esc_html__('Title', 'king-addons'), 'price' => esc_html__('Price', 'king-addons'), 'stock' => esc_html__('Stock', 'king-addons'), 'notes' => esc_html__('Notes', 'king-addons'), 'add_to_cart' => esc_html__('Add to cart', 'king-addons'), 'remove' => esc_html__('Remove', 'king-addons'), ]; // Only show notes column if Pro and user is logged in if (!$is_pro || !is_user_logged_in()) { $columns = array_filter($columns, function ($col) { return $col !== 'notes'; }); } ob_start(); ?>
variation_id ?: $item->product_id) : null; if (!$product) { continue; } ?>
render_table_cell($column, $product, $item)); ?>
get_permalink()) . '" class="king-addons-wishlist-table__thumb">' . $product->get_image('thumbnail') . ''; case 'title': return '' . esc_html($product->get_name()) . ''; case 'price': return '' . wp_kses_post($product->get_price_html()) . ''; case 'stock': return '' . esc_html($product->is_in_stock() ? __('In stock', 'king-addons') : __('Out of stock', 'king-addons')) . ''; case 'notes': return $this->render_notes_cell($item); case 'add_to_cart': return $this->render_add_to_cart_button($product); case 'remove': $remove_label = esc_html__('Remove', 'king-addons'); return ''; default: return ''; } } /** * Render Add to Cart button without relying on Woo shortcode. * * @param \WC_Product $product Product instance. * @return string */ private function render_add_to_cart_button(\WC_Product $product): string { if (!$product->is_purchasable() || !$product->is_in_stock()) { return '' . esc_html__('Unavailable', 'king-addons') . ''; } $classes = [ 'king-addons-wishlist-table__add-to-cart', 'button', 'product_type_' . $product->get_type(), ]; if ($product->supports('ajax_add_to_cart')) { $classes[] = 'ajax_add_to_cart'; $classes[] = 'add_to_cart_button'; } $attributes = [ 'href' => $product->add_to_cart_url(), 'data-quantity' => 1, 'data-product_id' => $product->get_id(), 'data-product_sku' => $product->get_sku(), 'rel' => 'nofollow', 'class' => implode(' ', array_filter($classes)), 'aria-label' => wp_strip_all_tags($product->add_to_cart_description()), ]; return '' . esc_html($product->add_to_cart_text()) . ''; } /** * Render the notes cell with inline editing (Pro feature). * * @param object $item Wishlist item row. * @return string Rendered HTML. */ private function render_notes_cell(object $item): string { $meta = []; if (!empty($item->meta)) { $decoded = json_decode($item->meta, true); if (is_array($decoded)) { $meta = $decoded; } } $note = $meta['note'] ?? ''; ob_start(); ?>
>
>