prefix . 'aieo_dmm_promo_surfaces'; } public static function tbl_slots() { global $wpdb; return $wpdb->prefix . 'aieo_dmm_promo_slots'; } public static function get_surface_by_slug($slug) { global $wpdb; $tbl = self::tbl_surfaces(); return $wpdb->get_row($wpdb->prepare("SELECT * FROM {$tbl} WHERE slug = %s LIMIT 1", $slug), ARRAY_A); } public static function get_surface($surface_id) { global $wpdb; $tbl = self::tbl_surfaces(); return $wpdb->get_row($wpdb->prepare("SELECT * FROM {$tbl} WHERE surface_id = %d LIMIT 1", (int) $surface_id), ARRAY_A); } public static function list_surfaces() { global $wpdb; $tbl = self::tbl_surfaces(); return $wpdb->get_results("SELECT * FROM {$tbl} ORDER BY label, slug", ARRAY_A) ?: array(); } public static function list_slots($surface_id) { global $wpdb; $tbl = self::tbl_slots(); return $wpdb->get_results($wpdb->prepare( "SELECT * FROM {$tbl} WHERE surface_id = %d ORDER BY position, sort_order, slot_id", (int) $surface_id ), ARRAY_A) ?: array(); } public function ajax_save_surface() { AIEO_DMM::verify_ajax_request(); global $wpdb; $surface_id = isset($_POST['surface_id']) ? (int) $_POST['surface_id'] : 0; $slug = sanitize_key(wp_unslash($_POST['slug'] ?? '')); $label = sanitize_text_field(wp_unslash($_POST['label'] ?? '')); $layout = sanitize_key(wp_unslash($_POST['layout_preset'] ?? 'hero_plus_3banners')); $is_active = !empty($_POST['is_active']) ? 1 : 0; if ($slug === '') { wp_send_json_error(array('message' => __('Slug is required.', 'ai-eshop-optimizer'))); } $allowed_layouts = array('hero_plus_3banners', 'hero_only', 'banners_only'); if (!in_array($layout, $allowed_layouts, true)) { $layout = 'hero_plus_3banners'; } $tbl = self::tbl_surfaces(); $data = array( 'slug' => $slug, 'label' => $label, 'layout_preset' => $layout, 'is_active' => $is_active, ); if (class_exists('AIEO_Display_Conditions_UI')) { $existing = $surface_id > 0 ? self::surface_conditions($surface_id) : array(); $posted = array(); foreach ($_POST as $k => $v) { if (strncmp((string) $k, 'cond_', 5) === 0) { $posted[$k] = is_scalar($v) ? sanitize_text_field(wp_unslash((string) $v)) : ''; } } if (!empty($posted)) { $data['conditions'] = wp_json_encode(AIEO_Display_Conditions_UI::collect($posted, $existing)); } } if ($surface_id > 0) { $wpdb->update($tbl, $data, array('surface_id' => $surface_id)); } else { $wpdb->insert($tbl, $data); $surface_id = (int) $wpdb->insert_id; } self::rebuild_conditional_surface_map(); self::purge_page_cache(); wp_send_json_success(array( 'message' => __('Surface saved.', 'ai-eshop-optimizer'), 'surface_id' => $surface_id, )); } public static function surface_conditions($surface_id) { $row = self::get_surface((int) $surface_id); return self::conditions_of($row); } public static function conditions_of($row) { if (!is_array($row) || empty($row['conditions'])) { return array(); } $decoded = json_decode((string) $row['conditions'], true); return is_array($decoded) ? $decoded : array(); } private static function purge_page_cache() { if (class_exists('AIEO_DMM_Cache') && method_exists('AIEO_DMM_Cache', 'bump_version')) { AIEO_DMM_Cache::bump_version('popups'); } do_action('aieo_purge_page_cache'); } public function register_static_promo_surfaces() { if (is_admin() || !class_exists('AIEO_Display_Conditions')) { return; } $map = self::conditional_surface_map(); if (empty($map)) { return; } $anchor = function_exists('get_queried_object_id') ? (int) get_queried_object_id() : 0; foreach ($map as $slug => $entry) { AIEO_Display_Conditions::register_static_surface( 'promo_' . sanitize_key((string) $slug), isset($entry['conditions']) ? $entry['conditions'] : array(), '[data-aieo-promo-surface="' . esc_attr((string) $slug) . '"]', array('id' => isset($entry['id']) ? (int) $entry['id'] : 0, 'v' => 0), $anchor, false ); } if (AIEO_Display_Conditions::has_static_surfaces()) { add_filter('rocket_defer_inline_exclusions', array($this, 'rocket_inline_exclusions')); } } const CONDITIONAL_MAP_OPTION = 'aieo_promo_conditional_surfaces'; public static function conditional_surface_map() { $map = get_option(self::CONDITIONAL_MAP_OPTION, null); if (is_array($map)) { return $map; } return self::rebuild_conditional_surface_map(); } public static function rebuild_conditional_surface_map() { $map = array(); foreach (self::list_surfaces() as $row) { if ((int) $row['is_active'] !== 1) { continue; } $conditions = self::conditions_of($row); if (empty($conditions)) { continue; } if (class_exists('AIEO_Display_Conditions') && !AIEO_Display_Conditions::can_deny(AIEO_Display_Conditions::sanitize($conditions))) { continue; } $map[(string) $row['slug']] = array( 'id' => (int) $row['surface_id'], 'conditions' => $conditions, ); } update_option(self::CONDITIONAL_MAP_OPTION, $map, false); return $map; } public function rocket_inline_exclusions($excluded) { if (!class_exists('AIEO_Display_Conditions')) { return $excluded; } $ours = AIEO_Display_Conditions::rocket_needles(); return is_array($excluded) ? array_merge($excluded, $ours) : $ours; } public function rocket_js_exclusions($excluded) { if (!class_exists('AIEO_Display_Conditions')) { return $excluded; } $ours = AIEO_Display_Conditions::rocket_needles(); return is_array($excluded) ? array_merge($excluded, $ours) : $ours; } public function ajax_save_slot() { AIEO_DMM::verify_ajax_request(); global $wpdb; $slot_id = isset($_POST['slot_id']) ? (int) $_POST['slot_id'] : 0; $surface_id = isset($_POST['surface_id']) ? (int) sanitize_text_field( wp_unslash( $_POST['surface_id'] ) ) : 0; if ($surface_id <= 0) { wp_send_json_error(array('message' => __('Missing surface_id.', 'ai-eshop-optimizer'))); } $position = max(0, (int) (sanitize_text_field( wp_unslash( $_POST['position'] ) ) ?? 0)); $type = sanitize_key(wp_unslash($_POST['slot_type'] ?? 'slide')); if (!in_array($type, array('slide', 'banner'), true)) { $type = 'slide'; } $data = array( 'surface_id' => $surface_id, 'position' => $position, 'slot_type' => $type, 'sort_order' => max(0, (int) (sanitize_text_field( wp_unslash( $_POST['sort_order'] ) ) ?? 0)), 'desktop_image_id' => max(0, (int) (sanitize_text_field( wp_unslash( $_POST['desktop_image_id'] ) ) ?? 0)), 'mobile_image_id' => max(0, (int) (sanitize_text_field( wp_unslash( $_POST['mobile_image_id'] ) ) ?? 0)), 'youtube_id' => sanitize_text_field(wp_unslash($_POST['youtube_id'] ?? '')), 'target_url' => esc_url_raw(wp_unslash($_POST['target_url'] ?? '')), 'alt_text' => sanitize_text_field(wp_unslash($_POST['alt_text'] ?? '')), 'time_delay_ms' => max(0, (int) (sanitize_text_field( wp_unslash( $_POST['time_delay_ms'] ) ) ?? 4000)), 'campaign_id' => max(0, (int) (sanitize_text_field( wp_unslash( $_POST['campaign_id'] ) ) ?? 0)), 'is_active' => !empty($_POST['is_active']) ? 1 : 0, ); $tbl = self::tbl_slots(); if ($slot_id > 0) { $wpdb->update($tbl, $data, array('slot_id' => $slot_id)); } else { $wpdb->insert($tbl, $data); $slot_id = (int) $wpdb->insert_id; } wp_send_json_success(array('message' => __('Slot saved.', 'ai-eshop-optimizer'), 'slot_id' => $slot_id)); } public function ajax_delete_slot() { AIEO_DMM::verify_ajax_request(); global $wpdb; $slot_id = isset($_POST['slot_id']) ? (int) $_POST['slot_id'] : 0; if ($slot_id <= 0) { wp_send_json_error(array('message' => 'missing slot_id')); } $wpdb->delete(self::tbl_slots(), array('slot_id' => $slot_id)); wp_send_json_success(array('message' => __('Slot deleted.', 'ai-eshop-optimizer'))); } public function ajax_reorder() { AIEO_DMM::verify_ajax_request(); global $wpdb; $raw = isset($_POST['order']) ? wp_unslash((string) sanitize_text_field( wp_unslash( $_POST['order'] ) )) : ''; $rows = $raw !== '' ? json_decode($raw, true) : null; if (!is_array($rows)) { wp_send_json_error(array('message' => 'bad payload')); } $tbl = self::tbl_slots(); foreach ($rows as $r) { if (!is_array($r)) { continue; } $slot_id = isset($r['slot_id']) ? (int) $r['slot_id'] : 0; $sort_order = isset($r['sort_order']) ? (int) $r['sort_order'] : 0; if ($slot_id > 0) { $wpdb->update($tbl, array('sort_order' => $sort_order), array('slot_id' => $slot_id)); } } wp_send_json_success(array('message' => __('Order updated.', 'ai-eshop-optimizer'))); } public function enqueue_block_editor_assets() { $handle = 'aieo-promo-surface-editor'; if (wp_script_is($handle, 'registered')) { wp_enqueue_script($handle); } } public function register_block() { if (!function_exists('register_block_type')) { return; } $editor_handle = 'aieo-promo-surface-editor'; $editor_rel = 'assets/js/aieo-promo-surface-editor.js'; $editor_path = AIEO_DIR . $editor_rel; $editor_url = plugins_url($editor_rel, AIEO_FILE); $editor_ver = file_exists($editor_path) ? filemtime($editor_path) : (defined('AIEO_VERSION') ? AIEO_VERSION : false); if (file_exists($editor_path)) { wp_register_script( $editor_handle, $editor_url, array('wp-blocks', 'wp-block-editor', 'wp-components', 'wp-element', 'wp-i18n'), $editor_ver, true ); $options = array(); foreach (self::list_surfaces() as $s) { $options[] = array( 'value' => (string) $s['slug'], 'label' => sprintf('%s (%s)', $s['label'] !== '' ? $s['label'] : $s['slug'], $s['slug'] ), 'is_active' => (int) $s['is_active'], ); } wp_localize_script($editor_handle, 'aieoPromoSurfaceConfig', array( 'surfaces' => $options, )); } register_block_type(self::BLOCK_NAME, array( 'api_version' => 3, 'title' => __('Rooster One Promo Surface', 'ai-eshop-optimizer'), 'category' => 'theme', 'icon' => 'megaphone', 'description' => __('Hero carousel + 3 banner row, driven by Promo surfaces config under Campaigns.', 'ai-eshop-optimizer'), 'keywords' => array('hero', 'banner', 'carousel', 'promo'), 'attributes' => array( 'surfaceSlug' => array('type' => 'string', 'default' => ''), 'align' => array('type' => 'string'), ), 'supports' => array( 'align' => array('wide', 'full'), 'spacing' => array('margin' => true, 'padding' => true), 'typography' => array('fontSize' => true, 'lineHeight' => true), 'color' => array('background' => true, 'text' => true), ), 'editor_script' => $editor_handle, 'render_callback' => array($this, 'render_block'), )); } public function render_block($attrs = array(), $content = '', $block = null) { $slug = isset($attrs['surfaceSlug']) ? sanitize_key((string) $attrs['surfaceSlug']) : ''; if ($slug === '') { return is_admin() ? '
' . esc_html__('No surface selected — pick one in the block sidebar.', 'ai-eshop-optimizer') . '
' : ''; } $surface = self::get_surface_by_slug($slug); if (!$surface || (int) $surface['is_active'] !== 1) { return ''; } $slots = self::list_slots((int) $surface['surface_id']); $by_position = array(); foreach ($slots as $slot) { if ((int) $slot['is_active'] !== 1) { continue; } $p = (int) $slot['position']; if (!isset($by_position[$p])) { $by_position[$p] = array(); } $by_position[$p][] = $slot; } if (empty($by_position)) { return ''; } $layout = (string) $surface['layout_preset']; $hero = isset($by_position[0]) ? $by_position[0] : array(); $banners = array(); for ($i = 1; $i <= 3; $i++) { if (isset($by_position[$i]) && !empty($by_position[$i])) { $banners[$i] = $by_position[$i][0]; } } $surface_slug = (string) $surface['slug']; $align = isset($attrs['align']) ? sanitize_html_class((string) $attrs['align']) : ''; $align_cls = ($align === 'full') ? ' alignfull' : (($align === 'wide') ? ' alignwide' : ''); $base_cls = 'aieo-promo-surface aieo-promo-surface--' . $layout; $cond_attr = ''; if (class_exists('AIEO_Display_Conditions')) { $cond_attr = wp_json_encode(AIEO_Display_Conditions::client_config( self::conditions_of($surface), array('id' => (int) $surface['surface_id'], 'surface' => 'promo_' . sanitize_key($surface_slug), 'v' => 0) )); } if ($block instanceof WP_Block) { $wrapper_attr = get_block_wrapper_attributes(array( 'class' => $base_cls, 'data-aieo-promo-surface' => $surface_slug, 'data-aieo-conditions' => $cond_attr, )); } else { $wrapper_attr = 'class="' . esc_attr($base_cls . $align_cls) . '" data-aieo-promo-surface="' . esc_attr($surface_slug) . '"' . ' data-aieo-conditions="' . esc_attr($cond_attr) . '"'; } if (class_exists('AIEO_Popup_Display') && AIEO_Popup_Display::ensure_conditions()) { AIEO_Display_Conditions::enqueue_runtime(); } self::enqueue_tracker(); static $css_emitted = false; ob_start(); if (!$css_emitted) { $css_emitted = true; $this->emit_layout_css(); } ?>
>
render_hero($hero, $surface_slug); ?>
$slot) : ?> render_banner_slot($slot, $surface_slug, $pos); ?>
'', 'surface' => ''), $atts, 'aieo_promo_surface'); $slug = $atts['slug'] !== '' ? $atts['slug'] : $atts['surface']; if ($slug === '') { return ''; } return $this->render_block(array('surfaceSlug' => (string) $slug)); } private function render_hero(array $slots, $surface_slug) { $count = count($slots); $uid = 'aieo-promo-hero-' . sanitize_key($surface_slug); $first = $slots[0]; $preload_url = $first['desktop_image_id'] ? (string) wp_get_attachment_image_url((int) $first['desktop_image_id'], 'large') : ''; if ($preload_url !== '') { printf('' . "\n", esc_url($preload_url)); } ?>
$slot) : ?> render_slide_or_banner($slot, $surface_slug, 0, $i); ?>
1) : ?>
render_slide_or_banner($slot, $surface_slug, (int) $position, 0); ?>
0) { $target_url = $this->stamp_utm_campaign($target_url, $campaign_id); } $delay_ms = max(0, (int) $slot['time_delay_ms']); $youtube = trim((string) $slot['youtube_id']); $link_label = $alt !== '' ? $alt : __( 'Promotion', 'ai-eshop-optimizer' ); $tag_open = $target_url !== '' ? sprintf('', esc_url($target_url), $slot_id, esc_attr($surface_slug), $position, esc_attr($link_label)) : sprintf('
', $slot_id, esc_attr($surface_slug), $position); $tag_close = $target_url !== '' ? '' : '
'; echo '
'; echo $tag_open; if ($youtube !== '') { printf( '', esc_attr($youtube), $eager ? 'eager' : 'lazy' ); } else { self::render_picture($slot, $eager, $alt); } echo $tag_close; echo '
'; } public static function render_picture(array $slot, $eager, $alt) { $desktop_id = (int) $slot['desktop_image_id']; $mobile_id = (int) $slot['mobile_image_id']; if ($desktop_id <= 0 && $mobile_id <= 0) { return; } $desktop = $desktop_id ? wp_get_attachment_image_src($desktop_id, 'full') : null; $mobile = $mobile_id ? wp_get_attachment_image_src($mobile_id, 'full') : null; if (!$desktop && !$mobile) { return; } $main = $desktop ?: $mobile; $loading = $eager ? 'eager' : 'lazy'; $fp_attr = $eager ? ' fetchpriority="high"' : ''; echo ''; if ($mobile) { printf('', esc_url((string) $mobile[0]), (int) $mobile[1], (int) $mobile[2]); } if ($desktop) { printf('', esc_url((string) $desktop[0]), (int) $desktop[1], (int) $desktop[2]); } printf( '%s', esc_url((string) $main[0]), esc_attr($alt), (int) $main[1], (int) $main[2], esc_attr($loading), $fp_attr ); echo ''; } private function emit_layout_css() { ?> $slug, 'utm_source' => 'promo_surface'), $url); } public static function enqueue_tracker() { static $emitted = false; if ($emitted || is_admin()) { return; } $emitted = true; $base = defined('AIEO_FILE') ? AIEO_FILE : __FILE__; $dir = dirname($base) . '/'; $rel = 'assets/js/aieo-promo-telemetry.js'; if (!file_exists($dir . $rel)) { return; } wp_enqueue_script('aieo-promo-telemetry', plugins_url($rel, $base), array(), (string) filemtime($dir . $rel), array('in_footer' => true, 'strategy' => 'defer')); add_filter('rooster/third_party/handles', static function ($handles) { $handles = is_array($handles) ? $handles : array(); $handles['aieo-promo-telemetry'] = array('trigger' => 'human'); return $handles; }); } public function render_subtab() { $action = isset($_GET['promo_action']) ? sanitize_key($_GET['promo_action']) : ''; $surface_id = isset($_GET['surface_id']) ? (int) $_GET['surface_id'] : 0; if ($action === 'edit' && $surface_id > 0) { $this->render_edit_view($surface_id); } elseif ($action === 'new') { $this->render_edit_view(0); } else { $this->render_list_view(); } } private function render_reports_card() { global $wpdb; $reco_tbl = $wpdb->prefix . 'aieo_reco_events'; $surfaces_tbl = self::tbl_surfaces(); $slots_tbl = self::tbl_slots(); $attr_tbl = $wpdb->prefix . 'aieo_dmm_offer_attributions'; $window_days = 30; $since = gmdate('Y-m-d H:i:s', time() - $window_days * DAY_IN_SECONDS); $rows = $wpdb->get_results($wpdb->prepare( "SELECT SUBSTRING(surface, 7) AS slug, SUM(CASE WHEN event_type = 'promo_impression' THEN 1 ELSE 0 END) AS impressions, SUM(CASE WHEN event_type = 'promo_click' THEN 1 ELSE 0 END) AS clicks FROM {$reco_tbl} WHERE surface LIKE 'promo\\_%' ESCAPE '\\\\' AND event_type IN ('promo_impression','promo_click') AND ts >= %s GROUP BY surface", $since ), ARRAY_A); $by_slug = array(); if (is_array($rows)) { foreach ($rows as $r) { $by_slug[(string) $r['slug']] = array( 'impressions' => (int) $r['impressions'], 'clicks' => (int) $r['clicks'], ); } } $surfaces = $wpdb->get_results( "SELECT s.surface_id, s.slug, s.label, s.is_active, COUNT(sl.slot_id) AS slot_count, SUM(CASE WHEN sl.is_active = 1 THEN 1 ELSE 0 END) AS active_slots FROM {$surfaces_tbl} s LEFT JOIN {$slots_tbl} sl ON sl.surface_id = s.surface_id GROUP BY s.surface_id, s.slug, s.label, s.is_active ORDER BY s.label, s.slug", ARRAY_A ) ?: array(); ?> 0, 'active' => 0, 'impressions' => 0, 'clicks' => 0); foreach ((array) $surfaces as $s) { $slug = (string) $s['slug']; $stats = isset($by_slug[$slug]) ? $by_slug[$slug] : array('impressions' => 0, 'clicks' => 0); $aieo_ps_totals['slots'] += (int) $s['slot_count']; $aieo_ps_totals['active'] += (int) $s['active_slots']; $aieo_ps_totals['impressions'] += (int) $stats['impressions']; $aieo_ps_totals['clicks'] += (int) $stats['clicks']; $aieo_ps_rows[] = array( 'slug' => $slug, 'label' => '' !== (string) $s['label'] ? (string) $s['label'] : $slug, 'active' => (int) $s['is_active'], 'slots' => (int) $s['active_slots'], 'of_slots' => (int) $s['slot_count'] > (int) $s['active_slots'] ? '/' . number_format_i18n((int) $s['slot_count']) : '', 'impressions' => (int) $stats['impressions'], 'clicks' => (int) $stats['clicks'], 'ctr' => (int) $stats['impressions'] > 0 ? round(((int) $stats['clicks'] / (int) $stats['impressions']) * 100, 1) : null, ); } AIEO_Table::card(array( 'anchor' => 'promo-surfaces-promo-surface-performance-last-d-days', 'icon' => 'chart-column', 'heading' => 'h2', 'title' => __('Promo surface performance', 'ai-eshop-optimizer'), 'description' => __('Impressions fire on first viewport intersection of any slot in a surface; clicks fire before navigation. Both flow into the shared wp_aieo_reco_events table — same plumbing as exit-intent and product-card analytics.', 'ai-eshop-optimizer'), 'page_id' => $this->get_id(), 'row_key' => 'slug', 'rows' => $aieo_ps_rows, 'empty' => __('No surfaces yet. Add one above to start collecting data.', 'ai-eshop-optimizer'), 'totals_at' => 'foot', 'per_page' => 0, 'totals_label' => __('Total', 'ai-eshop-optimizer'), 'totals' => array( 'slots' => $aieo_ps_totals['active'], 'of_slots' => '', 'impressions' => $aieo_ps_totals['impressions'], 'clicks' => $aieo_ps_totals['clicks'], 'ctr' => $aieo_ps_totals['impressions'] > 0 ? round(($aieo_ps_totals['clicks'] / $aieo_ps_totals['impressions']) * 100, 1) : null, ), 'columns' => array( array('key' => 'label', 'label' => __('Surface', 'ai-eshop-optimizer'), 'sort_key' => 'slug', 'subline' => 'slug'), array('key' => 'active', 'label' => __('Status', 'ai-eshop-optimizer'), 'type' => 'boolean'), array('key' => 'slots', 'label' => __('Slots', 'ai-eshop-optimizer'), 'type' => 'integer', 'suffix' => 'of_slots'), array('key' => 'impressions', 'label' => __('Impressions', 'ai-eshop-optimizer'), 'type' => 'integer'), array('key' => 'clicks', 'label' => __('Clicks', 'ai-eshop-optimizer'), 'type' => 'integer'), array('key' => 'ctr', 'label' => __('CTR', 'ai-eshop-optimizer'), 'type' => 'percent', 'format' => array('precision' => 1), 'total' => array('ratio' => array('clicks', 'impressions'), 'percent' => true)), ), 'footnote' => __('CTR = clicks ÷ impressions. Slots column shows active / total when some slots are disabled. To break clicks down by slot or campaign, query wp_aieo_reco_events directly (target_pid carries the slot_id) or wp_aieo_dmm_offer_attributions filtered by entry_channel = the surface\'s utm slug.', 'ai-eshop-optimizer'), )); ?> AIEO_DMM::MENU_SLUG, 'dmm_tab' => $this->get_id(), 'promo_action'=> 'new', ), admin_url('admin.php')); list($aieo_ps_open, $aieo_ps_close) = AIEO_DMM_UI::card(array( 'anchor' => 'promo-surfaces-list', 'title' => __('All surfaces', 'ai-eshop-optimizer'), 'description' => __('Operator-curated promo placements (homepage hero + 3 banner row, category top, etc.). On a block theme, drop the “Promo Surface” block onto a template and pick a surface. On a LEGACY (classic) theme, copy the shortcode shown next to each surface below — e.g. [aieo_promo_surface slug="mai-hero"] — into a template (via do_shortcode), a text widget, or a page-builder module. Both paths render identically.', 'ai-eshop-optimizer'), 'tools' => '' . esc_html__('Add new surface', 'ai-eshop-optimizer') . '', 'flush' => true, )); echo $aieo_ps_open; ?> (int) $s['surface_id'], 'slug' => (string) $s['slug'], 'shortcode' => '[aieo_promo_surface slug="' . (string) $s['slug'] . '"]', 'label' => (string) $s['label'], 'layout' => (string) $s['layout_preset'], 'active' => (int) $s['is_active'], 'updated' => (string) $s['updated_at'], 'edit_url' => add_query_arg(array( 'page' => AIEO_DMM::MENU_SLUG, 'dmm_tab' => $this->get_id(), 'promo_action' => 'edit', 'surface_id' => (int) $s['surface_id'], ), admin_url('admin.php')), ); } AIEO_Table::render(array( 'page_id' => $this->get_id(), 'row_key' => 'surface_id', 'rows' => $aieo_ps_list, 'empty' => __('No surfaces yet — click “Add new surface” to create the first one.', 'ai-eshop-optimizer'), 'caps' => array('search' => true), 'filters' => array(array('key' => 'active', 'label' => __('Active', 'ai-eshop-optimizer'), 'kind' => 'boolean', 'control' => 'segmented')), 'columns' => array( array('key' => 'slug', 'label' => __('Slug', 'ai-eshop-optimizer'), 'type' => 'code', 'subline' => 'shortcode'), array('key' => 'label', 'label' => __('Label', 'ai-eshop-optimizer')), array('key' => 'layout', 'label' => __('Layout', 'ai-eshop-optimizer'), 'type' => 'code'), array('key' => 'active', 'label' => __('Active', 'ai-eshop-optimizer'), 'type' => 'boolean'), array('key' => 'updated', 'label' => __('Updated', 'ai-eshop-optimizer'), 'nowrap' => true), ), 'actions' => array( array('id' => 'edit', 'label' => __('Edit', 'ai-eshop-optimizer'), 'href' => static function ($r) { return $r['edit_url']; }), ), )); ?> render_reports_card(); ?> 0 ? self::get_surface($surface_id) : null; if ($surface_id > 0 && !$surface) { echo '

' . esc_html__('Surface not found.', 'ai-eshop-optimizer') . '

'; $this->render_list_view(); return; } $slots = $surface_id > 0 ? self::list_slots($surface_id) : array(); $nonce = wp_create_nonce(AIEO_DMM::NONCE_ACTION); $ajax = admin_url('admin-ajax.php'); $list_url = add_query_arg(array( 'page' => AIEO_DMM::MENU_SLUG, 'dmm_tab' => $this->get_id(), ), admin_url('admin.php')); $defaults = array( 'surface_id' => 0, 'slug' => '', 'label' => '', 'layout_preset' => 'hero_plus_3banners', 'is_active' => 1, ); $s = is_array($surface) ? array_merge($defaults, $surface) : $defaults; global $wpdb; $campaigns_tbl = $wpdb->prefix . 'aieo_dmm_campaigns'; $campaigns = $wpdb->get_results("SELECT id, slug, label FROM {$campaigns_tbl} ORDER BY label, slug", ARRAY_A) ?: array(); $aieo_ps_form = 'aieo-card-promo-surfaces-edit-surface-form'; list($aieo_ps_open, $aieo_ps_close) = AIEO_DMM_UI::card(array( 'anchor' => 'promo-surfaces-edit-surface', 'icon' => 'pencil-line', 'title' => $surface_id ? __('Edit surface', 'ai-eshop-optimizer') : __('New surface', 'ai-eshop-optimizer'), 'footer' => \Rooster\Admin\States::save_button($aieo_ps_form, array('label' => __('Save surface', 'ai-eshop-optimizer'))), )); echo $aieo_ps_open; echo '

← ' . esc_html__('All surfaces', 'ai-eshop-optimizer') . '

'; $aieo_ps_row = static function ($id, $label, $control, $help = '') { $field = array('id' => 'aieo-ps-' . $id, 'label' => $label, 'control' => $control); if ('' !== $help) { $field['help'] = $help; } return \Rooster\Admin\Ui::field_row($field); }; $aieo_ps_fields = $aieo_ps_row('slug', __('Slug', 'ai-eshop-optimizer'), \Rooster\Admin\Ui::input('slug', (string) $s['slug'], array('id' => 'aieo-ps-slug', 'medium' => true, 'code' => true, 'required' => true)), __('URL-safe identifier. Used by the block to look this surface up — keep stable.', 'ai-eshop-optimizer')); $aieo_ps_fields .= $aieo_ps_row('label', __('Label', 'ai-eshop-optimizer'), \Rooster\Admin\Ui::input('label', (string) $s['label'], array('id' => 'aieo-ps-label', 'medium' => true))); $aieo_ps_fields .= $aieo_ps_row('layout', __('Layout', 'ai-eshop-optimizer'), \Rooster\Admin\Ui::select('layout_preset', array( 'hero_plus_3banners' => __('Hero + 3 banners', 'ai-eshop-optimizer'), 'hero_only' => __('Hero only', 'ai-eshop-optimizer'), 'banners_only' => __('3 banners only', 'ai-eshop-optimizer'), ), (string) $s['layout_preset'], array('id' => 'aieo-ps-layout'))); $aieo_ps_fields .= $aieo_ps_row('is-active', __('Active', 'ai-eshop-optimizer'), \Rooster\Admin\Ui::toggle('is_active', 1 === (int) $s['is_active'], array('id' => 'aieo-ps-is-active', 'hidden' => false, 'label' => __('Render on front-end.', 'ai-eshop-optimizer')))); $aieo_ps_html = '
' . $aieo_ps_fields . '
'; if (class_exists('AIEO_Display_Conditions_UI')) { ob_start(); ?>

'cond_', 'name' => true) ); ?>
'')); ?> 0) : ?> 'promo-surfaces-slots', 'icon' => 'layout-grid', 'title' => __('Slots', 'ai-eshop-optimizer'), 'description' => __('Position 0 = hero carousel (each row is a slide). Positions 1, 2, 3 = the three banner row slots (one slot each, the first active wins).', 'ai-eshop-optimizer'), 'flush' => true, 'footer' => \Rooster\Admin\States::outcome('', '', array('id' => 'aieo-promo-slots-status')), )); echo $aieo_ps_open; ?> 0, 'surface_id' => $surface_id, 'position' => 0, 'slot_type' => 'slide', 'sort_order' => 0, 'desktop_image_id' => 0, 'mobile_image_id' => 0, 'youtube_id' => '', 'target_url' => '', 'alt_text' => '', 'time_delay_ms' => 4000, 'campaign_id' => 0, 'is_active' => 1); $aieo_ps_trows = array(); foreach ($rows as $r) { $is_new = empty($r['slot_id']); ob_start(); $this->render_media_picker_cell('desktop', (int) $r['desktop_image_id']); $aieo_ps_desktop = (string) ob_get_clean(); ob_start(); $this->render_media_picker_cell('mobile', (int) $r['mobile_image_id']); $aieo_ps_mobile = (string) ob_get_clean(); $aieo_ps_camp = ''; $aieo_ps_trow = array( '_id' => $is_new ? 'new' : (string) (int) $r['slot_id'], '_name' => $is_new ? __('New slot', 'ai-eshop-optimizer') : sprintf( __('Slot %d', 'ai-eshop-optimizer'), (int) $r['slot_id'] ), '_attrs' => array('data-slot-id' => (string) (int) $r['slot_id']), 'pos' => array('html' => ''), 'type' => array('html' => ''), 'desktop' => array('html' => $aieo_ps_desktop), 'mobile' => array('html' => $aieo_ps_mobile), 'youtube' => array('html' => ''), 'url' => array('html' => ''), 'alt' => array('html' => ''), 'delay' => array('html' => ''), 'campaign' => array('html' => $aieo_ps_camp), 'active' => array('html' => ''), 'act' => array_merge( array(array('label' => $is_new ? __('Add', 'ai-eshop-optimizer') : __('Save', 'ai-eshop-optimizer'), 'attrs' => array('data-slot-act' => 'save'))), $is_new ? array() : array(array('label' => __('Delete', 'ai-eshop-optimizer'), 'icon' => 'trash-2', 'destructive' => true, 'attrs' => array('data-slot-act' => 'delete'))) ), ); if ($is_new) { $aieo_ps_trow['_locked'] = __('New slots are added here, after the others.', 'ai-eshop-optimizer'); } $aieo_ps_trows[] = $aieo_ps_trow; } echo \Rooster\Admin\Ui::table( array( array('key' => 'pos', 'label' => __('Pos', 'ai-eshop-optimizer'), 'type' => 'html', 'width' => 'min'), array('key' => 'type', 'label' => __('Type', 'ai-eshop-optimizer'), 'type' => 'html', 'width' => 'min'), array('key' => 'desktop', 'label' => __('Desktop img id', 'ai-eshop-optimizer'), 'type' => 'html', 'width' => 'min'), array('key' => 'mobile', 'label' => __('Mobile img id', 'ai-eshop-optimizer'), 'type' => 'html', 'width' => 'min'), array('key' => 'youtube', 'label' => __('YouTube id', 'ai-eshop-optimizer'), 'type' => 'html', 'width' => 'sm', 'priority' => 'low'), array('key' => 'url', 'label' => __('Target URL', 'ai-eshop-optimizer'), 'type' => 'html', 'width' => 'md'), array('key' => 'alt', 'label' => __('Alt', 'ai-eshop-optimizer'), 'type' => 'html', 'width' => 'sm', 'priority' => 'low'), array('key' => 'delay', 'label' => __('Delay (ms)', 'ai-eshop-optimizer'), 'type' => 'html', 'width' => 'xs', 'priority' => 'low'), array('key' => 'campaign', 'label' => __('Campaign', 'ai-eshop-optimizer'), 'type' => 'html', 'width' => 'sm', 'priority' => 'low'), array('key' => 'active', 'label' => __('Active', 'ai-eshop-optimizer'), 'type' => 'html', 'width' => 'min', 'align' => 'center'), array('key' => 'act', 'label' => __('Actions', 'ai-eshop-optimizer'), 'type' => 'actions'), ), $aieo_ps_trows, array( 'id' => 'aieo-promo-slots-table', 'caption' => __('Slots', 'ai-eshop-optimizer'), 'local' => array('form' => true, 'prefix' => 'ps_'), 'move' => array('label' => __('Slots', 'ai-eshop-optimizer')), 'chooser' => true, ) ); ?> render_admin_js($nonce, $ajax, $surface_id); ?> 0 ? wp_get_attachment_image_url($id, 'thumbnail') : ''; $cls = 'js-slot-' . $kind . '-id'; ?>
>