array('label' => __('Hero', 'ai-eshop-optimizer'), 'icon' => 'image'),
'seo' => array('label' => __('SEO', 'ai-eshop-optimizer'), 'icon' => 'search'),
'layout' => array('label' => __('Layout', 'ai-eshop-optimizer'), 'icon' => 'layout-template'),
'catalog' => array('label' => __('Catalog', 'ai-eshop-optimizer'), 'icon' => 'package'),
'related' => array('label' => __('Related', 'ai-eshop-optimizer'), 'icon' => 'link'),
);
if (in_array('product_cat', $this->managed_taxonomies(), true)) {
$tabs['marketplace'] = array('label' => __('Marketplace', 'ai-eshop-optimizer'), 'icon' => 'send');
}
return $tabs;
}
public function get_tab_base_url() {
$tax = $this->managed_taxonomies();
$tax = !empty($tax) ? (string) reset($tax) : 'product_cat';
return admin_url('edit-tags.php?taxonomy=' . rawurlencode($tax));
}
const LEGACY_PREFIX = 'woocommerce_advanced_categories_';
public static function templates() {
return (array) apply_filters('aieo_dmm_category_extras_templates', array(
'default' => __('Default (theme archive)', 'ai-eshop-optimizer'),
'hero' => __('Hero (image + headline + description)', 'ai-eshop-optimizer'),
'hero_brands' => __('Hero + featured brands grid', 'ai-eshop-optimizer'),
'landing' => __('Landing page (no products grid)', 'ai-eshop-optimizer'),
'hidden' => __('Hidden (404 the archive)', 'ai-eshop-optimizer'),
));
}
public function register_hooks() {
if (is_admin()) {
add_action('admin_enqueue_scripts', function () {
if ((isset($_GET['page']) ? sanitize_text_field(wp_unslash($_GET['page'])) : '') === 'ai-eshop-dmm' && (isset($_GET['dmm_tab']) ? sanitize_text_field(wp_unslash($_GET['dmm_tab'])) : '') === 'category_extras') {
$css = 'assets/css/aieo-dmm-category-extras-admin.css';
wp_enqueue_style('aieo-dmm-category-extras-admin', AIEO_URI . $css, array(), file_exists(AIEO_DIR . $css) ? (string) filemtime(AIEO_DIR . $css) : '1');
}
});
}
add_action('wp_ajax_aieo_dmm_category_extras_migrate', array($this, 'ajax_migrate'));
add_action('wp_ajax_aieo_dmm_category_extras_h3_migrate', array($this, 'ajax_migrate_h3_headlines'));
add_action('wp_ajax_aieo_dmm_category_extras_save', array($this, 'ajax_save'));
add_action('wp_ajax_aieo_dmm_category_extras_search', array($this, 'ajax_search'));
add_action('wp_ajax_aieo_dmm_marketplace_tax_browse', array($this, 'ajax_marketplace_tax_browse'));
add_action('wp_ajax_aieo_dmm_marketplace_tax_search', array($this, 'ajax_marketplace_tax_search'));
add_action('wp_ajax_aieo_dmm_category_extras_save_settings', array($this, 'ajax_save_settings'));
add_shortcode('aieo_category_hero', array($this, 'shortcode_category_hero'));
add_shortcode('aieo_category_chips', array($this, 'shortcode_category_chips'));
add_action('init', array($this, 'register_block'));
add_action('wp_enqueue_scripts', array($this, 'enqueue_frontend_assets'));
add_action('pre_get_posts', array($this, 'exclude_blog_hero_post'));
if (!is_admin()) {
return;
}
foreach ($this->managed_taxonomies() as $tax) {
add_action($tax . '_edit_form_fields', array($this, 'render_term_extras'), 1, 2);
add_action('edited_' . $tax, array($this, 'save_term_extras'), 10, 2);
add_action('created_' . $tax, array($this, 'save_term_extras'), 10, 2);
}
add_action('admin_enqueue_scripts', array($this, 'enqueue_term_admin_assets'));
}
private function managed_taxonomies() {
return (array) apply_filters(
'aieo_dmm_category_extras_managed_taxonomies',
array('product_cat')
);
}
private function description_editor_taxonomies() {
$list = array_merge(
$this->managed_taxonomies(),
array(
'product_brand', 'product_tag', 'supplier',
'pa_need', 'pa-need', 'category', 'post_tag',
)
);
$list = (array) apply_filters('aieo_dmm_description_editor_taxonomies', $list);
return array_values(array_unique(array_filter(array_map('sanitize_key', $list))));
}
public function is_enabled() {
if (method_exists('AIEO_DMM_Settings', 'is_module_enabled')) {
return (bool) AIEO_DMM_Settings::is_module_enabled('category_extras');
}
return true;
}
public static function table() {
global $wpdb;
return $wpdb->prefix . 'aieo_dmm_category_extras';
}
public static function get($taxonomy, $term_id) {
global $wpdb;
$taxonomy = sanitize_key($taxonomy);
$term_id = (int) $term_id;
if (!$taxonomy || !$term_id) return null;
$tbl = self::table();
$row = $wpdb->get_row(
$wpdb->prepare("SELECT * FROM {$tbl} WHERE taxonomy = %s AND term_id = %d LIMIT 1", $taxonomy, $term_id),
ARRAY_A
);
if (!$row) return null;
$row['landing_pages'] = self::decode_landing_pages($row['landing_pages']);
foreach (array('related_brand_ids','related_post_ids','related_campaign_ids') as $k) {
$row[$k] = isset($row[$k]) ? self::decode_id_list($row[$k]) : array();
}
return $row;
}
public static function save($taxonomy, $term_id, array $data) {
global $wpdb;
$taxonomy = sanitize_key($taxonomy);
$term_id = (int) $term_id;
if (!$taxonomy || !$term_id) return false;
$tbl = self::table();
$patch = self::sanitize_patch($data);
$patch['taxonomy'] = $taxonomy;
$patch['term_id'] = $term_id;
$existing = $wpdb->get_var($wpdb->prepare(
"SELECT id FROM {$tbl} WHERE taxonomy = %s AND term_id = %d LIMIT 1",
$taxonomy, $term_id
));
if ($existing) {
return $wpdb->update($tbl, $patch, array('id' => (int) $existing));
}
return $wpdb->insert($tbl, $patch);
}
private static function sanitize_patch(array $data) {
$out = array();
if (array_key_exists('headline', $data)) {
$out['headline'] = wp_kses_post((string) $data['headline']);
}
if (array_key_exists('description', $data)) {
$out['description'] = wp_kses_post((string) $data['description']);
}
if (array_key_exists('image_mobile_id', $data)) {
$out['image_mobile_id'] = $data['image_mobile_id'] ? (int) $data['image_mobile_id'] : null;
}
if (array_key_exists('image_desktop_id', $data)) {
$out['image_desktop_id'] = $data['image_desktop_id'] ? (int) $data['image_desktop_id'] : null;
}
if (array_key_exists('seo_title', $data)) {
$out['seo_title'] = sanitize_text_field((string) $data['seo_title']);
}
if (array_key_exists('sidebar_text', $data)) {
$out['sidebar_text'] = wp_kses_post((string) $data['sidebar_text']);
}
if (array_key_exists('footer_text', $data)) {
$out['footer_text'] = wp_kses_post((string) $data['footer_text']);
}
if (array_key_exists('columns', $data)) {
$c = (int) $data['columns'];
$out['columns'] = ($c >= 1 && $c <= 6) ? $c : null;
}
if (array_key_exists('disable_nested', $data)) {
$out['disable_nested'] = !empty($data['disable_nested']) ? 1 : 0;
}
if (array_key_exists('template', $data)) {
$tpl = sanitize_key((string) $data['template']);
$allowed = array_keys(self::templates());
$out['template'] = in_array($tpl, $allowed, true) ? $tpl : 'default';
}
if (array_key_exists('landing_pages', $data)) {
$out['landing_pages'] = self::encode_landing_pages($data['landing_pages']);
}
if (array_key_exists('pro_only', $data)) {
$out['pro_only'] = !empty($data['pro_only']) ? 1 : 0;
}
if (array_key_exists('reference_quantity', $data)) {
$rq = $data['reference_quantity'];
$out['reference_quantity'] = ($rq === null || $rq === '') ? null : max(0, (int) $rq);
}
if (array_key_exists('synonyms', $data)) {
$out['synonyms'] = (string) $data['synonyms'] !== '' ? (string) $data['synonyms'] : null;
}
foreach (array('season_start', 'season_end', 'season_midpoint') as $key) {
if (!array_key_exists($key, $data)) continue;
$val = (string) $data[$key];
$out[$key] = (preg_match('/^\d{4}-\d{2}-\d{2}$/', $val) && $val !== '0000-00-00') ? $val : null;
}
foreach (array('google_category_id', 'facebook_category_id') as $idkey) {
if (array_key_exists($idkey, $data)) {
$v = $data[$idkey];
$out[$idkey] = ($v === null || $v === '') ? null : max(0, (int) $v);
if ($out[$idkey] === 0) { $out[$idkey] = null; }
}
}
foreach (array('google_category_path', 'facebook_category_path') as $pathkey) {
if (array_key_exists($pathkey, $data)) {
$v = sanitize_text_field((string) $data[$pathkey]);
$out[$pathkey] = $v !== '' ? mb_substr($v, 0, 500) : null;
}
}
foreach (array('related_brand_ids', 'related_post_ids', 'related_campaign_ids') as $key) {
if (array_key_exists($key, $data)) {
$out[$key] = self::encode_id_list($data[$key]);
}
}
return $out;
}
private static function encode_id_list($val) {
if (!is_array($val)) return null;
$clean = array_values(array_unique(array_map('intval', $val)));
$clean = array_values(array_filter($clean, static function ($v) { return $v > 0; }));
return empty($clean) ? null : wp_json_encode($clean);
}
private static function decode_id_list($json) {
if (!$json) return array();
$arr = json_decode((string) $json, true);
return is_array($arr) ? array_values(array_map('intval', $arr)) : array();
}
private static function encode_landing_pages($val) {
if (is_array($val)) {
$clean = array();
foreach ($val as $lp) {
if (!is_array($lp)) continue;
$clean[] = array(
'title' => isset($lp['title']) ? sanitize_text_field((string) $lp['title']) : '',
'link' => isset($lp['link']) ? esc_url_raw((string) $lp['link']) : '',
'image_id' => isset($lp['image_id']) ? (int) $lp['image_id'] : 0,
);
}
return wp_json_encode(array_values($clean));
}
return null;
}
private static function decode_landing_pages($json) {
if (!$json) return array();
$arr = json_decode((string) $json, true);
return is_array($arr) ? $arr : array();
}
public function run_migration() {
$taxonomies = (array) apply_filters(
'aieo_dmm_category_extras_migration_taxonomies',
array('product_cat', 'product_brand')
);
$stats = array(
'processed' => 0,
'migrated' => 0,
'skipped' => 0,
'taxonomies' => array(),
'pods_loaded' => array(),
);
foreach ($taxonomies as $taxonomy) {
if (!taxonomy_exists($taxonomy)) continue;
$stats['taxonomies'][$taxonomy] = 0;
$pods_cache = $this->load_pods_caches($taxonomy);
$stats['pods_loaded'][$taxonomy] = !empty($pods_cache['pod_id']);
$term_ids = get_terms(array(
'taxonomy' => $taxonomy,
'hide_empty' => false,
'fields' => 'ids',
));
if (is_wp_error($term_ids) || empty($term_ids)) continue;
foreach ($term_ids as $term_id) {
$stats['processed']++;
$patch = $this->extract_legacy_for_term((int) $term_id);
if (!empty($pods_cache['pod_id'])) {
$patch = array_merge($patch, $this->extract_pods_for_term((int) $term_id, $pods_cache));
}
if (empty($patch)) {
$stats['skipped']++;
continue;
}
self::save($taxonomy, (int) $term_id, $patch);
$stats['migrated']++;
$stats['taxonomies'][$taxonomy]++;
}
}
update_option('aieo_dmm_category_extras_migrated_at', time(), false);
return $stats;
}
private function load_pods_caches($pod_name) {
global $wpdb;
$cache = array(
'pod_id' => 0,
'pods_table' => array(),
'rel_fields' => array(),
'rel_data' => array(),
);
$pod_id = (int) $wpdb->get_var($wpdb->prepare(
"SELECT ID FROM {$wpdb->posts}
WHERE post_type = '_pods_pod' AND post_name = %s LIMIT 1",
$pod_name
));
if (!$pod_id) return $cache;
$cache['pod_id'] = $pod_id;
$relevant = array('related_brand', 'related_posts', 'related_campaigns');
$field_rows = $wpdb->get_results($wpdb->prepare(
"SELECT ID, post_name FROM {$wpdb->posts}
WHERE post_type = '_pods_field' AND post_parent = %d
AND post_name IN ('" . implode("','", $relevant) . "')",
$pod_id
));
$field_id_to_name = array();
foreach ((array) $field_rows as $row) {
$cache['rel_fields'][$row->post_name] = (int) $row->ID;
$field_id_to_name[(int) $row->ID] = $row->post_name;
}
$tbl = $wpdb->prefix . 'pods_' . $pod_name;
$exists = ($wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $tbl)) === $tbl);
if ($exists) {
$rows = $wpdb->get_results("SELECT * FROM {$tbl}", ARRAY_A);
foreach ((array) $rows as $row) {
if (!isset($row['id'])) continue;
$cache['pods_table'][(int) $row['id']] = $row;
}
}
if (!empty($field_id_to_name)) {
$field_ids = array_keys($field_id_to_name);
$place = implode(',', array_fill(0, count($field_ids), '%d'));
$params = array_merge(array($pod_id), $field_ids);
$rows = $wpdb->get_results($wpdb->prepare(
"SELECT item_id, field_id, related_item_id
FROM {$wpdb->prefix}podsrel
WHERE pod_id = %d AND field_id IN ({$place})
ORDER BY item_id, field_id, weight ASC",
$params
));
foreach ((array) $rows as $row) {
$name = $field_id_to_name[(int) $row->field_id] ?? null;
if (!$name) continue;
$cache['rel_data'][(int) $row->item_id][$name][] = (int) $row->related_item_id;
}
}
return $cache;
}
private function extract_pods_for_term($term_id, array $cache) {
$patch = array();
if (isset($cache['pods_table'][$term_id])) {
$row = $cache['pods_table'][$term_id];
if (!empty($row['pro_only'])) {
$patch['pro_only'] = 1;
}
if (isset($row['reference_quantity']) && $row['reference_quantity'] !== null && $row['reference_quantity'] !== '') {
$rq = (int) $row['reference_quantity'];
if ($rq > 0) $patch['reference_quantity'] = $rq;
}
if (!empty($row['synonyms'])) {
$patch['synonyms'] = (string) $row['synonyms'];
}
foreach (array('season_start', 'season_end', 'season_midpoint') as $col) {
if (!empty($row[$col]) && $row[$col] !== '0000-00-00') {
$patch[$col] = (string) $row[$col];
}
}
}
if (isset($cache['rel_data'][$term_id])) {
$rels = $cache['rel_data'][$term_id];
if (!empty($rels['related_brand'])) {
$patch['related_brand_ids'] = $rels['related_brand'];
}
if (!empty($rels['related_posts'])) {
$patch['related_post_ids'] = $rels['related_posts'];
}
if (!empty($rels['related_campaigns'])) {
$patch['related_campaign_ids'] = $rels['related_campaigns'];
}
}
return $patch;
}
private function extract_legacy_for_term($term_id) {
$patch = array();
$seo = get_term_meta($term_id, self::LEGACY_PREFIX . 'seo_title', true);
if ($seo !== '') $patch['seo_title'] = $seo;
$bg = get_term_meta($term_id, self::LEGACY_PREFIX . 'background', true);
if (is_array($bg) && !empty($bg['id'])) {
$patch['image_desktop_id'] = (int) $bg['id'];
} elseif (is_numeric($bg) && (int) $bg > 0) {
$patch['image_desktop_id'] = (int) $bg;
}
$short = get_term_meta($term_id, self::LEGACY_PREFIX . 'short_description', true);
if ($short !== '') $patch['headline'] = $short;
$sidebar = get_term_meta($term_id, self::LEGACY_PREFIX . 'sidebar_text', true);
if ($sidebar !== '') $patch['sidebar_text'] = $sidebar;
$footer = get_term_meta($term_id, self::LEGACY_PREFIX . 'footer_text', true);
if ($footer !== '') $patch['footer_text'] = $footer;
$cols = get_term_meta($term_id, self::LEGACY_PREFIX . 'product_category_columns', true);
if ($cols !== '' && (int) $cols > 0) $patch['columns'] = (int) $cols;
$nested = get_term_meta($term_id, self::LEGACY_PREFIX . 'disable_nested_categories', true);
if ($nested === '1' || $nested === 1) $patch['disable_nested'] = 1;
$landing = array();
for ($i = 1; $i <= 8; $i++) {
$title = get_term_meta($term_id, self::LEGACY_PREFIX . 'landingpage_' . $i . '_title', true);
$link = get_term_meta($term_id, self::LEGACY_PREFIX . 'landingpage_' . $i . '_link', true);
$img = get_term_meta($term_id, self::LEGACY_PREFIX . 'landingpage_' . $i . '_image', true);
$img_id = 0;
if (is_array($img) && !empty($img['id'])) {
$img_id = (int) $img['id'];
} elseif (is_numeric($img) && (int) $img > 0) {
$img_id = (int) $img;
}
if ($title === '' && $link === '' && !$img_id) continue;
$landing[] = array(
'title' => (string) $title,
'link' => (string) $link,
'image_id' => $img_id,
);
}
if (!empty($landing)) $patch['landing_pages'] = $landing;
return $patch;
}
public function ajax_migrate() {
AIEO_DMM::verify_ajax_request();
if (function_exists('ob_get_level') && ob_get_level()) { @ob_end_clean(); }
$stats = $this->run_migration();
wp_send_json_success(array(
'stats' => $stats,
'migrated_at' => time(),
));
}
public function run_h3_headline_migration() {
$stats = array(
'processed' => 0,
'headlines' => 0,
'style_stripped' => 0,
'skipped' => 0,
'errors' => 0,
'taxonomies' => array(),
);
$nav_module = null;
if (class_exists('AIEO_DMM') && method_exists('AIEO_DMM', 'instance')) {
$dmm = AIEO_DMM::instance();
if ($dmm && method_exists($dmm, 'get_module')) {
$nav_module = $dmm->get_module('navigator');
}
}
if ($nav_module) {
remove_action('created_term', array($nav_module, 'on_term_change'), 99);
remove_action('edited_term', array($nav_module, 'on_term_change'), 99);
}
foreach ($this->managed_taxonomies() as $taxonomy) {
if (!taxonomy_exists($taxonomy)) continue;
$stats['taxonomies'][$taxonomy] = 0;
$terms = get_terms(array(
'taxonomy' => $taxonomy,
'hide_empty' => false,
));
if (is_wp_error($terms) || empty($terms)) continue;
foreach ($terms as $term) {
$stats['processed']++;
$desc = (string) $term->description;
if ($desc === '') {
$stats['skipped']++;
continue;
}
$headline_inner = '';
$headline_offset = 0;
$headline_length = 0;
$stripped = '';
if (preg_match('#^\s*
]*>(.*?)
\s*#is', $desc, $m, PREG_OFFSET_CAPTURE)) {
$headline_inner = trim((string) $m[1][0]);
$headline_offset = (int) $m[0][1];
$headline_length = strlen((string) $m[0][0]);
}
elseif (preg_match('#^\s*]*>(.*?)\s*(?:
|\r\n|\n|$)#is', $desc, $m, PREG_OFFSET_CAPTURE)) {
$headline_inner = trim((string) $m[1][0]);
$headline_offset = (int) $m[0][1];
$headline_length = strlen((string) $m[0][0]);
}
elseif (preg_match('#^\s*]*>(.*?)
\s*#is', $desc, $m, PREG_OFFSET_CAPTURE)) {
$headline_inner = trim((string) $m[1][0]);
$headline_offset = (int) $m[0][1];
$headline_length = strlen((string) $m[0][0]);
}
$new_desc = $desc;
if ($headline_length > 0) {
$stripped = trim(html_entity_decode(wp_strip_all_tags($headline_inner), ENT_QUOTES | ENT_HTML5, 'UTF-8'));
if ($stripped === '') {
$headline_inner = '';
$headline_length = 0;
} else {
$new_desc = (string) substr_replace($desc, '', $headline_offset, $headline_length);
}
}
$cleaned = $new_desc;
$cleaned = (string) preg_replace('#\s*text-align\s*:\s*[^;"\']+;?\s*#i', '', $cleaned);
$cleaned = (string) preg_replace('#\s*style\s*=\s*"\s*"#i', '', $cleaned);
$cleaned = (string) preg_replace("#\\s*style\\s*=\\s*'\\s*'#i", '', $cleaned);
$cleaned = trim($cleaned);
if ($cleaned !== $new_desc) {
$stats['style_stripped']++;
$new_desc = $cleaned;
}
$headline_changed = ($headline_length > 0 && $headline_inner !== '');
$description_changed = ($new_desc !== $desc);
if (!$headline_changed && !$description_changed) {
$stats['skipped']++;
continue;
}
if ($headline_changed) {
$ok = self::save($taxonomy, (int) $term->term_id, array('headline' => $stripped));
if ($ok === false) {
$stats['errors']++;
continue;
}
$stats['headlines']++;
}
if ($description_changed) {
$upd = wp_update_term((int) $term->term_id, $taxonomy, array('description' => $new_desc));
if (is_wp_error($upd)) {
$stats['errors']++;
continue;
}
}
$stats['taxonomies'][$taxonomy]++;
}
}
if ($nav_module) {
add_action('created_term', array($nav_module, 'on_term_change'), 99, 3);
add_action('edited_term', array($nav_module, 'on_term_change'), 99, 3);
if (method_exists($nav_module, 'generate_json')) {
$nav_module->generate_json();
}
}
update_option('aieo_dmm_category_extras_h3_migrated_at', time(), false);
return $stats;
}
public function ajax_migrate_h3_headlines() {
AIEO_DMM::verify_ajax_request();
if (function_exists('ob_get_level') && ob_get_level()) { @ob_end_clean(); }
$migrated_at = (int) get_option('aieo_dmm_category_extras_h3_migrated_at', 0);
$force = !empty($_POST['force']);
if ($migrated_at && !$force) {
wp_send_json_error(array(
'message' => __('H3 → headline migration has already run on this site. Re-running would touch term descriptions again. Pass `force=1` to override.', 'ai-eshop-optimizer'),
'migrated_at' => $migrated_at,
));
}
$stats = $this->run_h3_headline_migration();
wp_send_json_success(array(
'stats' => $stats,
'migrated_at' => time(),
));
}
public function ajax_save_settings() {
AIEO_DMM::verify_ajax_request();
$patch = array();
if (isset($_POST['chips_enabled'])) {
$patch['category_extras_chips_enabled'] = !empty($_POST['chips_enabled']);
}
if (isset($_POST['chips_mobile_slider'])) {
$patch['category_extras_chips_mobile_slider'] = !empty($_POST['chips_mobile_slider']);
}
$int_fields = array(
'chips_border_width' => 'category_extras_chips_border_width',
'chips_border_radius' => 'category_extras_chips_border_radius',
'chips_padding_y' => 'category_extras_chips_padding_y',
'chips_padding_x' => 'category_extras_chips_padding_x',
'chips_gap_y' => 'category_extras_chips_gap_y',
'chips_gap_x' => 'category_extras_chips_gap_x',
'nav_border_width' => 'navigator_border_width',
'nav_border_radius' => 'navigator_border_radius',
);
foreach ($int_fields as $post_key => $setting_key) {
if (isset($_POST[$post_key]) && $_POST[$post_key] !== '') {
$patch[$setting_key] = (int) $_POST[$post_key];
}
}
$color_fields = array(
'chips_border_color' => 'category_extras_chips_border_color',
'chips_text_color' => 'category_extras_chips_text_color',
'nav_border_color' => 'navigator_border_color',
);
foreach ($color_fields as $post_key => $setting_key) {
if (isset($_POST[$post_key])) {
$patch[$setting_key] = sanitize_text_field( wp_unslash( (string) $_POST[$post_key] ) );
}
}
if (!empty($patch)) {
AIEO_DMM_Settings::update($patch);
}
wp_send_json_success(array(
'chips_enabled' => self::chips_enabled() ? 1 : 0,
));
}
public function ajax_save() {
AIEO_DMM::verify_ajax_request();
$taxonomy = isset($_POST['taxonomy']) ? sanitize_key( wp_unslash( (string) $_POST['taxonomy'] ) ) : '';
$term_id = isset($_POST['term_id']) ? (int) $_POST['term_id'] : 0;
if (!$taxonomy || !$term_id) {
wp_send_json_error(array('message' => __('Missing taxonomy/term_id.', 'ai-eshop-optimizer')));
}
$payload = array();
foreach (array('headline','description','seo_title','sidebar_text','footer_text','columns','disable_nested','image_mobile_id','image_desktop_id') as $k) {
if (isset($_POST[$k])) {
$payload[$k] = is_array($_POST[$k])
? map_deep(wp_unslash($_POST[$k]), 'sanitize_text_field')
: sanitize_textarea_field(wp_unslash($_POST[$k]));
}
}
if (isset($_POST['landing_pages']) && is_array($_POST['landing_pages'])) {
$payload['landing_pages'] = map_deep(wp_unslash($_POST['landing_pages']), 'sanitize_text_field');
}
$ok = self::save($taxonomy, $term_id, $payload);
if ($ok === false) {
wp_send_json_error(array('message' => __('Save failed.', 'ai-eshop-optimizer')));
}
wp_send_json_success(array('row' => self::get($taxonomy, $term_id)));
}
public function render_term_extras($term, $taxonomy) {
if (!is_object($term) || !isset($term->term_id)) return;
$row = self::get($taxonomy, (int) $term->term_id);
if (!$row) {
$row = array(
'headline' => '',
'image_mobile_id' => 0,
'image_desktop_id' => 0,
'seo_title' => '',
'sidebar_text' => '',
'footer_text' => '',
'columns' => null,
'disable_nested' => 0,
'template' => 'default',
'pro_only' => 0,
'reference_quantity' => '',
'synonyms' => '',
'season_start' => '',
'season_end' => '',
'season_midpoint' => '',
'related_brand_ids' => array(),
'related_post_ids' => array(),
'related_campaign_ids' => array(),
'google_category_id' => null,
'google_category_path' => '',
'facebook_category_id' => null,
'facebook_category_path' => '',
);
}
$mobile_url = $row['image_mobile_id'] ? wp_get_attachment_image_url((int) $row['image_mobile_id'], 'medium') : '';
$desktop_url = $row['image_desktop_id'] ? wp_get_attachment_image_url((int) $row['image_desktop_id'], 'medium') : '';
$brand_chips = $this->resolve_term_chips((array) $row['related_brand_ids'], 'product_brand');
$post_chips = $this->resolve_post_chips((array) $row['related_post_ids']);
$campaign_chips = $this->resolve_post_chips((array) $row['related_campaign_ids']);
$allow_brand_picker = ($taxonomy !== 'product_brand') && taxonomy_exists('product_brand');
?>
term_id, 'aieo_ce_nonce'); ?>
>
>
'aieo_ce[headline]',
'textarea_rows' => 5,
'media_buttons' => true,
'teeny' => false,
'quicktags' => true,
)
); ?>
'aieo_ce[sidebar_text]',
'textarea_rows' => 8,
'media_buttons' => true,
'teeny' => false,
'quicktags' => true,
)
); ?>
'aieo_ce[footer_text]',
'textarea_rows' => 12,
'media_buttons' => true,
'teeny' => false,
'quicktags' => true,
)
); ?>
term_id);
} else {
echo ' ' . esc_html__('Related-tab component unavailable.', 'ai-eshop-optimizer') . ' ';
}
?>
|
$pid, 'label' => $title);
}
return $out;
}
private static function mt_current_notice($path, $set, $about = '') {
$html = '' . esc_html__('Current:', 'ai-eshop-optimizer') . ' '
. '' . esc_html('' !== $path ? $path : __('(not set)', 'ai-eshop-optimizer')) . ''
. '';
return AIEO_DMM_UI::notice($html, 'info');
}
private static function describe_editor($editor_id, $help_id) {
$needle = 'id="' . esc_attr($editor_id) . '">';
$filter = static function ($html) use ($needle, $help_id, &$filter) {
if (!is_string($html) || false === strpos($html, $needle)) {
return $html;
}
remove_filter('the_editor', $filter);
return str_replace($needle, substr($needle, 0, -1) . ' aria-describedby="' . esc_attr($help_id) . '">', $html);
};
add_filter('the_editor', $filter);
}
private function resolve_term_chips(array $ids, $taxonomy) {
$ids = array_filter(array_map('intval', $ids));
if (empty($ids)) return array();
$out = array();
foreach ($ids as $tid) {
$term = get_term($tid, $taxonomy);
$label = ($term && !is_wp_error($term)) ? $term->name : ('#' . $tid);
$out[] = array('id' => $tid, 'label' => $label);
}
return $out;
}
public function save_term_extras($term_id, $tt_id = 0) {
$term_id = (int) $term_id;
if (!$term_id) return;
if (empty($_POST['aieo_ce_nonce']) || !wp_verify_nonce(
sanitize_key(wp_unslash($_POST['aieo_ce_nonce'])),
'aieo_ce_term_edit_' . $term_id
)) {
return;
}
if (empty($_POST['aieo_ce']) || !is_array($_POST['aieo_ce'])) return;
$term = get_term($term_id);
if (!$term || is_wp_error($term)) return;
if (!in_array($term->taxonomy, $this->managed_taxonomies(), true)) return;
$raw = map_deep(wp_unslash($_POST['aieo_ce']), 'sanitize_textarea_field');
$patch = array();
if (isset($raw['headline'])) $patch['headline'] = (string) $raw['headline'];
if (isset($raw['image_mobile_id'])) $patch['image_mobile_id'] = (int) $raw['image_mobile_id'];
if (isset($raw['image_desktop_id'])) $patch['image_desktop_id'] = (int) $raw['image_desktop_id'];
if (isset($raw['seo_title'])) $patch['seo_title'] = (string) $raw['seo_title'];
if (isset($raw['sidebar_text'])) $patch['sidebar_text'] = (string) $raw['sidebar_text'];
if (isset($raw['footer_text'])) $patch['footer_text'] = (string) $raw['footer_text'];
if (isset($raw['template'])) $patch['template'] = (string) $raw['template'];
$patch['columns'] = (isset($raw['columns']) && $raw['columns'] !== '') ? (int) $raw['columns'] : null;
$patch['disable_nested'] = !empty($raw['disable_nested']) ? 1 : 0;
$patch['pro_only'] = !empty($raw['pro_only']) ? 1 : 0;
$patch['reference_quantity'] = (isset($raw['reference_quantity']) && $raw['reference_quantity'] !== '') ? (int) $raw['reference_quantity'] : null;
if (isset($raw['synonyms'])) $patch['synonyms'] = (string) $raw['synonyms'];
foreach (array('season_start', 'season_end', 'season_midpoint') as $k) {
if (array_key_exists($k, $raw)) $patch[$k] = (string) $raw[$k];
}
if ($term->taxonomy === 'product_cat') {
foreach (array('google', 'facebook') as $source) {
$id_key = $source . '_category_id';
$path_key = $source . '_category_path';
if (array_key_exists($id_key, $raw) || array_key_exists($path_key, $raw)) {
$id = isset($raw[$id_key]) ? (int) $raw[$id_key] : 0;
$path = isset($raw[$path_key]) ? (string) $raw[$path_key] : '';
$patch[$id_key] = $id > 0 ? $id : null;
$patch[$path_key] = $path !== '' ? $path : null;
}
}
}
self::save($term->taxonomy, $term_id, $patch);
}
public function enqueue_term_admin_assets($hook) {
if ($hook !== 'term.php') return;
$tax = isset($_GET['taxonomy']) ? sanitize_key(wp_unslash($_GET['taxonomy'])) : '';
if (!$tax || !in_array($tax, $this->description_editor_taxonomies(), true)) return;
wp_enqueue_media();
wp_enqueue_editor();
wp_enqueue_style(
'aieo-dmm-category-extras-admin',
AIEO_URI . 'assets/css/aieo-dmm-category-extras-admin.css',
array(),
AIEO_VERSION
);
wp_enqueue_script(
'aieo-dmm-category-extras-admin',
AIEO_URI . 'assets/js/aieo-dmm-category-extras-admin.js',
array('jquery'),
AIEO_VERSION,
true
);
wp_localize_script('aieo-dmm-category-extras-admin', 'aieoCategoryExtras', array(
'ajaxurl' => admin_url('admin-ajax.php'),
'nonce' => wp_create_nonce(AIEO_DMM::NONCE_ACTION),
'i18n' => array(
'noResults' => __('No matches.', 'ai-eshop-optimizer'),
'searching' => __('Searching…', 'ai-eshop-optimizer'),
'pickImage' => __('Select image', 'ai-eshop-optimizer'),
'useImage' => __('Use this image', 'ai-eshop-optimizer'),
'notSet' => __('(not set)', 'ai-eshop-optimizer'),
'rootLabel' => __('Top level', 'ai-eshop-optimizer'),
'pickPrompt' => __('— pick —', 'ai-eshop-optimizer'),
'picked' => __('Picked', 'ai-eshop-optimizer'),
'browseAria' => __('Browse taxonomy level', 'ai-eshop-optimizer'),
),
'mt' => array(
'actionBrowse' => 'aieo_dmm_marketplace_tax_browse',
'actionSearch' => 'aieo_dmm_marketplace_tax_search',
),
));
}
public function ajax_search() {
AIEO_DMM::verify_ajax_request();
$source = isset($_POST['source']) ? sanitize_text_field(wp_unslash($_POST['source'])) : '';
$q = isset($_POST['q']) ? sanitize_text_field(wp_unslash($_POST['q'])) : '';
$allowed_post_types = (array) apply_filters(
'aieo_dmm_category_extras_search_post_types',
array('post', 'campaign', 'page')
);
$allowed_taxonomies = (array) apply_filters(
'aieo_dmm_category_extras_search_taxonomies',
array('product_brand', 'product_cat')
);
$items = array();
if (strpos($source, 'post:') === 0) {
$type = sanitize_key(substr($source, 5));
if (in_array($type, $allowed_post_types, true)) {
$posts = get_posts(array(
'post_type' => $type,
'post_status' => 'publish',
's' => $q,
'posts_per_page' => 20,
'orderby' => 'date',
'order' => 'DESC',
'no_found_rows' => true,
));
foreach ($posts as $p) {
$items[] = array('id' => (int) $p->ID, 'label' => get_the_title($p) ?: '#' . $p->ID);
}
}
} elseif (strpos($source, 'taxonomy:') === 0) {
$tax = sanitize_key(substr($source, 9));
if (in_array($tax, $allowed_taxonomies, true) && taxonomy_exists($tax)) {
$terms = get_terms(array(
'taxonomy' => $tax,
'hide_empty' => false,
'name__like' => $q,
'number' => 20,
'orderby' => 'name',
'order' => 'ASC',
));
if (!is_wp_error($terms)) {
foreach ($terms as $t) {
$items[] = array('id' => (int) $t->term_id, 'label' => $t->name);
}
}
}
}
wp_send_json_success(array('items' => $items));
}
public function ajax_marketplace_tax_browse() {
AIEO_DMM::verify_ajax_request();
$source = isset($_POST['source']) ? sanitize_key(wp_unslash($_POST['source'])) : '';
$parent_id = isset($_POST['parent_id']) ? (int) $_POST['parent_id'] : 0;
if (!in_array($source, array('google', 'facebook'), true)) {
wp_send_json_error(array('message' => __('Bad source.', 'ai-eshop-optimizer')));
}
if (!class_exists('AIEO_DMM_Marketplace_Taxonomy')) {
wp_send_json_error(array('message' => __('Marketplace taxonomy helper unavailable.', 'ai-eshop-optimizer')));
}
$children = AIEO_DMM_Marketplace_Taxonomy::get_children($source, $parent_id);
$out = array();
foreach ($children as $n) {
$has_kids = !empty(AIEO_DMM_Marketplace_Taxonomy::get_children($source, (int) $n['i']));
$out[] = array(
'id' => (int) $n['i'],
'name' => (string) $n['n'],
'path' => (string) $n['h'],
'has_children' => $has_kids,
'depth' => (int) $n['d'],
);
}
wp_send_json_success(array('nodes' => $out));
}
public function ajax_marketplace_tax_search() {
AIEO_DMM::verify_ajax_request();
$source = isset($_POST['source']) ? sanitize_key(wp_unslash($_POST['source'])) : '';
$q = isset($_POST['q']) ? sanitize_text_field(wp_unslash($_POST['q'])) : '';
if (!in_array($source, array('google', 'facebook'), true)) {
wp_send_json_error(array('message' => __('Bad source.', 'ai-eshop-optimizer')));
}
if (!class_exists('AIEO_DMM_Marketplace_Taxonomy')) {
wp_send_json_error(array('message' => __('Marketplace taxonomy helper unavailable.', 'ai-eshop-optimizer')));
}
$hits = AIEO_DMM_Marketplace_Taxonomy::search($source, $q, 25);
$out = array();
foreach ($hits as $n) {
$out[] = array(
'id' => (int) $n['i'],
'name' => (string) $n['n'],
'path' => (string) $n['h'],
'site_path' => (string) ($n['site_path'] ?? $n['h']),
'matched_locale' => (string) ($n['matched_locale'] ?? ''),
'depth' => (int) $n['d'],
);
}
wp_send_json_success(array('hits' => $out));
}
public static function get_template($taxonomy, $term_id) {
$row = self::get($taxonomy, $term_id);
if (!$row || empty($row['template'])) return 'default';
$allowed = array_keys(self::templates());
return in_array($row['template'], $allowed, true) ? $row['template'] : 'default';
}
public static function should_hide_products($taxonomy, $term_id) {
$tpl = self::get_template($taxonomy, $term_id);
return in_array($tpl, array('landing', 'hidden'), true);
}
private function current_term() {
if (!function_exists('is_tax') && !function_exists('is_category')) return null;
$obj = get_queried_object();
if (!$obj || empty($obj->term_id) || empty($obj->taxonomy)) return null;
if (!in_array($obj->taxonomy, $this->managed_taxonomies(), true)) return null;
return $obj;
}
public function shortcode_category_hero($atts = array()) {
$atts = shortcode_atts(array(
'taxonomy' => '',
'term_id' => 0,
'template' => '',
), (array) $atts, 'aieo_category_hero');
if (empty($atts['taxonomy']) || empty($atts['term_id'])) {
$term = $this->current_term();
if (!$term) return '';
$atts['taxonomy'] = $term->taxonomy;
$atts['term_id'] = (int) $term->term_id;
}
return $this->render_hero($atts['taxonomy'], (int) $atts['term_id'], (string) $atts['template']);
}
public function register_block() {
if (!function_exists('register_block_type')) return;
if (class_exists('AIEO_Engine_Blocks', false) && AIEO_Engine_Blocks::none_rendered()) { return; }
register_block_type(__DIR__ . '/category-blocks/category-hero', array(
'title' => __('Rooster One Category Hero', 'ai-eshop-optimizer'),
'category' => 'widgets',
'icon' => 'cover-image',
'attributes' => array(
'taxonomy' => array('type' => 'string', 'default' => ''),
'termId' => array('type' => 'number', 'default' => 0),
'template' => array('type' => 'string', 'default' => ''),
'align' => array('type' => 'string'),
),
'supports' => array(
'align' => array('wide', 'full'),
),
'render_callback' => array($this, 'render_block_callback'),
));
register_block_type(__DIR__ . '/category-blocks/category-chips', array(
'title' => __('Rooster One Category Chips', 'ai-eshop-optimizer'),
'category' => 'widgets',
'icon' => 'tag',
'attributes' => array(
'taxonomy' => array('type' => 'string', 'default' => ''),
'termId' => array('type' => 'number', 'default' => 0),
'align' => array('type' => 'string'),
),
'supports' => array(
'align' => array('wide', 'full'),
),
'render_callback' => array($this, 'render_chips_block_callback'),
));
if (!wp_style_is('aieo-dmm-category-extras', 'registered')) {
$css_rel = 'assets/css/aieo-dmm-category-extras.css';
$css_path = defined('AIEO_DIR') ? AIEO_DIR . $css_rel : '';
$css_ver = ($css_path && file_exists($css_path)) ? (string) filemtime($css_path) : (defined('AIEO_VERSION') ? AIEO_VERSION : '1.0');
wp_register_style('aieo-dmm-category-extras', AIEO_URI . $css_rel, array(), $css_ver);
}
register_block_type(__DIR__ . '/category-blocks/page-hero', array(
'title' => __('Rooster One Page Hero', 'ai-eshop-optimizer'),
'category' => 'theme',
'icon' => 'cover-image',
'description' => __('Manual hero — top title, left headline, right description and desktop/mobile images. Identical look to the category hero, usable on any page.', 'ai-eshop-optimizer'),
'keywords' => array('hero', 'banner', 'title', 'headline', 'image'),
'attributes' => array(
'topTitle' => array('type' => 'string', 'default' => ''),
'headline' => array('type' => 'string', 'default' => ''),
'description' => array('type' => 'string', 'default' => ''),
'desktopId' => array('type' => 'number', 'default' => 0),
'desktopUrl' => array('type' => 'string', 'default' => ''),
'mobileId' => array('type' => 'number', 'default' => 0),
'mobileUrl' => array('type' => 'string', 'default' => ''),
'align' => array('type' => 'string', 'default' => ''),
),
'supports' => array('align' => array('wide', 'full')),
'style' => 'aieo-dmm-category-extras',
'editor_style' => 'aieo-dmm-category-extras',
'render_callback' => array($this, 'render_page_hero_block'),
));
register_block_type(__DIR__ . '/category-blocks/blog-hero', array(
'title' => __('Rooster One Blog Hero (Latest Post)', 'ai-eshop-optimizer'),
'category' => 'theme',
'icon' => 'admin-post',
'description' => __('Featured latest-post hero with the category-hero look: a top title (default “Blog”), the newest post’s title as the headline, its excerpt as the description and its featured image — all clickable, no underline. On a blog category/tag archive it features that term’s latest post.', 'ai-eshop-optimizer'),
'keywords' => array('blog', 'hero', 'header', 'latest', 'post', 'archive', 'category'),
'attributes' => array(
'postId' => array('type' => 'number', 'default' => 0),
'topTitle' => array('type' => 'string', 'default' => ''),
'showExcerpt' => array('type' => 'boolean', 'default' => true),
'align' => array('type' => 'string', 'default' => ''),
),
'supports' => array('align' => array('wide', 'full')),
'style' => 'aieo-dmm-category-extras',
'editor_style' => 'aieo-dmm-category-extras',
'render_callback' => array($this, 'render_blog_hero_block'),
));
register_block_type(__DIR__ . '/category-blocks/blog-subcategories', array(
'title' => __('Rooster One Blog Subcategories (Dropdown)', 'ai-eshop-optimizer'),
'category' => 'theme',
'icon' => 'list-view',
'description' => __('A dropdown of the current blog category’s subcategories, styled like the blog-post categories dropdown. Renders nothing when the category has none.', 'ai-eshop-optimizer'),
'keywords' => array('blog', 'category', 'subcategories', 'dropdown', 'filter'),
'attributes' => array(
'categoryId' => array('type' => 'number', 'default' => 0),
'label' => array('type' => 'string', 'default' => ''),
),
'supports' => array('spacing' => array('margin' => true, 'padding' => true), 'typography' => array('fontSize' => true)),
'style' => 'aieo-dmm-category-extras',
'editor_style' => 'aieo-dmm-category-extras',
'render_callback' => array($this, 'render_blog_subcategories_block'),
));
register_block_type(__DIR__ . '/category-blocks/category-lead', array(
'title' => __('Rooster One Category Lead', 'ai-eshop-optimizer'),
'category' => 'theme',
'icon' => 'text',
'description' => __('The current category’s description, falling back to its name when it has none. For blog category archive headers.', 'ai-eshop-optimizer'),
'keywords' => array('category', 'blog', 'description', 'title', 'archive'),
'attributes' => array('categoryId' => array('type' => 'number', 'default' => 0)),
'render_callback' => array($this, 'render_category_lead_block'),
));
register_block_type(__DIR__ . '/category-blocks/category-footer-hero', array(
'title' => __('Rooster One Category Footer Hero', 'ai-eshop-optimizer'),
'category' => 'widgets',
'icon' => 'align-pull-left',
'description' => __('The category’s SEO footer text beside the category image (image left, rich text right) with the hero divider line. Auto-fills from the queried product category.', 'ai-eshop-optimizer'),
'keywords' => array('category', 'footer', 'seo', 'hero', 'archive', 'description'),
'attributes' => array(
'taxonomy' => array('type' => 'string', 'default' => ''),
'termId' => array('type' => 'number', 'default' => 0),
'align' => array('type' => 'string'),
),
'supports' => array('align' => array('wide', 'full')),
'style' => 'aieo-dmm-category-extras',
'editor_style' => 'aieo-dmm-category-extras',
'render_callback' => array($this, 'render_category_footer_hero_block'),
));
}
public function render_block_callback($attributes) {
$tax = isset($attributes['taxonomy']) ? sanitize_key((string) $attributes['taxonomy']) : '';
$tid = isset($attributes['termId']) ? (int) $attributes['termId'] : 0;
$tpl = isset($attributes['template']) ? sanitize_key((string) $attributes['template']) : '';
$align = isset($attributes['align']) ? sanitize_key((string) $attributes['align']) : '';
if (!$tax || !$tid) {
$term = $this->current_term();
if (!$term) return '';
$tax = $term->taxonomy;
$tid = (int) $term->term_id;
}
$html = $this->render_hero($tax, $tid, $tpl);
if ($html && in_array($align, array('wide', 'full'), true)) {
$html = preg_replace(
'/^render_responsive_image($mobile_id, $desktop_id, $title, true);
if ($title === '' && $headline === '' && $desc === '' && $img_html === '') {
return '';
}
$extra = in_array($align, array('wide', 'full'), true) ? array('align' . $align) : array();
return $this->build_hero_html($title, $headline, $desc, $img_html, 'default', $extra);
}
public function render_blog_hero_block($attributes) {
$attributes = is_array($attributes) ? $attributes : array();
$post_id = isset($attributes['postId']) ? (int) $attributes['postId'] : 0;
if ($post_id <= 0) {
$qo = get_queried_object();
if ($qo instanceof WP_Term && in_array($qo->taxonomy, array('category', 'post_tag'), true)) {
$post_id = $this->latest_post_in_term($qo->taxonomy, (int) $qo->term_id);
} else {
$ids = get_posts(array('post_type' => 'post', 'posts_per_page' => 1, 'post_status' => 'publish', 'ignore_sticky_posts' => true, 'no_found_rows' => true, 'fields' => 'ids'));
$post_id = !empty($ids) ? (int) $ids[0] : 0;
}
}
if ($post_id <= 0 || get_post_type($post_id) !== 'post' || get_post_status($post_id) !== 'publish') {
return '';
}
$top = isset($attributes['topTitle']) ? trim((string) $attributes['topTitle']) : '';
if ($top === '') {
$qo = get_queried_object();
$top = ($qo instanceof WP_Term) ? (string) $qo->name : __('Blog', 'ai-eshop-optimizer');
}
$headline = get_the_title($post_id);
$show_excerpt = !isset($attributes['showExcerpt']) || !empty($attributes['showExcerpt']);
$desc = '';
if ($show_excerpt) {
$desc = has_excerpt($post_id)
? get_the_excerpt($post_id)
: wp_trim_words(wp_strip_all_tags(strip_shortcodes((string) get_post_field('post_content', $post_id))), 30);
}
$link = (string) get_permalink($post_id);
$thumb_id = (int) get_post_thumbnail_id($post_id);
$img_html = $thumb_id ? $this->render_responsive_image($thumb_id, $thumb_id, $headline, true) : '';
$align = isset($attributes['align']) ? sanitize_key((string) $attributes['align']) : '';
$extra = in_array($align, array('wide', 'full'), true) ? array('align' . $align) : array();
return $this->build_hero_html($top, $headline, $desc, $img_html, 'blog', $extra, $link);
}
public function render_blog_subcategories_block($attributes) {
$attributes = is_array($attributes) ? $attributes : array();
$term_id = isset($attributes['categoryId']) ? (int) $attributes['categoryId'] : 0;
if ($term_id <= 0) {
$qo = get_queried_object();
if ($qo instanceof WP_Term && $qo->taxonomy === 'category') { $term_id = (int) $qo->term_id; }
}
if ($term_id <= 0) { return ''; }
$children = get_terms(array(
'taxonomy' => 'category',
'parent' => $term_id,
'hide_empty' => false,
'orderby' => 'name',
'order' => 'ASC',
));
if (empty($children) || is_wp_error($children)) {
$term = get_term($term_id, 'category');
$parent_id = ($term && !is_wp_error($term)) ? (int) $term->parent : 0;
if ($parent_id > 0) {
$children = get_terms(array(
'taxonomy' => 'category',
'parent' => $parent_id,
'hide_empty' => false,
'orderby' => 'name',
'order' => 'ASC',
));
}
}
if (empty($children) || is_wp_error($children)) { return ''; }
$label = (isset($attributes['label']) && trim((string) $attributes['label']) !== '')
? trim((string) $attributes['label'])
: __('Categories', 'ai-eshop-optimizer');
$opts = array();
foreach ($children as $child) {
$link = get_term_link($child);
if (!$link || is_wp_error($link)) { continue; }
$opts[] = array('name' => $child->name, 'url' => (string) $link);
}
if (empty($opts)) { return ''; }
$wrapper = get_block_wrapper_attributes(array('class' => 'aieo-blog-subcats'));
$arrow = 'data:image/svg+xml,%3Csvg%20xmlns=%22http://www.w3.org/2000/svg%22%20viewBox=%220%200%2012%2012%22%20fill=%22%23222222%22%3E%3Cpath%20d=%22M6%208.5%201.5%204h9z%22/%3E%3C/svg%3E';
$sel_style = 'appearance:none;-webkit-appearance:none;-moz-appearance:none;width:auto;max-width:100%;'
. 'padding:7px 28px 7px 12px;border:1px solid var(--wp--preset--color--contrast,#1a1a1a);border-radius:999px;'
. 'background-color:transparent;color:inherit;font-family:inherit;font-size:0.8rem;line-height:1.2;cursor:pointer;'
. 'background-image:url(' . $arrow . ');background-repeat:no-repeat;background-position:right 10px center;background-size:9px 9px;';
ob_start();
?>
>
taxonomy === 'category') { $term_id = (int) $qo->term_id; }
}
if ($term_id <= 0) { return ''; }
$term = get_term($term_id, 'category');
if (!$term || is_wp_error($term)) { return ''; }
$desc = trim((string) $term->description);
$is_desc = ($desc !== '');
$text = $is_desc ? $desc : (string) $term->name;
if (trim($text) === '') { return ''; }
$wrapper = get_block_wrapper_attributes(array('class' => 'rooster-blog-archive__term-description roosterx-blog-archive__term-description aieo-category-lead' . ($is_desc ? '' : ' aieo-category-lead--name')));
return ''
. ($is_desc ? wp_kses_post(wpautop($desc)) : esc_html($text))
. '
';
}
public function render_category_footer_hero_block($attributes) {
$attributes = is_array($attributes) ? $attributes : array();
$tax = isset($attributes['taxonomy']) ? sanitize_key((string) $attributes['taxonomy']) : '';
$tid = isset($attributes['termId']) ? (int) $attributes['termId'] : 0;
if (!$tax || $tid <= 0) {
$qo = get_queried_object();
if ($qo instanceof WP_Term) { $tax = (string) $qo->taxonomy; $tid = (int) $qo->term_id; }
}
if (!$tax || $tid <= 0) { return ''; }
$row = array();
$footer = '';
if ($tax === 'product_brand' && class_exists('AIEO_DMM_Brand_Extras_Data')) {
$row = AIEO_DMM_Brand_Extras_Data::get($tax, $tid); $footer = (string) (is_array($row) ? ($row['footer_text'] ?? '') : '');
} elseif ($tax === 'pa_need' && class_exists('AIEO_DMM_Need_Extras_Data')) {
$row = AIEO_DMM_Need_Extras_Data::get($tax, $tid); $footer = (string) (is_array($row) ? ($row['footer_text'] ?? '') : '');
} elseif ($tax === 'product_cat') {
$row = self::get($tax, $tid); $footer = is_array($row) ? (string) ($row['footer_text'] ?? '') : '';
}
$term = get_term($tid, $tax);
$footer = (string) apply_filters('aieo_taxonomy_archive_footer_text', $footer, $tax, $tid, ($term && !is_wp_error($term)) ? $term : null);
$footer = trim($footer);
if ($footer === '') { return ''; }
$img_html = '';
if ($tax === 'product_cat' && is_array($row)) {
$mobile_id = !empty($row['image_mobile_id']) ? (int) $row['image_mobile_id'] : 0;
$desktop_id = !empty($row['image_desktop_id']) ? (int) $row['image_desktop_id'] : 0;
if ((!$mobile_id || !$desktop_id) && $term && !is_wp_error($term) && !empty($term->parent)) {
$ancestor = $this->resolve_root_ancestor($tax, $tid);
if ($ancestor) {
$anc_row = self::get($tax, (int) $ancestor->term_id);
if (is_array($anc_row)) {
if (!$mobile_id && !empty($anc_row['image_mobile_id'])) $mobile_id = (int) $anc_row['image_mobile_id'];
if (!$desktop_id && !empty($anc_row['image_desktop_id'])) $desktop_id = (int) $anc_row['image_desktop_id'];
}
}
}
$alt = ($term && !is_wp_error($term)) ? $term->name : '';
$img_html = $this->render_responsive_image($mobile_id, $desktop_id, $alt);
}
$align = isset($attributes['align']) ? sanitize_html_class((string) $attributes['align']) : '';
$align_cls = ('full' === $align) ? ' alignfull' : (('wide' === $align) ? ' alignwide' : '');
$no_img = $img_html ? '' : ' aieo-category-footer-hero--no-image';
$img_kses = array(
'picture' => array('class' => array()),
'source' => array('media' => array(), 'srcset' => array(), 'data-srcset' => array(), 'sizes' => array(), 'type' => array()),
'img' => array('src' => array(), 'srcset' => array(), 'sizes' => array(), 'alt' => array(), 'class' => array(), 'width' => array(), 'height' => array(), 'loading' => array(), 'decoding' => array()),
'figure' => array('class' => array()),
'span' => array('class' => array()),
);
ob_start();
?>
'post',
'posts_per_page' => 1,
'post_status' => 'publish',
'ignore_sticky_posts' => true,
'no_found_rows' => true,
'fields' => 'ids',
'tax_query' => array(array('taxonomy' => (string) $taxonomy, 'field' => 'term_id', 'terms' => $term_id)),
));
return !empty($ids) ? (int) $ids[0] : 0;
}
public function exclude_blog_hero_post($query) {
if (is_admin() || !($query instanceof WP_Query) || !$query->is_main_query() || !$query->is_category()) {
return;
}
$term_id = (int) $query->get('cat');
if ($term_id <= 0) {
$slug = (string) $query->get('category_name');
if ($slug !== '') {
$slug = sanitize_title(basename(untrailingslashit($slug)));
$t = get_term_by('slug', $slug, 'category');
if ($t && !is_wp_error($t)) { $term_id = (int) $t->term_id; }
}
}
if ($term_id <= 0) { return; }
$featured = $this->latest_post_in_term('category', $term_id);
if ($featured <= 0) { return; }
if (!apply_filters('aieo_dmm_exclude_blog_hero_post', true, $term_id, $query)) {
return;
}
$exclude = array_map('intval', (array) $query->get('post__not_in'));
if (!in_array($featured, $exclude, true)) {
$exclude[] = $featured;
$query->set('post__not_in', $exclude);
}
}
public function render_hero($taxonomy, $term_id, $template_override = '') {
$row = self::get($taxonomy, $term_id);
$term = get_term($term_id, $taxonomy);
if (!$term || is_wp_error($term)) return '';
$template = $template_override !== '' ? sanitize_key($template_override) : self::get_template($taxonomy, $term_id);
if ($template === 'hidden') return '';
$row = is_array($row) ? $row : array();
$title = !empty($row['seo_title']) ? (string) $row['seo_title'] : $term->name;
$headline = !empty($row['headline']) ? (string) $row['headline'] : '';
$desc = (string) $term->description;
$mobile_id = !empty($row['image_mobile_id']) ? (int) $row['image_mobile_id'] : 0;
$desktop_id = !empty($row['image_desktop_id']) ? (int) $row['image_desktop_id'] : 0;
if ((!$mobile_id || !$desktop_id) && !empty($term->parent)) {
$ancestor = $this->resolve_root_ancestor($taxonomy, (int) $term->term_id);
if ($ancestor) {
$anc_row = self::get($taxonomy, (int) $ancestor->term_id);
if (is_array($anc_row)) {
if (!$mobile_id && !empty($anc_row['image_mobile_id'])) $mobile_id = (int) $anc_row['image_mobile_id'];
if (!$desktop_id && !empty($anc_row['image_desktop_id'])) $desktop_id = (int) $anc_row['image_desktop_id'];
}
}
}
$img_html = $this->render_responsive_image($mobile_id, $desktop_id, $title, true);
return $this->build_hero_html($title, $headline, $desc, $img_html, $template);
}
private function build_hero_html($title, $headline, $desc, $img_html, $template = 'default', $extra_classes = array(), $link = '') {
$link = (string) $link;
$classes = array_merge(array(
'aieo-category-hero',
'aieo-category-hero--' . sanitize_html_class($template),
), (array) $extra_classes);
if ($link !== '') { $classes[] = 'aieo-category-hero--linked'; }
if (!$headline) $classes[] = 'aieo-category-hero--no-headline';
if (!$desc) $classes[] = 'aieo-category-hero--no-description';
ob_start();
?>
array('class' => array()),
'source' => array(
'media' => array(),
'srcset' => array(),
'data-srcset' => array(),
'sizes' => array(),
'type' => array(),
),
'img' => array(
'src' => array(),
'srcset' => array(),
'sizes' => array(),
'alt' => array(),
'class' => array(),
'width' => array(),
'height' => array(),
'loading' => array(),
'decoding' => array(),
),
'figure' => array('class' => array()),
'span' => array('class' => array()),
)
);
?>
' . wp_kses_post($headline) . ''; }
else { echo wp_kses_post($headline); }
?>
' . wp_kses_post(wpautop($desc)) . ''; }
else { echo wp_kses_post(wpautop($desc)); }
?>
parent > 0) {
$current = get_term((int) $current->parent, $taxonomy);
}
$cache[$key] = ($current && !is_wp_error($current)) ? $current : null;
return $cache[$key];
}
private function render_responsive_image($mobile_id, $desktop_id, $alt_fallback, $is_lcp = false) {
if (!$mobile_id && !$desktop_id) return '';
$alt = (string) $alt_fallback;
$img_attr = array('alt' => $alt, 'class' => 'aieo-category-hero__img', 'loading' => $is_lcp ? 'eager' : 'lazy', 'decoding' => 'async');
if ($is_lcp) { $img_attr['fetchpriority'] = 'high'; }
if ($mobile_id && $desktop_id) {
$desktop_url = wp_get_attachment_image_url($desktop_id, 'full');
$desktop_srcset = wp_get_attachment_image_srcset($desktop_id, 'full');
$mobile_img = wp_get_attachment_image(
$mobile_id, 'large', false,
$img_attr
);
if ($mobile_img && $desktop_url) {
$sources = '' . $sources . $mobile_img . '';
}
}
$only = $desktop_id ?: $mobile_id;
return wp_get_attachment_image(
$only, 'large', false,
$img_attr
);
}
private function resolve_chip_siblings($taxonomy, $term_id) {
$out = array('parent' => null, 'items' => array());
$term = get_term((int) $term_id, $taxonomy);
if (!$term || is_wp_error($term)) return $out;
$kids = get_term_children($term->term_id, $taxonomy);
$parent_id = (!empty($kids) && !is_wp_error($kids))
? (int) $term->term_id
: ((int) $term->parent ?: (int) $term->term_id);
$args = (array) apply_filters('aieo_dmm_category_chips_query_args', array(
'taxonomy' => $taxonomy,
'parent' => $parent_id,
'hide_empty' => true,
'orderby' => 'menu_order',
'order' => 'ASC',
), $taxonomy, $term_id);
$siblings = get_terms($args);
if (is_wp_error($siblings)) $siblings = array();
$excluded = $this->excluded_term_keys_normalised();
if (!empty($excluded)) {
$siblings = array_values(array_filter($siblings, static function ($t) use ($taxonomy, $excluded) {
return !isset($excluded[$taxonomy . ':' . (int) $t->term_id]);
}));
}
$out['items'] = $siblings;
if ($parent_id !== (int) $term->term_id) {
$parent = get_term($parent_id, $taxonomy);
if ($parent && !is_wp_error($parent)) {
$out['parent'] = $parent;
}
}
return $out;
}
private function excluded_term_keys_normalised() {
$raw = (string) AIEO_DMM_Settings::get('navigator_excluded_terms', '');
if ($raw === '') return array();
$out = array();
foreach (preg_split('/[,\s]+/', $raw) as $tok) {
$tok = trim($tok);
if ($tok === '') continue;
if (strpos($tok, ':') === false) {
$out[] = 'product_cat:' . (int) $tok;
} else {
list($tax, $id) = explode(':', $tok, 2);
$out[] = sanitize_key($tax) . ':' . (int) $id;
}
}
return array_flip(array_unique($out));
}
public function shortcode_category_chips($atts = array()) {
$atts = shortcode_atts(array(
'taxonomy' => '',
'term_id' => 0,
), (array) $atts, 'aieo_category_chips');
if (empty($atts['taxonomy']) || empty($atts['term_id'])) {
$term = $this->current_term();
if (!$term) return '';
$atts['taxonomy'] = $term->taxonomy;
$atts['term_id'] = (int) $term->term_id;
}
return $this->render_chip_strip($atts['taxonomy'], (int) $atts['term_id']);
}
public function render_chips_block_callback($attributes) {
$tax = isset($attributes['taxonomy']) ? sanitize_key((string) $attributes['taxonomy']) : '';
$tid = isset($attributes['termId']) ? (int) $attributes['termId'] : 0;
$align = isset($attributes['align']) ? sanitize_key((string) $attributes['align']) : '';
if (!$tax || !$tid) {
$term = $this->current_term();
if (!$term) return '';
$tax = $term->taxonomy;
$tid = (int) $term->term_id;
}
$html = $this->render_chip_strip($tax, $tid);
if ($html && in_array($align, array('wide', 'full'), true)) {
$html = preg_replace(
'/^(\s*)