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