get_display_settings();
if ($this->is_active()) {
if ($d['display_location'] === 'tab') {
add_filter('woocommerce_product_tabs', array($this, 'add_tab'));
} else {
$use_classic_hook = ! ( wp_is_block_theme() );
if ($use_classic_hook) {
foreach (array(
'woocommerce_simple_add_to_cart',
'woocommerce_variable_add_to_cart',
'woocommerce_grouped_add_to_cart',
'woocommerce_external_add_to_cart',
) as $hook) {
add_action($hook, array($this, 'render_summary_hook'), 5);
}
}
}
add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_assets'));
add_shortcode('aieo_product_needs', array($this, 'shortcode_handler'));
}
}
public function get_display_settings($raw = false) {
$stored = get_option(self::DISPLAY_OPTION, array());
if (!is_array($stored)) { $stored = array(); }
return $raw ? (array_merge(array(
'enabled' => false,
'frontend_enabled' => 'no',
'display_location' => 'summary',
'title' => '',
'show_title' => true,
'tab_title' => '',
'tab_priority' => 25,
'show_links' => 'yes',
'show_descriptions' => 'yes',
'align' => 'flex-start',
'display_style' => 'list',
'icon_kind' => 'check',
'icon_url' => '',
'icon_attachment_id'=> 0,
'icon_color' => '#27ae60',
'text_color' => '#333333',
'link_color' => '#0073aa',
'bg_color' => '#f8f9fa',
'border_color' => '#e9ecef',
'border_width' => 1,
'border_radius' => 0,
'margin_top' => 20,
'margin_bottom' => 20,
'pill_text_size' => 13,
'pill_radius' => 16,
'pill_pad_v' => 6,
'pill_pad_h' => 12,
'pill_gap' => 8,
'pill_bg' => '#eef1f4',
'pill_border_width' => 1,
'pill_border_color' => '#d6dbe1',
'pill_text_color' => '#333333',
), $stored)) : self::fill_i18n_defaults(array_merge(array(
'enabled' => false,
'frontend_enabled' => 'no',
'display_location' => 'summary',
'title' => '',
'show_title' => true,
'tab_title' => '',
'tab_priority' => 25,
'show_links' => 'yes',
'show_descriptions' => 'yes',
'align' => 'flex-start',
'display_style' => 'list',
'icon_kind' => 'check',
'icon_url' => '',
'icon_attachment_id'=> 0,
'icon_color' => '#27ae60',
'text_color' => '#333333',
'link_color' => '#0073aa',
'bg_color' => '#f8f9fa',
'border_color' => '#e9ecef',
'border_width' => 1,
'border_radius' => 0,
'margin_top' => 20,
'margin_bottom' => 20,
'pill_text_size' => 13,
'pill_radius' => 16,
'pill_pad_v' => 6,
'pill_pad_h' => 12,
'pill_gap' => 8,
'pill_bg' => '#eef1f4',
'pill_border_width' => 1,
'pill_border_color' => '#d6dbe1',
'pill_text_color' => '#333333',
), $stored));
}
private static function i18n_labels() {
return array(
'title' => __('This product covers:', 'ai-eshop-optimizer'),
'tab_title' => __('Needs', 'ai-eshop-optimizer'),
);
}
private static function fill_i18n_defaults(array $s) {
if (!did_action('init')) { return $s; }
foreach (self::i18n_labels() as $k => $v) {
if (!isset($s[$k]) || '' === trim((string) $s[$k])) { $s[$k] = $v; }
}
return $s;
}
public function ajax_save_display() {
AIEO_DMM::verify_ajax_request();
$align = isset($_POST['align']) ? sanitize_key(wp_unslash($_POST['align'])) : 'flex-start';
if (!in_array($align, array('flex-start', 'center', 'flex-end'), true)) {
$align = 'flex-start';
}
$location = isset($_POST['display_location']) ? sanitize_key(wp_unslash($_POST['display_location'])) : 'summary';
if (!in_array($location, array('summary', 'tab'), true)) { $location = 'summary'; }
$style = isset($_POST['display_style']) ? sanitize_key(wp_unslash($_POST['display_style'])) : 'list';
if (!in_array($style, array('list', 'pills'), true)) { $style = 'list'; }
$icon_kind = isset($_POST['icon_kind']) ? sanitize_key(wp_unslash($_POST['icon_kind'])) : 'check';
if (!in_array($icon_kind, array('check', 'custom', 'none'), true)) { $icon_kind = 'check'; }
$icon_url = isset($_POST['icon_url']) ? esc_url_raw(wp_unslash($_POST['icon_url'])) : '';
$icon_aid = isset($_POST['icon_attachment_id']) ? max(0, (int) $_POST['icon_attachment_id']) : 0;
if ($icon_kind === 'custom' && $icon_url === '') { $icon_kind = 'check'; }
$data = array(
'enabled' => !empty($_POST['enabled']),
'display_location' => $location,
'title' => isset($_POST['title']) ? sanitize_text_field(wp_unslash($_POST['title'])) : '',
'show_title' => !empty($_POST['show_title']),
'tab_title' => isset($_POST['tab_title']) ? sanitize_text_field(wp_unslash($_POST['tab_title'])) : '',
'tab_priority' => isset($_POST['tab_priority']) ? max(1, min(999, (int) $_POST['tab_priority'])) : 25,
'show_links' => !empty($_POST['show_links']) ? 'yes' : 'no',
'show_descriptions' => !empty($_POST['show_descriptions']) ? 'yes' : 'no',
'align' => $align,
'display_style' => $style,
'icon_kind' => $icon_kind,
'icon_url' => $icon_url,
'icon_attachment_id'=> $icon_aid,
'icon_color' => sanitize_hex_color(wp_unslash($_POST['icon_color'] ?? '')) ?: '#27ae60',
'text_color' => sanitize_hex_color(wp_unslash($_POST['text_color'] ?? '')) ?: '#333333',
'link_color' => sanitize_hex_color(wp_unslash($_POST['link_color'] ?? '')) ?: '#0073aa',
'bg_color' => sanitize_hex_color(wp_unslash($_POST['bg_color'] ?? '')) ?: '#f8f9fa',
'border_color' => sanitize_hex_color(wp_unslash($_POST['border_color'] ?? '')) ?: '#e9ecef',
'border_width' => isset($_POST['border_width']) ? max(0, min(20, (int) $_POST['border_width'])) : 1,
'border_radius' => isset($_POST['border_radius']) ? max(0, min(100, (int) $_POST['border_radius'])) : 0,
'margin_top' => isset($_POST['margin_top']) ? max(0, min(200, (int) $_POST['margin_top'])) : 20,
'margin_bottom' => isset($_POST['margin_bottom']) ? max(0, min(200, (int) $_POST['margin_bottom'])) : 20,
'pill_text_size' => isset($_POST['pill_text_size']) ? max(8, min(40, (int) $_POST['pill_text_size'])) : 13,
'pill_radius' => isset($_POST['pill_radius']) ? max(0, min(100, (int) $_POST['pill_radius'])) : 16,
'pill_pad_v' => isset($_POST['pill_pad_v']) ? max(0, min(60, (int) $_POST['pill_pad_v'])) : 6,
'pill_pad_h' => isset($_POST['pill_pad_h']) ? max(0, min(80, (int) $_POST['pill_pad_h'])) : 12,
'pill_gap' => isset($_POST['pill_gap']) ? max(0, min(60, (int) $_POST['pill_gap'])) : 8,
'pill_bg' => sanitize_hex_color(wp_unslash($_POST['pill_bg'] ?? '')) ?: '#eef1f4',
'pill_border_width' => isset($_POST['pill_border_width']) ? max(0, min(20, (int) $_POST['pill_border_width'])) : 1,
'pill_border_color' => sanitize_hex_color(wp_unslash($_POST['pill_border_color'] ?? '')) ?: '#d6dbe1',
'pill_text_color' => sanitize_hex_color(wp_unslash($_POST['pill_text_color'] ?? '')) ?: '#333333',
);
$stored = get_option(self::DISPLAY_OPTION, array());
if (!is_array($stored)) { $stored = array(); }
update_option(self::DISPLAY_OPTION, array_merge($stored, $data), false);
wp_send_json_success(array('message' => __('Display settings saved.', 'ai-eshop-optimizer')));
}
public function enqueue_frontend_assets() {
if (!is_product()) { return; }
$rel = 'assets/css/aieo-product-needs.css';
$path = dirname(AIEO_FILE) . '/' . $rel;
$ver = file_exists($path) ? filemtime($path) : AIEO_VERSION;
wp_enqueue_style('aieo-product-needs', plugins_url($rel, AIEO_FILE), array(), $ver);
wp_add_inline_style('aieo-product-needs', $this->build_css_vars());
}
const SHIPPED_COLOURS = array(
'icon_color' => array('--aieo-pn-icon', '#27ae60'),
'text_color' => array('--aieo-pn-text', '#333333'),
'link_color' => array('--aieo-pn-link', '#0073aa'),
'bg_color' => array('--aieo-pn-bg', '#f8f9fa'),
'border_color' => array('--aieo-pn-border', '#e9ecef'),
'pill_bg' => array('--aieo-pn-pill-bg', '#eef1f4'),
'pill_border_color' => array('--aieo-pn-pill-bc', '#d6dbe1'),
'pill_text_color' => array('--aieo-pn-pill-text', '#333333'),
);
public static function colour_vars(array $s) {
$set = array();
foreach (self::SHIPPED_COLOURS as $key => $def) {
$c = sanitize_hex_color(trim((string) ($s[$key] ?? '')));
if (!$c) { continue; }
$long = 4 === strlen($c) ? '#' . $c[1] . $c[1] . $c[2] . $c[2] . $c[3] . $c[3] : $c;
if (0 !== strcasecmp($long, $def[1])) { $set[$key] = $c; }
}
$ink = static function ($bg) {
if (!class_exists('AIEO_Theme_Contracts')) {
require_once AIEO_DIR . 'includes/class-aieo-theme-contracts.php';
}
return AIEO_Theme_Contracts::contrast($bg, '#ffffff') >= AIEO_Theme_Contracts::contrast($bg, '#111111') ? '#ffffff' : '#111111';
};
if (isset($set['bg_color'])) {
foreach (array('text_color', 'link_color') as $key) {
if (!isset($set[$key])) { $set[$key] = $ink($set['bg_color']); }
}
}
if (isset($set['pill_bg']) && !isset($set['pill_text_color'])) {
$set['pill_text_color'] = $ink($set['pill_bg']);
}
$vars = array();
foreach ($set as $key => $c) {
$vars[self::SHIPPED_COLOURS[$key][0]] = $c;
}
return $vars;
}
private function build_css_vars() {
$s = $this->get_display_settings();
$vars = '';
foreach (self::colour_vars($s) as $name => $c) {
$vars .= $name . ':' . esc_attr($c) . ';';
}
return sprintf(
'.aieo-product-needs{'
. '--aieo-pn-align:%s;%s'
. '--aieo-pn-border-w:%dpx;--aieo-pn-border-r:%dpx;'
. '--aieo-pn-mt:%dpx;--aieo-pn-mb:%dpx;'
. '--aieo-pn-pill-fs:%dpx;--aieo-pn-pill-r:%dpx;--aieo-pn-pill-pv:%dpx;--aieo-pn-pill-ph:%dpx;--aieo-pn-pill-gap:%dpx;'
. '--aieo-pn-pill-bw:%dpx;}',
esc_attr($s['align']),
$vars,
(int) ($s['border_width'] ?? 1),
(int) ($s['border_radius'] ?? 0),
(int) $s['margin_top'],
(int) $s['margin_bottom'],
(int) ($s['pill_text_size'] ?? 13),
(int) ($s['pill_radius'] ?? 16),
(int) ($s['pill_pad_v'] ?? 6),
(int) ($s['pill_pad_h'] ?? 12),
(int) ($s['pill_gap'] ?? 8),
(int) ($s['pill_border_width'] ?? 1)
);
}
public function render_summary_hook() {
echo $this->render_html(false);
}
public function add_tab($tabs) {
global $product;
if (!$product) { return $tabs; }
if (empty($this->get_product_needs($product->get_id()))) { return $tabs; }
$s = $this->get_display_settings();
$tabs['aieo_product_needs'] = array(
'title' => esc_html($s['tab_title']),
'priority' => (int) $s['tab_priority'],
'callback' => array($this, 'render_tab_hook'),
);
return $tabs;
}
public function render_tab_hook() {
echo $this->render_html(true);
}
public function shortcode_handler($atts = array()) {
return $this->render_html(false);
}
public function render_for_product($product_id) {
$product = function_exists('wc_get_product') ? wc_get_product((int) $product_id) : null;
return $product instanceof WC_Product ? $this->render_html(false, $product) : '';
}
public function output() {
echo $this->shortcode_handler(array());
}
public function is_active() {
$stored = get_option(self::DISPLAY_OPTION, array());
if (!is_array($stored)) { $stored = array(); }
return !empty($stored['enabled']);
}
private function render_html($is_tab, $product = null) {
if (!$product instanceof WC_Product) {
require_once dirname(__DIR__) . '/blocks/class-aieo-block-item.php';
$product = AIEO_Block_Item::current_product();
}
if (!$product) { return ''; }
$needs = $this->get_product_needs($product->get_id());
if (empty($needs)) { return ''; }
$s = $this->get_display_settings();
$style_cls = (($s['display_style'] ?? 'list') === 'pills') ? ' aieo-product-needs--pills' : '';
$cls = 'aieo-product-needs' . ($is_tab ? ' aieo-product-needs--tab' : '') . $style_cls;
ob_start();
?>
description)) {
$title_attr = ' title="' . esc_attr(wp_strip_all_tags($need->description)) . '"';
}
?>
- >
render_icon($s); ?>
name); ?>
name); ?>
'all'));
if (is_wp_error($terms) || empty($terms)) {
wp_cache_set($cache_key, array(), 'aieo_dmm', HOUR_IN_SECONDS);
return array();
}
$terms = array_values(array_filter($terms, function ($t) {
return !self::is_term_hidden((int) $t->term_id);
}));
if (empty($terms)) {
wp_cache_set($cache_key, array(), 'aieo_dmm', HOUR_IN_SECONDS);
return array();
}
$saved = get_post_meta($product_id, '_pne_need_order', true);
if (!empty($saved)) {
$order = json_decode($saved, true);
if (is_array($order) && !empty($order)) {
$lookup = array();
foreach ($terms as $t) { $lookup[$t->term_id] = $t; }
$ordered = array();
foreach ($order as $tid) {
$tid = (int) $tid;
if (isset($lookup[$tid])) { $ordered[] = $lookup[$tid]; unset($lookup[$tid]); }
}
foreach ($lookup as $t) { $ordered[] = $t; }
wp_cache_set($cache_key, $ordered, 'aieo_dmm', HOUR_IN_SECONDS);
return $ordered;
}
}
usort($terms, function ($a, $b) { return strcasecmp($a->name, $b->name); });
wp_cache_set($cache_key, $terms, 'aieo_dmm', HOUR_IN_SECONDS);
return $terms;
}
private function checkmark_svg() {
return '';
}
private function render_icon(array $s) {
$kind = isset($s['icon_kind']) ? (string) $s['icon_kind'] : 'check';
if ($kind === 'none') { return ''; }
if ($kind === 'custom' && !empty($s['icon_url'])) {
return '
';
}
return $this->checkmark_svg();
}
public function render_subtab() {
$stats = $this->get_impactful_products_without_needs(50);
$this->render_template('product-needs.php', array(
'all_needs' => $this->get_available_needs(),
'preselect_pid' => isset($_GET['pid']) ? absint(wp_unslash($_GET['pid'])) : 0,
'display' => $this->get_display_settings(),
'impactful' => $stats['rows'],
'impactful_total_missing'=> $stats['total_missing'],
));
}
public function get_impactful_products_without_needs(int $limit = 50): array {
global $wpdb;
$temp_orders = $wpdb->prefix . 'aieo_temp_orders';
if (!aieo_dmm_table_exists($temp_orders)) {
return array('rows' => array(), 'total_missing' => 0);
}
$taxonomy = apply_filters('aieo_dmm_needs_taxonomy', 'pa_need');
$sql = $wpdb->prepare(
"SELECT
COALESCE(NULLIF(t.ParentProductId, 0), t.ProductId) AS pid,
SUM(t.PrimaryQty) AS qty,
SUM(t.LineValue) AS turnover
FROM {$temp_orders} t
WHERE t.OrderStatus IN ('wc-completed', 'wc-processing')
AND COALESCE(NULLIF(t.ParentProductId, 0), t.ProductId) > 0
AND COALESCE(NULLIF(t.ParentProductId, 0), t.ProductId) NOT IN (
SELECT DISTINCT tr.object_id
FROM {$wpdb->term_relationships} tr
INNER JOIN {$wpdb->term_taxonomy} tt
ON tt.term_taxonomy_id = tr.term_taxonomy_id
WHERE tt.taxonomy = %s
)
GROUP BY pid
ORDER BY turnover DESC
LIMIT %d",
$taxonomy,
max(1, min(500, $limit))
);
$rows = $wpdb->get_results($sql, ARRAY_A);
if (!is_array($rows)) { $rows = array(); }
$total_missing = (int) $wpdb->get_var($wpdb->prepare(
"SELECT COUNT(DISTINCT COALESCE(NULLIF(t.ParentProductId, 0), t.ProductId))
FROM {$temp_orders} t
WHERE t.OrderStatus IN ('wc-completed', 'wc-processing')
AND COALESCE(NULLIF(t.ParentProductId, 0), t.ProductId) > 0
AND COALESCE(NULLIF(t.ParentProductId, 0), t.ProductId) NOT IN (
SELECT DISTINCT tr.object_id
FROM {$wpdb->term_relationships} tr
INNER JOIN {$wpdb->term_taxonomy} tt
ON tt.term_taxonomy_id = tr.term_taxonomy_id
WHERE tt.taxonomy = %s
)",
$taxonomy
));
$base = add_query_arg(array(
'page' => AIEO_DMM::MENU_SLUG,
'dmm_tab' => 'product_needs',
), admin_url('admin.php'));
$out = array();
foreach ($rows as $r) {
$pid = (int) $r['pid'];
$product = function_exists('wc_get_product') ? wc_get_product($pid) : null;
$title = $product ? (string) $product->get_name() : '';
$sku = $product ? (string) $product->get_sku() : '';
$out[] = array(
'pid' => $pid,
'qty' => (int) $r['qty'],
'turnover' => (float) $r['turnover'],
'title' => $title,
'sku' => $sku,
'edit_link' => admin_url('post.php?post=' . $pid . '&action=edit'),
'select_link' => add_query_arg('pid', $pid, $base) . '#aieo-dmm-needs-editor',
);
}
return array(
'rows' => $out,
'total_missing' => $total_missing,
);
}
public function get_available_needs() {
$taxonomy = apply_filters('aieo_dmm_needs_taxonomy', 'pa_need');
$terms = get_terms(array(
'taxonomy' => $taxonomy,
'hide_empty' => false,
'orderby' => 'name',
'order' => 'ASC',
));
$out = array();
if (is_array($terms)) {
foreach ($terms as $t) {
$out[] = array(
'term_id' => (int) $t->term_id,
'name' => $t->name,
'slug' => $t->slug,
'hidden' => self::is_term_hidden((int) $t->term_id),
);
}
}
if (class_exists('Collator')) {
$coll = new Collator('el_GR');
$coll->setStrength(Collator::PRIMARY);
usort($out, function ($a, $b) use ($coll) {
return $coll->compare((string) $a['name'], (string) $b['name']);
});
} else {
usort($out, function ($a, $b) {
$na = self::normalize_for_sort((string) $a['name']);
$nb = self::normalize_for_sort((string) $b['name']);
return strcmp($na, $nb);
});
}
return $out;
}
private static function normalize_for_sort(string $s): string {
$s = function_exists('mb_strtolower') ? mb_strtolower($s, 'UTF-8') : strtolower($s);
if (class_exists('Normalizer')) {
$s = Normalizer::normalize($s, Normalizer::FORM_D);
$s = preg_replace('/\p{Mn}+/u', '', $s);
}
return $s;
}
public static function is_term_hidden(int $term_id): bool {
$v = get_term_meta($term_id, self::TERM_META_HIDDEN, true);
return (string) $v === '1';
}
public function ajax_create_term(): void {
AIEO_DMM::verify_ajax_request();
$taxonomy = apply_filters('aieo_dmm_needs_taxonomy', 'pa_need');
if (!taxonomy_exists($taxonomy)) {
wp_send_json_error(array('message' => __('The pa_need taxonomy is not registered. Visit Products → Attributes once to bootstrap it.', 'ai-eshop-optimizer')));
}
$name = isset($_POST['name']) ? sanitize_text_field(wp_unslash((string) $_POST['name'])) : '';
$slug = isset($_POST['slug']) ? sanitize_title(wp_unslash((string) $_POST['slug'])) : '';
$hide = !empty($_POST['hidden']);
if ($name === '') {
wp_send_json_error(array('message' => __('Name is required.', 'ai-eshop-optimizer')));
}
$args = array();
if ($slug !== '') { $args['slug'] = $slug; }
$created = wp_insert_term($name, $taxonomy, $args);
if (is_wp_error($created)) {
wp_send_json_error(array(
'message' => $created->get_error_message(),
'code' => $created->get_error_code(),
));
}
$term_id = (int) $created['term_id'];
if ($hide) {
update_term_meta($term_id, self::TERM_META_HIDDEN, '1');
}
$term = get_term($term_id, $taxonomy);
if (!$term || is_wp_error($term)) {
wp_send_json_error(array('message' => __('Term created but could not be re-loaded.', 'ai-eshop-optimizer')));
}
if (function_exists('wp_cache_flush_group')) {
wp_cache_flush_group('aieo_dmm');
}
wp_send_json_success(array(
'term_id' => $term_id,
'name' => $term->name,
'slug' => $term->slug,
'hidden' => $hide,
));
}
public function render_term_add_form_field(): void {
\Rooster\Admin\Library::island_style();
?>
term_id);
\Rooster\Admin\Library::island_style();
?>
|
|
$label) {
$new[$key] = $label;
if ($key === 'name') {
$new['aieo_hidden'] = __('Front-end', 'ai-eshop-optimizer');
}
}
if (!isset($new['aieo_hidden'])) {
$new['aieo_hidden'] = __('Front-end', 'ai-eshop-optimizer');
}
return $new;
}
public function term_list_render_hidden_column($content, $column_name, $term_id): string {
if ($column_name !== 'aieo_hidden') { return (string) $content; }
$hidden = self::is_term_hidden((int) $term_id);
$label = $hidden ? __('Hidden', 'ai-eshop-optimizer') : __('Visible', 'ai-eshop-optimizer');
if (class_exists('\Rooster\Admin\Library') && class_exists('\Rooster\Admin\Ui') && wp_style_is(\Rooster\Admin\Library::HANDLE, 'enqueued')) {
return \Rooster\Admin\Ui::badge($label, $hidden ? 'warning' : 'success', array('sm' => true));
}
return $hidden ? '' . esc_html($label) . '' : esc_html($label);
}
public function ajax_load_product() {
AIEO_DMM::verify_ajax_request();
$pid = isset($_POST['product_id']) ? absint(wp_unslash($_POST['product_id'])) : 0;
if ($pid <= 0) {
wp_send_json_error(array('message' => __('Invalid product id.', 'ai-eshop-optimizer')));
}
$post = get_post($pid);
if (!$post || $post->post_type !== 'product') {
wp_send_json_error(array('message' => __('Product not found.', 'ai-eshop-optimizer')));
}
$selected_terms = wp_get_object_terms($pid, apply_filters('aieo_dmm_needs_taxonomy', 'pa_need'), array('fields' => 'ids'));
if (is_wp_error($selected_terms)) {
$selected_terms = array();
}
global $wpdb;
$table = $wpdb->prefix . 'aieo_dmm_product_needs';
$row = null;
if (aieo_dmm_table_exists($table)) {
$row = $wpdb->get_row($wpdb->prepare(
"SELECT * FROM {$table} WHERE product_id = %d",
$pid
), ARRAY_A);
}
$order = array();
if ($row && !empty($row['need_order'])) {
$order = json_decode($row['need_order'], true) ?: array();
}
wp_send_json_success(array(
'product_id' => $pid,
'title' => $post->post_title,
'sku' => get_post_meta($pid, '_sku', true),
'selected_term_ids' => array_map('intval', $selected_terms),
'order' => array_map('intval', (array) $order),
));
}
public function ajax_save() {
AIEO_DMM::verify_ajax_request();
$pid = isset($_POST['product_id']) ? absint(wp_unslash($_POST['product_id'])) : 0;
if ($pid <= 0) {
wp_send_json_error(array('message' => __('Invalid product id.', 'ai-eshop-optimizer')));
}
if (get_post_type($pid) !== 'product') {
wp_send_json_error(array('message' => __('Product not found.', 'ai-eshop-optimizer')));
}
$term_ids = isset($_POST['term_ids']) && is_array($_POST['term_ids'])
? array_map('absint', wp_unslash($_POST['term_ids']))
: array();
$order = isset($_POST['order']) && is_array($_POST['order'])
? array_map('absint', wp_unslash($_POST['order']))
: array();
$taxonomy = apply_filters('aieo_dmm_needs_taxonomy', 'pa_need');
wp_set_object_terms($pid, $term_ids, $taxonomy, false);
$selected_labels = array();
foreach ($term_ids as $tid) {
$t = get_term($tid, $taxonomy);
if ($t && !is_wp_error($t)) {
$selected_labels[] = $t->name;
}
}
global $wpdb;
$table = $wpdb->prefix . 'aieo_dmm_product_needs';
if (aieo_dmm_table_exists($table)) {
$wpdb->replace($table, array(
'product_id' => $pid,
'selected_needs' => implode(',', $selected_labels),
'need_order' => wp_json_encode($order),
), array('%d', '%s', '%s'));
}
AIEO_DMM_Cache::bump_version('needs');
wp_send_json_success(array('message' => __('Product needs updated.', 'ai-eshop-optimizer')));
}
public function ajax_search_products() {
AIEO_DMM::verify_ajax_request();
$q = isset($_GET['q']) ? sanitize_text_field(wp_unslash($_GET['q'])) : '';
global $wpdb;
$temp_orders = $wpdb->prefix . 'aieo_temp_orders';
$taxonomy = apply_filters('aieo_dmm_needs_taxonomy', 'pa_need');
$params = array();
$where = array(
"p.post_status = 'publish'",
"p.post_type IN ('product', 'product_variation')",
);
if ($q !== '') {
$like = '%' . $wpdb->esc_like($q) . '%';
$where[] = "(p.post_title LIKE %s OR pm.meta_value LIKE %s OR p.ID = %d)";
$params[] = $like;
$params[] = $like;
$params[] = (int) preg_replace('/\D/', '', $q);
}
$has_needs_subq = "EXISTS (
SELECT 1 FROM {$wpdb->term_relationships} tr
JOIN {$wpdb->term_taxonomy} tt
ON tt.term_taxonomy_id = tr.term_taxonomy_id
WHERE tt.taxonomy = %s
AND tr.object_id = COALESCE(p.post_parent, p.ID)
)";
$sql = $wpdb->prepare(
"SELECT
p.ID AS pid,
p.post_title AS title,
pm.meta_value AS sku,
IFNULL(t.turnover, 0) AS turnover,
({$has_needs_subq}) AS has_needs
FROM {$wpdb->posts} p
LEFT JOIN {$wpdb->postmeta} pm
ON pm.post_id = p.ID AND pm.meta_key = '_sku'
LEFT JOIN (
SELECT
COALESCE(NULLIF(ParentProductId, 0), ProductId) AS pid,
SUM(LineValue) AS turnover
FROM {$temp_orders}
WHERE OrderStatus IN ('wc-completed', 'wc-processing')
AND COALESCE(NULLIF(ParentProductId, 0), ProductId) > 0
GROUP BY pid
) t ON t.pid = p.ID
WHERE " . implode(' AND ', $where) . "
AND p.post_type = 'product'
ORDER BY has_needs ASC, turnover DESC, p.post_title ASC
LIMIT 30",
array_merge(array($taxonomy), $params)
);
$rows = $wpdb->get_results($sql, ARRAY_A);
$out = array();
foreach ($rows as $r) {
$title = (string) $r['title'];
$sku = (string) $r['sku'];
$tag = $r['has_needs'] ? '' : ' ★';
$out[] = array(
'id' => (int) $r['pid'],
'text' => $title . ($sku !== '' ? ' [' . $sku . ']' : '') . $tag,
);
}
wp_send_json(array('results' => $out));
}
public function product_row_action($actions, $post) {
if (!current_user_can(AIEO_CAPABILITY)) {
return $actions;
}
if (!isset($post->post_type) || $post->post_type !== 'product') {
return $actions;
}
$url = add_query_arg(array(
'page' => AIEO_DMM::MENU_SLUG,
'dmm_tab' => 'product_needs',
'pid' => (int) $post->ID,
), admin_url('admin.php'));
$actions['aieo_dmm_edit_needs'] = '' . esc_html__('Edit Needs', 'ai-eshop-optimizer') . '';
return $actions;
}
}