'',
'name' => '',
'type' => 'html',
'enabled' => false,
'conditions' => class_exists('AIEO_Display_Conditions')
? AIEO_Display_Conditions::defaults()
: array(),
'placement' => class_exists('AIEO_Display_Conditions')
? AIEO_Display_Conditions::sanitize_placement(array('pages' => array('product')))
: array(),
'title' => '',
'show_title' => false,
'tolstoy_widget_id' => '',
'tolstoy_account_uuid' => '',
'raw_html' => '',
), $row);
}
return $out;
}
public static function placement_ok($slot) {
if (!class_exists('AIEO_Display_Conditions')) {
return function_exists('is_singular') && is_singular('product');
}
$rules = (is_array($slot) && isset($slot['placement']) && is_array($slot['placement']))
? $slot['placement']
: array('pages' => array('product'));
return AIEO_Display_Conditions::placement_matches($rules, true);
}
public static function any_placement_ok($slots = null) {
$slots = is_array($slots) ? $slots : self::get_all_slots();
foreach ($slots as $slot) {
if (empty($slot['enabled'])) { continue; }
if (self::placement_ok($slot)) { return true; }
}
return false;
}
public static function get_slot($slot_id) {
$slot_id = (string) $slot_id;
if ($slot_id === '') { return null; }
foreach (self::get_all_slots() as $row) {
if ($row['id'] === $slot_id) { return $row; }
}
return null;
}
public static function update_all_slots(array $slots) {
$reserved = array('upsells', 'crosssells', 'related', 'recently_viewed', 'same_needs');
$clean = array();
$seen = array();
$unfiltered = current_user_can('unfiltered_html');
$stored = array();
foreach (self::get_all_slots() as $was) { $stored[(string) $was['id']] = (string) $was['raw_html']; }
foreach ($slots as $row) {
if (!is_array($row)) { continue; }
$id = isset($row['id']) ? sanitize_key($row['id']) : '';
$type = isset($row['type']) && in_array($row['type'], self::TYPES, true) ? $row['type'] : 'html';
if ($id === '' || in_array($id, $reserved, true) || isset($seen[$id])) { continue; }
$seen[$id] = true;
$clean[] = array(
'id' => $id,
'name' => isset($row['name']) ? sanitize_text_field((string) $row['name']) : '',
'type' => $type,
'enabled' => !empty($row['enabled']),
'title' => isset($row['title']) ? sanitize_text_field((string) $row['title']) : '',
'show_title' => !empty($row['show_title']),
'tolstoy_widget_id' => isset($row['tolstoy_widget_id']) ? sanitize_text_field((string) $row['tolstoy_widget_id']) : '',
'tolstoy_account_uuid' => isset($row['tolstoy_account_uuid']) ? sanitize_text_field((string) $row['tolstoy_account_uuid']) : '',
'raw_html' => self::raw_html_to_store($row, isset($stored[$id]) ? $stored[$id] : null, $unfiltered),
'conditions' => class_exists('AIEO_Display_Conditions_UI')
? AIEO_Display_Conditions_UI::collect(
$row,
isset($row['conditions']) && is_array($row['conditions']) ? $row['conditions'] : array()
)
: (isset($row['conditions']) && is_array($row['conditions']) ? $row['conditions'] : array()),
'placement' => class_exists('AIEO_Display_Conditions_UI')
? AIEO_Display_Conditions_UI::collect_placement(
$row,
isset($row['placement']) && is_array($row['placement'])
? $row['placement']
: array('pages' => array('product')),
self::PLACEMENT_TOKENS
)
: (isset($row['placement']) && is_array($row['placement']) ? $row['placement'] : array()),
);
}
update_option(self::OPTION_KEY, $clean, false);
self::purge_page_cache();
}
private static function raw_html_to_store($row, $stored, $unfiltered) {
$raw = isset($row['raw_html']) && is_scalar($row['raw_html']) ? (string) $row['raw_html'] : '';
if (null !== $stored && ($raw === $stored || wp_unslash($raw) === $stored)) { return $stored; }
$html = (string) wp_unslash($raw);
return $unfiltered ? $html : wp_kses_post($html);
}
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 static function add_slot($type) {
if (!in_array($type, self::TYPES, true)) { $type = 'html'; }
$slots = self::get_all_slots();
$id = sanitize_key('slot_' . wp_generate_password(8, false, false));
$slots[] = array(
'id' => $id,
'name' => $type === 'tolstoy' ? __('Tolstoy widget', 'ai-eshop-optimizer') : __('Custom block', 'ai-eshop-optimizer'),
'type' => $type,
'enabled' => false,
);
self::update_all_slots($slots);
return $id;
}
public static function delete_slot($slot_id) {
$slots = self::get_all_slots();
$kept = array();
foreach ($slots as $row) {
if ($row['id'] === $slot_id) { continue; }
$kept[] = $row;
}
self::update_all_slots($kept);
}
public static function is_client_armed($slot) {
if (!is_array($slot) || empty($slot['enabled'])) { return false; }
if (($slot['type'] ?? '') !== 'tolstoy') { return false; }
return '' !== self::clean_widget_id($slot);
}
private static function clean_widget_id($slot) {
$widget = isset($slot['tolstoy_widget_id']) ? trim((string) $slot['tolstoy_widget_id']) : '';
return (string) preg_replace('/[^A-Za-z0-9]/', '', $widget);
}
public static function slot_client_config($slot) {
$slot_id = (string) $slot['id'];
$conditions = array();
if (class_exists('AIEO_Display_Conditions')) {
$stored = isset($slot['conditions']) && is_array($slot['conditions']) ? $slot['conditions'] : array();
$conditions = AIEO_Display_Conditions::client_config($stored, array(
'id' => 0,
'surface' => $slot_id,
'v' => (int) crc32(wp_json_encode(array(
$slot['tolstoy_widget_id'] ?? '',
$slot['tolstoy_account_uuid'] ?? '',
$slot['title'] ?? '',
!empty($slot['show_title']),
))),
));
}
$title = '';
if (!empty($slot['show_title']) && trim((string) ($slot['title'] ?? '')) !== '') {
$title = (string) $slot['title'];
}
return array(
'conditions' => $conditions,
'payload' => array(
'type' => 'tolstoy',
'widget' => self::clean_widget_id($slot),
'account' => preg_replace('/[^A-Za-z0-9\-]/', '', (string) ($slot['tolstoy_account_uuid'] ?? '')),
'productId' => function_exists('get_the_ID') ? (int) get_the_ID() : 0,
'title' => $title,
'titleClass' => 'aieo-recommendations-title',
),
);
}
public static function container_attrs($slot) {
$cfg = self::slot_client_config($slot);
return array(
'data-aieo-surface' => (string) $slot['id'],
'data-aieo-conditions' => (string) wp_json_encode($cfg['conditions']),
'data-aieo-payload' => (string) wp_json_encode($cfg['payload']),
);
}
public static function slot_section_html($slot, $wrapper_attrs = null) {
if (!is_array($slot) || empty($slot['enabled'])) { return ''; }
if (!self::placement_ok($slot)) { return ''; }
$armed = self::is_client_armed($slot);
$html = $armed ? '' : self::render_slot((string) $slot['id']);
if (!$armed && $html === '') { return ''; }
if (null === $wrapper_attrs) {
$slot_id = (string) $slot['id'];
$attrs = array(
'class' => 'aieo-custom-slot aieo-custom-slot--' . sanitize_html_class($slot_id) . ' aieo-recommendations',
'data-aieo-reco' => $slot_id,
'data-section' => $slot_id,
);
if ($armed) { $attrs = array_merge($attrs, self::container_attrs($slot)); }
$pairs = array();
foreach ($attrs as $k => $v) { $pairs[] = esc_attr($k) . '="' . esc_attr($v) . '"'; }
$wrapper_attrs = implode(' ', $pairs);
}
return '
'; echo ' '; echo ''; echo '
'; echo $close; echo ''; } private static function render_slot_row($slot) { $id = (string) $slot['id']; $type = (string) $slot['type']; $uid = 'aieo-cs-' . sanitize_key($id) . '-'; $aieo_cs_dialog = 'aieo-cs-delete-' . sanitize_key($id); $aieo_cs_label = trim((string) ($slot['name'] ?? '')) !== '' ? (string) $slot['name'] : $id; $aieo_cs_open = \Rooster\Admin\Ui::card_open( $type === 'tolstoy' ? __('Tolstoy slot', 'ai-eshop-optimizer') : __('HTML / code slot', 'ai-eshop-optimizer'), array( 'id' => $uid . 'slot', 'level' => 3, 'class' => 'is-sub aieo-cs-slot', 'icon' => 'layout-template', 'meta' => \Rooster\Admin\Ui::badge($id, 'mono', array('sm' => true)), 'tools' => \Rooster\Admin\Ui::button(__('Delete', 'ai-eshop-optimizer'), array('variant' => 'destructive', 'size' => 'sm', 'attrs' => array('data-aieo-cs-delete' => $id) + \Rooster\Admin\States::confirm_attrs($aieo_cs_dialog))), 'attrs' => array('data-aieo-cs-slot' => $id), ) ); echo $aieo_cs_open; $aieo_cs_ask = \Rooster\Admin\States::confirm( $aieo_cs_dialog, 'delete', sprintf(__('Delete the slot "%s"?', 'ai-eshop-optimizer'), $aieo_cs_label), '' . esc_html__('The slot and everything typed into it are removed at once. This cannot be undone.', 'ai-eshop-optimizer') . '
' ); echo $aieo_cs_ask; echo ''; $row = static function ($id, $label, $control, $help = '') { return \Rooster\Admin\Ui::field_row(array('id' => $id, 'label' => $label, 'control' => $control, 'help' => $help)); }; $check = static function ($field, $id, $on, $text) { return ''; }; $text = static function ($field, $id, $value, array $opts = array()) { return \Rooster\Admin\Ui::input('', (string) $value, $opts + array('id' => $id, 'medium' => true, 'attrs' => array('data-field' => $field))); }; $fields = $row($uid . 'name', __('Name', 'ai-eshop-optimizer'), $text('name', $uid . 'name', $slot['name'], array('placeholder' => __('Internal label, shown in the Recommendations Ordering list', 'ai-eshop-optimizer')))) . $row($uid . 'enabled', __('Enabled', 'ai-eshop-optimizer'), $check('enabled', $uid . 'enabled', !empty($slot['enabled']), __('Render this slot. Where it may appear is set below.', 'ai-eshop-optimizer'))) . $row($uid . 'title', __('Title', 'ai-eshop-optimizer'), $text('title', $uid . 'title', $slot['title'], array('placeholder' => __('e.g. "Moments you must see"', 'ai-eshop-optimizer'))), __('Operator-set heading rendered above the slot content. Styled identically to the built-in "RECENTLY VIEWED PRODUCTS" / "PRODUCTS FOR THE SAME NEEDS" titles (heading + underline) via the shared aieo-recommendations-title class.', 'ai-eshop-optimizer')) . $row($uid . 'show-title', __('Show title', 'ai-eshop-optimizer'), $check('show_title', $uid . 'show-title', !empty($slot['show_title']), __('Display the title above this slot', 'ai-eshop-optimizer'))); if ($type === 'tolstoy') { $fields .= $row($uid . 'widget', __('Widget ID', 'ai-eshop-optimizer'), $text('tolstoy_widget_id', $uid . 'widget', $slot['tolstoy_widget_id'], array('code' => true, 'placeholder' => '01HZ7XMP3VBQR8TKWY5JCDFN24')), __('The base32 ID inside data-tolstoy-widget-id="…" on the embed snippet gotolstoy.com gives you.', 'ai-eshop-optimizer')) . $row($uid . 'uuid', __('Account UUID', 'ai-eshop-optimizer'), $text('tolstoy_account_uuid', $uid . 'uuid', $slot['tolstoy_account_uuid'], array('code' => true, 'medium' => false, 'placeholder' => 'a7c2f1e4-9b53-4d6e-bc01-5f8e7a3c2b94')), __('The UUID at the start of the script URL — https://ai-widgets.gotolstoy.com/