(string) $key, 'type' => (string) $type, 'class' => class_exists('AIEO_Display_Conditions') ? AIEO_Display_Conditions::surface_class($key) : 'inline', 'label' => (string) $label, 'enabled' => (bool) $enabled, 'conditions' => class_exists('AIEO_Display_Conditions') ? AIEO_Display_Conditions::sanitize(is_array($conditions) ? $conditions : array()) : (array) $conditions, 'placement' => class_exists('AIEO_Display_Conditions') ? AIEO_Display_Conditions::sanitize_placement($placement) : (array) $placement, 'ref' => $ref, ); } private static function popup_surfaces() { if (!class_exists('AIEO_Popup_CPT')) { return array(); } $out = array(); $posts = get_posts(array( 'post_type' => AIEO_Popup_CPT::POST_TYPE, 'post_status' => array('publish', 'draft'), 'numberposts' => 200, 'suppress_filters' => false, )); foreach ($posts as $p) { $cfg = AIEO_Popup_CPT::client_config($p->ID); unset($cfg['id'], $cfg['surface'], $cfg['class'], $cfg['_ab']); $out[] = self::row( AIEO_Popup_CPT::surface_tag($p->ID), 'popup', $p->post_title, 'publish' === $p->post_status, $cfg, AIEO_Popup_CPT::placement_rules($p->ID), (int) $p->ID ); } return $out; } private static function slot_surfaces() { if (!class_exists('AIEO_DMM_Custom_Slots')) { return array(); } $out = array(); foreach (AIEO_DMM_Custom_Slots::get_all_slots() as $slot) { $out[] = self::row( (string) $slot['id'], 'slot', '' !== (string) $slot['name'] ? (string) $slot['name'] : (string) $slot['id'], !empty($slot['enabled']), isset($slot['conditions']) ? $slot['conditions'] : array(), isset($slot['placement']) ? $slot['placement'] : array(), (string) $slot['id'] ); } return $out; } private static function rail_surfaces() { if (!class_exists('AIEO_DMM_Product_Display')) { return array(); } $labels = array(); foreach (AIEO_DMM_Product_Display::reco_catalog() as $c) { $labels[$c['id']] = $c['label']; } $out = array(); foreach (AIEO_DMM_Product_Display::get_reco_order() as $r) { $id = (string) $r['id']; if (strpos($id, 'slot_') === 0) { continue; } $out[] = self::row( $id, 'rail', isset($labels[$id]) ? $labels[$id] : $id, !empty($r['visible']), isset($r['conditions']) ? $r['conditions'] : array(), array('pages' => array('product')), $id ); } return $out; } private static function promo_surfaces() { if (!class_exists('AIEO_DMM_Promo_Surfaces')) { return array(); } $out = array(); foreach (AIEO_DMM_Promo_Surfaces::list_surfaces() as $row) { $out[] = self::row( 'promo_' . sanitize_key((string) $row['slug']), 'promo', '' !== (string) $row['label'] ? (string) $row['label'] : (string) $row['slug'], 1 === (int) $row['is_active'], AIEO_DMM_Promo_Surfaces::conditions_of($row), array('pages' => array('all')), (int) $row['surface_id'] ); } return $out; } public static function origins($limit = 200) { if (!class_exists('AIEO_Display_Conditions') || !AIEO_Display_Conditions::ensure_origin()) { return array(); } global $wpdb; $tbl = AIEO_Display_Origin::table(); $exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $tbl)); if (!$exists) { return array(); } $rows = $wpdb->get_results($wpdb->prepare("SELECT * FROM {$tbl} ORDER BY last_seen DESC LIMIT %d", max(1, (int) $limit)), ARRAY_A); if (!is_array($rows)) { return array(); } $out = array(); foreach ($rows as $r) { $tuple = array( 'src' => isset($r['src']) ? (string) $r['src'] : '', 'campaign' => isset($r['canon_campaign']) ? (string) $r['canon_campaign'] : '', 'content' => isset($r['canon_content']) ? (string) $r['canon_content'] : '', 'term' => isset($r['canon_term']) ? (string) $r['canon_term'] : '', 'medium' => isset($r['canon_medium']) ? (string) $r['canon_medium'] : '', 'source' => isset($r['canon_source']) ? (string) $r['canon_source'] : '', ); $out[] = array( 'id' => isset($r['id']) ? (int) $r['id'] : 0, 'ref' => AIEO_Display_Origin::ref($tuple), 'src' => $tuple['src'], 'campaign' => $tuple['campaign'], 'medium' => $tuple['medium'], 'source' => $tuple['source'], 'content' => $tuple['content'], 'term' => $tuple['term'], 'first_seen' => isset($r['first_seen']) ? (string) $r['first_seen'] : '', 'last_seen' => isset($r['last_seen']) ? (string) $r['last_seen'] : '', ); } return $out; } public static function contributors() { if (!class_exists('AIEO_DMM_Contributors')) { return array(); } global $wpdb; $tbl = AIEO_DMM_Contributors::table(); $exists = $wpdb->get_var($wpdb->prepare("SHOW TABLES LIKE %s", $tbl)); if (!$exists) { return array(); } $rows = $wpdb->get_results("SELECT match_value, match_canonical, match_field, match_type, label FROM {$tbl} WHERE enabled = 1 ORDER BY priority DESC, id ASC LIMIT 500", ARRAY_A); if (!is_array($rows)) { return array(); } $out = array(); foreach ($rows as $r) { $out[] = array( 'match' => (string) $r['match_value'], 'canonical' => (string) $r['match_canonical'], 'field' => (string) $r['match_field'], 'type' => (string) $r['match_type'], 'label' => isset($r['label']) ? (string) $r['label'] : '', ); } return $out; } public static function vocabulary() { if (!class_exists('AIEO_Display_Conditions')) { return array(); } return array( 'conditions' => AIEO_Display_Conditions::defaults(), 'enums' => AIEO_Display_Conditions::ENUMS, 'overridable' => AIEO_Display_Conditions::OVERRIDABLE, 'triggers' => AIEO_Display_Conditions::TRIGGERS, 'instigators' => AIEO_Display_Conditions::INSTIGATORS, 'placements' => AIEO_Display_Conditions::PLACEMENTS, 'denied_pages' => AIEO_Display_Conditions::CONVERSION_PAGES, 'suppress_reasons' => AIEO_Display_Conditions::SUPPRESS_REASONS, 'types' => self::TYPES, 'server_painted' => self::SERVER_PAINTED, 'surfaces' => self::surfaces(), 'contributors' => self::contributors(), 'origins' => self::origins(), ); } public static function create($type, $args = array()) { $type = (string) $type; if (!in_array($type, self::TYPES, true)) { return new WP_Error('aieo_display_type', __('Unknown surface type.', 'ai-eshop-optimizer')); } if ('rail' === $type) { return new WP_Error('aieo_display_builtin', __('Recommendation rails are built in and cannot be created.', 'ai-eshop-optimizer')); } $check = self::validate($type, isset($args['conditions']) ? $args['conditions'] : array()); if (is_wp_error($check)) { return $check; } switch ($type) { case 'popup': return self::create_popup($args); case 'slot': return self::create_slot($args); case 'promo': return self::create_promo($args); } return new WP_Error('aieo_display_type', __('Unknown surface type.', 'ai-eshop-optimizer')); } public static function update($key, $args = array()) { $surface = self::surface($key); if (!$surface) { return new WP_Error('aieo_display_missing', __('No such surface.', 'ai-eshop-optimizer')); } if (array_key_exists('conditions', $args)) { $check = self::validate($surface['type'], $args['conditions']); if (is_wp_error($check)) { return $check; } $args['conditions'] = AIEO_Display_Conditions::sanitize( array_merge($surface['conditions'], (array) $args['conditions']) ); } if (array_key_exists('placement', $args)) { $args['placement'] = AIEO_Display_Conditions::sanitize_placement( array_merge($surface['placement'], (array) $args['placement']) ); } switch ($surface['type']) { case 'popup': return self::update_popup($surface, $args); case 'slot': return self::update_slot($surface, $args); case 'rail': return self::update_rail($surface, $args); case 'promo': return self::update_promo($surface, $args); } return new WP_Error('aieo_display_type', __('Unknown surface type.', 'ai-eshop-optimizer')); } public static function validate($type, $conditions) { if (!is_array($conditions) || !in_array($type, self::SERVER_PAINTED, true)) { return true; } if (!class_exists('AIEO_Display_Conditions')) { return true; } $cfg = AIEO_Display_Conditions::sanitize($conditions); foreach (self::ASYNC_CONDITIONS as $key) { $set = ('cart' === $key) ? ('any' !== $cfg['cart']) : !empty($cfg['audience']); if (!$set) { continue; } return new WP_Error( 'aieo_display_async_condition', sprintf( __('The ā€œ%1$sā€ rule needs to ask the server a question, and a %2$s is drawn by the server before the browser can ask one. Use a rule that can be answered from the visitor\'s own browser instead.', 'ai-eshop-optimizer'), $key, $type ) ); } $defaults = AIEO_Display_Conditions::defaults(); foreach (AIEO_Display_Conditions::CLIENT_PAINTED_ONLY as $key) { if (!array_key_exists($key, $cfg) || $cfg[$key] === $defaults[$key]) { continue; } return new WP_Error( 'aieo_display_client_painted_only', sprintf( __('ā€œ%1$sā€ decides WHEN something appears, and a %2$s is already on the page before anything can decide. Set it on a popup or a custom slot, whose content the browser puts there.', 'ai-eshop-optimizer'), $key, $type ) ); } return true; } public static function save_experiment(array $payload) { if (!class_exists('AIEO_Display_Experiment')) { $file = dirname(__DIR__) . '/class-aieo-display-experiment.php'; if (file_exists($file)) { require_once $file; } } if (!class_exists('AIEO_Display_Experiment')) { return array(array('code' => 'unavailable', 'surface' => '', 'key' => '', 'message' => __('The experiment store is not available on this install.', 'ai-eshop-optimizer'))); } $errors = AIEO_Display_Experiment::validate_payload($payload); if (!empty($errors)) { return $errors; } $list = AIEO_Display_Experiment::all(); $id = isset($payload['id']) ? sanitize_key((string) $payload['id']) : ''; $found = null; if ('' !== $id) { foreach ($list as $i => $e) { if ($e['id'] === $id) { $found = $i; break; } } } if (null === $found) { $mint = AIEO_Display_Experiment::mint(); if ('' === $id) { $id = sanitize_key('exp_' . wp_generate_password(8, false, false)); } $payload['id'] = $id; if (empty($payload['salt'])) { $payload['salt'] = $mint['salt']; } if (!isset($payload['started_at'])) { $payload['started_at'] = !empty($payload['enabled']) ? $mint['started_at'] : ''; } $list[] = $payload; } else { $payload['id'] = $id; if (empty($payload['salt'])) { $payload['salt'] = $list[$found]['salt']; } if (!isset($payload['started_at'])) { $payload['started_at'] = $list[$found]['started_at']; } if (!array_key_exists('enabled', $payload)) { $payload['enabled'] = $list[$found]['enabled']; } if (!array_key_exists('label', $payload)) { $payload['label'] = $list[$found]['label']; } $list[$found] = $payload; } $saved = AIEO_Display_Experiment::save($list); foreach ($saved as $e) { if ($e['id'] === $id) { return array(); } } return array(array('code' => 'rejected', 'surface' => '', 'key' => '', 'message' => __('The experiment did not survive storage sanitisation and was not stored.', 'ai-eshop-optimizer'))); } public static function is_server_painted($type) { return in_array((string) $type, self::SERVER_PAINTED, true); } private static function create_popup($args) { if (!class_exists('AIEO_Popup_CPT')) { return new WP_Error('aieo_display_store', __('Popups are not available on this site.', 'ai-eshop-optimizer')); } $post_id = wp_insert_post(array( 'post_type' => AIEO_Popup_CPT::POST_TYPE, 'post_title' => isset($args['label']) ? (string) $args['label'] : '', 'post_content' => isset($args['content']) ? (string) $args['content'] : '', 'post_status' => !empty($args['enabled']) ? 'publish' : 'draft', ), true); if (is_wp_error($post_id)) { return $post_id; } $surface = array('key' => AIEO_Popup_CPT::surface_tag($post_id), 'type' => 'popup', 'ref' => (int) $post_id); $written = self::update_popup($surface, $args); if (is_wp_error($written)) { return $written; } return AIEO_Popup_CPT::surface_tag($post_id); } private static function popup_meta_map() { return array( 'trigger' => AIEO_Popup_CPT::META_TRIGGER, 'delayMs' => AIEO_Popup_CPT::META_DELAY_MS, 'scrollPct' => AIEO_Popup_CPT::META_SCROLL_PCT, 'idleS' => AIEO_Popup_CPT::META_IDLE_S, 'freq' => AIEO_Popup_CPT::META_FREQ, 'freqDays' => AIEO_Popup_CPT::META_FREQ_DAYS, 'maxImpr' => AIEO_Popup_CPT::META_MAX_IMPR, 'device' => AIEO_Popup_CPT::META_DEVICE, 'visitor' => AIEO_Popup_CPT::META_VISITOR, 'cart' => AIEO_Popup_CPT::META_CART, 'cartMin' => AIEO_Popup_CPT::META_CART_MIN, 'audience' => AIEO_Popup_CPT::META_AUDIENCE_ID, ); } private static function update_popup($surface, $args) { $post_id = (int) $surface['ref']; $post = array('ID' => $post_id); if (array_key_exists('label', $args)) { $post['post_title'] = (string) $args['label']; } if (array_key_exists('content', $args)) { $post['post_content'] = (string) $args['content']; } if (array_key_exists('enabled', $args)) { $post['post_status'] = !empty($args['enabled']) ? 'publish' : 'draft'; } if (array_key_exists('conditions', $args) && isset($args['conditions']['priority'])) { $post['menu_order'] = (int) $args['conditions']['priority']; } if (count($post) > 1) { wp_update_post($post); } if (array_key_exists('conditions', $args)) { foreach (self::popup_meta_map() as $key => $meta) { if (!array_key_exists($key, $args['conditions'])) { continue; } update_post_meta($post_id, $meta, $args['conditions'][$key]); } if (array_key_exists('test', $args['conditions'])) { update_post_meta($post_id, AIEO_Popup_CPT::META_TEST, !empty($args['conditions']['test']) ? 'yes' : 'no'); } } if (array_key_exists('placement', $args)) { $p = $args['placement']; update_post_meta($post_id, AIEO_Popup_CPT::META_PAGES, (array) $p['pages']); update_post_meta($post_id, AIEO_Popup_CPT::META_EXCLUDE_PAGES, (array) $p['exclude']); update_post_meta($post_id, AIEO_Popup_CPT::META_POST_IDS, (array) $p['ids']); update_post_meta($post_id, AIEO_Popup_CPT::META_URL_CONTAINS, (string) $p['url']); } if (method_exists('AIEO_Popup_CPT', 'on_save')) { $obj = get_post($post_id); if ($obj) { AIEO_Popup_CPT::on_save($post_id, $obj); } } return true; } private static function create_slot($args) { if (!class_exists('AIEO_DMM_Custom_Slots')) { return new WP_Error('aieo_display_store', __('Custom slots are not available on this site.', 'ai-eshop-optimizer')); } $content = isset($args['content']) && is_array($args['content']) ? $args['content'] : array(); $type = isset($content['type']) && 'tolstoy' === $content['type'] ? 'tolstoy' : 'html'; $id = AIEO_DMM_Custom_Slots::add_slot($type); $written = self::update_slot(array('key' => $id, 'type' => 'slot', 'ref' => $id), $args); if (is_wp_error($written)) { return $written; } return $id; } private static function update_slot($surface, $args) { $slots = AIEO_DMM_Custom_Slots::get_all_slots(); $found = false; foreach ($slots as $i => $slot) { if ((string) $slot['id'] !== (string) $surface['ref']) { continue; } $found = true; if (array_key_exists('label', $args)) { $slots[$i]['name'] = (string) $args['label']; } if (array_key_exists('enabled', $args)) { $slots[$i]['enabled'] = (bool) $args['enabled']; } if (array_key_exists('conditions', $args)) { $slots[$i]['conditions'] = $args['conditions']; } if (array_key_exists('placement', $args)) { $slots[$i]['placement'] = $args['placement']; } if (array_key_exists('content', $args) && is_array($args['content'])) { foreach (array('type', 'title', 'show_title', 'tolstoy_widget_id', 'tolstoy_account_uuid', 'raw_html') as $k) { if (array_key_exists($k, $args['content'])) { $slots[$i][$k] = $args['content'][$k]; } } } } if (!$found) { return new WP_Error('aieo_display_missing', __('No such slot.', 'ai-eshop-optimizer')); } AIEO_DMM_Custom_Slots::update_all_slots($slots); return true; } private static function update_rail($surface, $args) { $rows = AIEO_DMM_Product_Display::get_reco_order(); $found = false; foreach ($rows as $i => $r) { if ((string) $r['id'] !== (string) $surface['ref']) { continue; } $found = true; if (array_key_exists('enabled', $args)) { $rows[$i]['visible'] = (bool) $args['enabled']; } if (array_key_exists('conditions', $args)) { $rows[$i]['conditions'] = $args['conditions']; } } if (!$found) { return new WP_Error('aieo_display_missing', __('No such rail.', 'ai-eshop-optimizer')); } if (array_key_exists('label', $args) || array_key_exists('content', $args)) { return new WP_Error('aieo_display_builtin', __('A recommendation rail\'s name and content are built in.', 'ai-eshop-optimizer')); } update_option(AIEO_DMM_Product_Display::RECO_ORDER_OPTION, $rows, false); do_action('aieo_purge_page_cache'); return true; } private static function create_promo($args) { if (!class_exists('AIEO_DMM_Promo_Surfaces')) { return new WP_Error('aieo_display_store', __('Promo surfaces are not available on this site.', 'ai-eshop-optimizer')); } global $wpdb; $content = isset($args['content']) && is_array($args['content']) ? $args['content'] : array(); $slug = sanitize_key(isset($content['slug']) ? (string) $content['slug'] : (string) ($args['label'] ?? '')); if ('' === $slug) { return new WP_Error('aieo_display_slug', __('A promo surface needs a slug.', 'ai-eshop-optimizer')); } $wpdb->insert(AIEO_DMM_Promo_Surfaces::tbl_surfaces(), array( 'slug' => $slug, 'label' => isset($args['label']) ? (string) $args['label'] : '', 'layout_preset' => isset($content['layout_preset']) ? sanitize_key((string) $content['layout_preset']) : 'hero_plus_3banners', 'is_active' => !empty($args['enabled']) ? 1 : 0, )); $surface_id = (int) $wpdb->insert_id; if ($surface_id <= 0) { return new WP_Error('aieo_display_store', __('Could not create the promo surface.', 'ai-eshop-optimizer')); } $key = 'promo_' . $slug; $written = self::update_promo(array('key' => $key, 'type' => 'promo', 'ref' => $surface_id), $args); if (is_wp_error($written)) { return $written; } return $key; } private static function update_promo($surface, $args) { global $wpdb; $data = array(); if (array_key_exists('label', $args)) { $data['label'] = (string) $args['label']; } if (array_key_exists('enabled', $args)) { $data['is_active'] = !empty($args['enabled']) ? 1 : 0; } if (array_key_exists('conditions', $args)) { $data['conditions'] = wp_json_encode($args['conditions']); } if (array_key_exists('content', $args) && is_array($args['content']) && isset($args['content']['layout_preset'])) { $data['layout_preset'] = sanitize_key((string) $args['content']['layout_preset']); } if (empty($data)) { return true; } $wpdb->update(AIEO_DMM_Promo_Surfaces::tbl_surfaces(), $data, array('surface_id' => (int) $surface['ref'])); if (method_exists('AIEO_DMM_Promo_Surfaces', 'rebuild_conditional_surface_map')) { AIEO_DMM_Promo_Surfaces::rebuild_conditional_surface_map(); } do_action('aieo_purge_page_cache'); return true; } }