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(); ?>

'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; } }