!empty($_POST['ring']) ? 'yes' : 'no',
'fill' => in_array($fill, array('page', 'none', 'theme', 'white'), true) ? $fill : 'page',
'card_box' => isset($_POST['card_box']) ? max(32, min(56, (int) $_POST['card_box'])) : 44,
'line' => 'embossed' === $line ? 'embossed' : 'plain',
'color_empty' => sanitize_hex_color(wp_unslash($_POST['color_empty'] ?? '')) ?: '#c0c4c9',
'color_filled' => sanitize_hex_color(wp_unslash($_POST['color_filled'] ?? '')) ?: '#dd1419',
'color_hover' => sanitize_hex_color(wp_unslash($_POST['color_hover'] ?? '')) ?: '#b3101a',
'size' => isset($_POST['size']) ? max(12, min(60, (int) $_POST['size'])) : 22,
'size_mobile' => isset($_POST['size_mobile'])
? ((int) $_POST['size_mobile'] <= 0 ? 0 : max(12, min(60, (int) $_POST['size_mobile'])))
: 18,
'tooltip_add' => isset($_POST['tooltip_add']) ? sanitize_text_field(wp_unslash($_POST['tooltip_add'])) : '',
'tooltip_remove' => isset($_POST['tooltip_remove']) ? sanitize_text_field(wp_unslash($_POST['tooltip_remove'])) : '',
'show_tooltip' => !empty($_POST['show_tooltip']) ? 'yes' : 'no',
'single_label_add' => isset($_POST['single_label_add']) ? sanitize_text_field(wp_unslash($_POST['single_label_add'])) : '',
'single_label_view' => isset($_POST['single_label_view']) ? sanitize_text_field(wp_unslash($_POST['single_label_view'])) : '',
'single_show_label' => !empty($_POST['single_show_label']) ? 'yes' : 'no',
'show_on_single' => !empty($_POST['show_on_single']) ? 'yes' : 'no',
'show_on_archive' => !empty($_POST['show_on_archive']) ? 'yes' : 'no',
'single_placement'=> $placement,
'archive_position'=> $archive_pos,
'wishlist_page_id'=> isset($_POST['wishlist_page_id']) ? max(0, (int) $_POST['wishlist_page_id']) : 0,
'wishlist_type_product' => !empty($_POST['wishlist_type_product']) ? 'yes' : 'no',
'wishlist_type_brand' => !empty($_POST['wishlist_type_brand']) ? 'yes' : 'no',
'wishlist_type_category' => !empty($_POST['wishlist_type_category']) ? 'yes' : 'no',
'wishlist_type_need' => !empty($_POST['wishlist_type_need']) ? 'yes' : 'no',
'wishlist_type_blogpost' => !empty($_POST['wishlist_type_blogpost']) ? 'yes' : 'no',
);
update_option(AIEO_Wishlist::DISPLAY_OPTION, $data, false);
wp_send_json_success(array('message' => __('Wishlist display settings saved.', 'ai-eshop-optimizer')));
}
public function render_subtab() {
global $wpdb;
$table = $wpdb->prefix . 'aieo_wishlist';
$exists = $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table));
if ($exists !== $table) {
list($aieo_wlx_open, $aieo_wlx_close) = AIEO_DMM_UI::card(array('icon' => 'book-marked', 'title' => $this->get_label()));
echo $aieo_wlx_open;
$aieo_wlx_state = \Rooster\Admin\States::state('not-set-up', array(
'feature' => __('Wishlist analytics', 'ai-eshop-optimizer'),
'text' => __('No wishlist table found. The Rooster One wishlist module may not be active yet — activate it and the analytics will appear here.', 'ai-eshop-optimizer'),
));
echo $aieo_wlx_state;
echo $aieo_wlx_close;
return;
}
$total = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$table}");
$unique = (int) $wpdb->get_var("SELECT COUNT(DISTINCT item_id) FROM {$table}");
$unique_logged_in = (int) $wpdb->get_var(
"SELECT COUNT(DISTINCT user_id) FROM {$table} WHERE user_id > 0"
);
$unique_anonymous = (int) $wpdb->get_var(
"SELECT COUNT(DISTINCT session_id) FROM {$table} WHERE user_id = 0 AND session_id <> ''"
);
$unique_customers = $unique_logged_in + $unique_anonymous;
$popular = $wpdb->get_results("SELECT item_id, COUNT(*) AS c FROM {$table} GROUP BY item_id ORDER BY c DESC LIMIT 20");
$by_source = $wpdb->get_results("SELECT source_type, COUNT(*) AS c FROM {$table} WHERE source_type <> '' GROUP BY source_type ORDER BY c DESC LIMIT 10");
$breakdown = $wpdb->get_results("SELECT source_type, source_id, source_name, COUNT(*) AS c FROM {$table} WHERE source_type <> '' GROUP BY source_type, source_id, source_name ORDER BY c DESC LIMIT 20");
$total_with_source = (int) $wpdb->get_var("SELECT COUNT(*) FROM {$table} WHERE source_type <> ''");
if (function_exists('rooster_admin_band_open')) { rooster_admin_band_open(array('cols' => 1)); }
list($aieo_wlx_open, $aieo_wlx_close) = AIEO_DMM_UI::card(array(
'anchor' => 'wishlist-overview',
'icon' => 'layout-dashboard',
'title' => __('Wishlist totals', 'ai-eshop-optimizer'),
'description' => __('Counts saved items and the unique products behind them, customers with a list split into logged-in and anonymous, and how many items carry a known source.', 'ai-eshop-optimizer'),
));
echo $aieo_wlx_open;
$aieo_wlx_pointer = wp_kses_post(sprintf(
__('Looking for the heart-button styling (placement, colors, labels)? It now lives under %1$sVisual Display → Wishlist Display%2$s.', 'ai-eshop-optimizer'),
'',
''
));
echo \Rooster\Admin\Ui::notice($aieo_wlx_pointer, 'mono', array('inline' => true));
if (0 === $total) {
$aieo_wlx_first = \Rooster\Admin\States::state('first-use', array(
'title' => __('No wishlist items yet', 'ai-eshop-optimizer'),
'text' => __('Items will appear once customers start adding them.', 'ai-eshop-optimizer'),
));
echo $aieo_wlx_first;
echo $aieo_wlx_close;
if (function_exists('rooster_admin_band_close')) { rooster_admin_band_close(); }
return;
}
echo '
';
$this->tile(__('Total items in wishlists', 'ai-eshop-optimizer'), number_format_i18n($total));
$this->tile(__('Unique products', 'ai-eshop-optimizer'), number_format_i18n($unique));
$this->tile(__('Customers with a list', 'ai-eshop-optimizer'), number_format_i18n($unique_customers));
$this->tile(__('… logged in', 'ai-eshop-optimizer'), number_format_i18n($unique_logged_in));
$this->tile(__('… anonymous', 'ai-eshop-optimizer'), number_format_i18n($unique_anonymous));
$this->tile(__('Items with a known source', 'ai-eshop-optimizer'), number_format_i18n($total_with_source));
echo '
';
echo $aieo_wlx_close;
if (function_exists('rooster_admin_band_close')) { rooster_admin_band_close(); }
if (function_exists('rooster_admin_band_open')) { rooster_admin_band_open(array('cols' => 2)); }
$this->render_source_stats_card($by_source, $total_with_source);
$this->render_source_breakdown_card($breakdown);
$this->render_popular_products_card($popular);
if (function_exists('rooster_admin_band_close')) { rooster_admin_band_close(); }
}
public function render_display_settings_card($intro = '') {
$d = AIEO_Wishlist::get_display_settings(true);
$d_hint = array(
'tooltip_add' => __('Add to wishlist', 'ai-eshop-optimizer'),
'tooltip_remove' => __('Remove from wishlist', 'ai-eshop-optimizer'),
'single_label_add' => __('Add to your favourites', 'ai-eshop-optimizer'),
'single_label_view' => __('See your favourites', 'ai-eshop-optimizer'),
);
$placements = array(
'before_title' => __('Before title', 'ai-eshop-optimizer'),
'after_title' => __('After title', 'ai-eshop-optimizer'),
'after_price' => __('After price', 'ai-eshop-optimizer'),
'after_excerpt' => __('After short description', 'ai-eshop-optimizer'),
'before_add_to_cart' => __('Before Add to Cart form', 'ai-eshop-optimizer'),
'after_add_to_cart_btn'=> __('Next to Add to Cart button', 'ai-eshop-optimizer'),
'after_add_to_cart' => __('After Add to Cart (default)', 'ai-eshop-optimizer'),
'after_meta' => __('After product meta', 'ai-eshop-optimizer'),
);
$archive_positions = array(
'top-right' => __('Top right', 'ai-eshop-optimizer'),
'top-left' => __('Top left', 'ai-eshop-optimizer'),
'bottom-right' => __('Bottom right', 'ai-eshop-optimizer'),
'bottom-left' => __('Bottom left', 'ai-eshop-optimizer'),
);
$where_desc = __('Puts the heart on product pages at one of eight positions and in a chosen corner of catalogue cards, and sets the page a filled heart opens instead of un-wishlisting.', 'ai-eshop-optimizer');
$id_of = static function ($name) {
return 'aieo-wl-' . str_replace('_', '-', $name);
};
$row = static function ($name, $label, $control, $help = '') use ($id_of) {
$field = array('id' => $id_of($name), 'label' => $label, 'control' => $control);
if ('' !== $help) { $field['help'] = $help; }
return \Rooster\Admin\Ui::field_row($field);
};
$switch = static function ($name, $text) use ($d, $id_of) {
$on = !isset($d[$name]) || 'yes' === $d[$name];
return \Rooster\Admin\Ui::toggle($name, $on, array('id' => $id_of($name), 'hidden' => false, 'label' => $text));
};
$yes = static function ($name, $text) use ($d, $id_of) {
return \Rooster\Admin\Ui::toggle($name, 'yes' === ($d[$name] ?? ''), array('id' => $id_of($name), 'hidden' => false, 'label' => $text));
};
$text = static function ($name) use ($d, $d_hint, $id_of) {
return \Rooster\Admin\Ui::input($name, (string) $d[$name], array('id' => $id_of($name), 'medium' => true, 'placeholder' => $d_hint[$name]));
};
$colour = static function ($name, $fallback) use ($d, $id_of) {
return \Rooster\Admin\Fields::color($name, (string) $d[$name], array('id' => $id_of($name), 'default' => $fallback));
};
$card = static function ($anchor, $icon, $title, $fields, $extra = array()) {
$form_id = 'aieo-card-' . $anchor . '-form';
list($open, $close) = AIEO_DMM_UI::card(array(
'anchor' => $anchor,
'icon' => $icon,
'title' => $title,
'footer' => class_exists('\Rooster\Admin\States') ? \Rooster\Admin\States::save_button($form_id) : '',
) + $extra);
echo $open;
echo AIEO_DMM_UI::card_form('aieo_dmm_wishlist_save_display', 'wishlist_display', $form_id, '' . $fields . '
');
echo $close;
};
rooster_admin_band_open(array('cols' => 2));
$page_select = wp_dropdown_pages(array(
'name' => 'wishlist_page_id',
'id' => $id_of('wishlist_page_id'),
'selected' => (int) $d['wishlist_page_id'],
'show_option_none' => esc_html__('— none —', 'ai-eshop-optimizer'),
'option_none_value'=> 0,
'echo' => 0,
));
$fields = $row('show_on_single', __('Show on single product', 'ai-eshop-optimizer'), $yes('show_on_single', __('Render the heart on product pages', 'ai-eshop-optimizer')));
$fields .= $row('single_placement', __('Placement on single product', 'ai-eshop-optimizer'), \Rooster\Admin\Ui::select('single_placement', $placements, (string) $d['single_placement'], array('id' => $id_of('single_placement'))));
$fields .= $row('show_on_archive', __('Show on archive cards', 'ai-eshop-optimizer'), $yes('show_on_archive', __('Render on shop / category pages', 'ai-eshop-optimizer')));
$fields .= $row('archive_position', __('Archive corner', 'ai-eshop-optimizer'), \Rooster\Admin\Ui::select('archive_position', $archive_positions, (string) $d['archive_position'], array('id' => $id_of('archive_position'), 'short' => true)));
if ('' !== trim((string) $page_select)) {
$fields .= $row('wishlist_page_id', __('Wishlist page', 'ai-eshop-optimizer'), preg_replace('/^