__('Competitions', 'ai-eshop-optimizer'), 'description' => __('Run prize competitions and giveaways through your existing Gravity Forms — with a ready-made landing page, desktop/mobile creatives, one-click social share links, and (next) participant tracking, winner selection and profiling insights.', 'ai-eshop-optimizer'), 'bullets' => array( __('Associate any Gravity Form as the entry form', 'ai-eshop-optimizer'), __('Branded landing page via a roosterx pattern + block', 'ai-eshop-optimizer'), __('Desktop + mobile creatives, used as the social preview', 'ai-eshop-optimizer'), __('Facebook / TikTok / Instagram share links', 'ai-eshop-optimizer'), __('Participants, winners and profiling metrics', 'ai-eshop-optimizer'), ), ); } public function register_hooks() { if (!class_exists('AIEO_Competition_CPT') && defined('AIEO_DIR') && file_exists(AIEO_DIR . 'includes/dmm/competitions/class-aieo-competition-cpt.php')) { require_once AIEO_DIR . 'includes/dmm/competitions/class-aieo-competition-cpt.php'; } if (class_exists('AIEO_Competition_CPT')) { AIEO_Competition_CPT::register(); } if (!class_exists('AIEO_Competition_Participants') && defined('AIEO_DIR') && file_exists(AIEO_DIR . 'includes/dmm/competitions/class-aieo-competition-participants.php')) { require_once AIEO_DIR . 'includes/dmm/competitions/class-aieo-competition-participants.php'; } if (class_exists('AIEO_Competition_Participants')) { AIEO_Competition_Participants::register(); } if (!class_exists('AIEO_Competition_Entry_Form') && defined('AIEO_DIR') && file_exists(AIEO_DIR . 'includes/dmm/competitions/class-aieo-competition-entry-form.php')) { require_once AIEO_DIR . 'includes/dmm/competitions/class-aieo-competition-entry-form.php'; } if (class_exists('AIEO_Competition_Entry_Form')) { AIEO_Competition_Entry_Form::register(); } if (!class_exists('AIEO_Competition_Analytics') && defined('AIEO_DIR') && file_exists(AIEO_DIR . 'includes/dmm/competitions/class-aieo-competition-analytics.php')) { require_once AIEO_DIR . 'includes/dmm/competitions/class-aieo-competition-analytics.php'; } add_action('wp_ajax_aieo_competitions_save', array($this, 'ajax_save')); add_action('wp_ajax_aieo_competitions_settings_save', array($this, 'ajax_save_settings')); add_action('wp_ajax_aieo_competition_draw_winners', array($this, 'ajax_draw_winners')); add_action('wp_ajax_aieo_competition_toggle_winner', array($this, 'ajax_toggle_winner')); if (class_exists('AIEO_Competition_Analytics')) { add_action(AIEO_Competition_Analytics::STATS_REFRESH_HOOK, array('AIEO_Competition_Analytics', 'refresh_stats_cache')); add_action(AIEO_Competition_Analytics::STATS_REFRESH_HOOK . '_once', array('AIEO_Competition_Analytics', 'refresh_stats_cache')); } add_action('wp_ajax_aieo_competitions_refresh_stats', array($this, 'ajax_refresh_stats')); add_action('wp_ajax_aieo_competitions_stats_cadence', array($this, 'ajax_save_stats_cadence')); add_filter('cron_schedules', array($this, 'ensure_cron_schedules')); add_action('admin_init', array($this, 'maybe_schedule_stats')); } const STATS_CADENCES = array('off', 'hourly', 'daily', 'weekly', 'monthly'); public static function get_stats_cadence() { $s = self::get_settings(); $c = isset($s['stats_refresh']) ? (string) $s['stats_refresh'] : 'daily'; return in_array($c, self::STATS_CADENCES, true) ? $c : 'daily'; } public function ensure_cron_schedules($schedules) { if (!is_array($schedules)) { $schedules = array(); } if (!isset($schedules['weekly'])) { $schedules['weekly'] = array('interval' => 7 * DAY_IN_SECONDS, 'display' => __('Once Weekly', 'ai-eshop-optimizer')); } if (!isset($schedules['monthly'])) { $schedules['monthly'] = array('interval' => 30 * DAY_IN_SECONDS, 'display' => __('Once Monthly', 'ai-eshop-optimizer')); } return $schedules; } public function maybe_schedule_stats() { if (!class_exists('AIEO_Competition_Analytics')) { return; } $hook = AIEO_Competition_Analytics::STATS_REFRESH_HOOK; $current = wp_get_schedule($hook); $cadence = self::get_stats_cadence(); if ('off' === $cadence || !self::competitions_exist()) { if (false !== $current) { wp_clear_scheduled_hook($hook); } return; } if ($current === $cadence) { return; } if (false !== $current) { wp_clear_scheduled_hook($hook); } wp_schedule_event(time() + 300, $cadence, $hook); } public function ajax_save_stats_cadence() { if (!check_ajax_referer(self::NONCE, 'nonce', false)) { wp_send_json_error(array('message' => __('Security check failed.', 'ai-eshop-optimizer'))); } if (!current_user_can('edit_posts')) { wp_send_json_error(array('message' => __('You are not allowed to do this.', 'ai-eshop-optimizer'))); } $cadence = isset($_POST['cadence']) ? sanitize_key(wp_unslash($_POST['cadence'])) : 'daily'; if (!in_array($cadence, self::STATS_CADENCES, true)) { $cadence = 'daily'; } $s = self::get_settings(); $s['stats_refresh'] = $cadence; update_option(self::SETTINGS_OPTION, $s, false); $this->maybe_schedule_stats(); wp_send_json_success(array('cadence' => $cadence)); } private static function competitions_exist() { if (!class_exists('AIEO_Competition_CPT')) { return false; } $counts = (array) wp_count_posts(AIEO_Competition_CPT::POST_TYPE); foreach (array('publish', 'draft', 'pending', 'future', 'private') as $st) { if (!empty($counts[$st])) { return true; } } return false; } public function ajax_refresh_stats() { if (!check_ajax_referer(self::NONCE, 'nonce', false)) { wp_send_json_error(array('message' => __('Security check failed.', 'ai-eshop-optimizer'))); } if (!current_user_can('edit_posts') || !class_exists('AIEO_Competition_Analytics')) { wp_send_json_error(array('message' => __('Not allowed.', 'ai-eshop-optimizer'))); } $payload = AIEO_Competition_Analytics::refresh_stats_cache(10); wp_send_json_success(array('generated_at' => $payload['generated_at'])); } public function ajax_draw_winners() { if (!check_ajax_referer(self::NONCE, 'nonce', false)) { wp_send_json_error(array('message' => __('Security check failed.', 'ai-eshop-optimizer'))); } if (!current_user_can('edit_posts') || !class_exists('AIEO_Competition_Participants')) { wp_send_json_error(array('message' => __('Not allowed.', 'ai-eshop-optimizer'))); } $cid = isset($_POST['competition_id']) ? (int) $_POST['competition_id'] : 0; $count = isset($_POST['count']) ? (int) $_POST['count'] : 1; if ($cid <= 0) { wp_send_json_error(array('message' => __('Missing competition.', 'ai-eshop-optimizer'))); } $winners = AIEO_Competition_Participants::draw_winners($cid, $count); wp_send_json_success(array('winners' => self::winners_payload($winners))); } public function ajax_toggle_winner() { if (!check_ajax_referer(self::NONCE, 'nonce', false)) { wp_send_json_error(array('message' => __('Security check failed.', 'ai-eshop-optimizer'))); } if (!current_user_can('edit_posts') || !class_exists('AIEO_Competition_Participants')) { wp_send_json_error(array('message' => __('Not allowed.', 'ai-eshop-optimizer'))); } $pid = isset($_POST['participant_id']) ? (int) $_POST['participant_id'] : 0; $on = !empty($_POST['on']); if ($pid <= 0) { wp_send_json_error(array('message' => __('Missing participant.', 'ai-eshop-optimizer'))); } AIEO_Competition_Participants::set_winner($pid, $on); wp_send_json_success(); } private static function winners_payload($winners) { $out = array(); foreach ((array) $winners as $w) { $name = trim((string) ($w->first_name ?? '') . ' ' . (string) ($w->last_name ?? '')); $out[] = array('pid' => (int) $w->participant_id, 'name' => $name, 'email' => (string) $w->email); } return $out; } const SETTINGS_OPTION = 'aieo_competitions_settings'; public static function get_settings() { $s = get_option(self::SETTINGS_OPTION, array()); return is_array($s) ? $s : array(); } public function enqueue_admin_assets() { if (!self::is_aieo_pro_user()) { return; } wp_enqueue_media(); $css = 'assets/css/aieo-competitions-admin.css'; wp_enqueue_style('aieo-competitions-admin', plugins_url($css, AIEO_FILE), array(), file_exists(AIEO_DIR . $css) ? (string) filemtime(AIEO_DIR . $css) : '1'); $handle = 'aieo-competitions-admin'; $js_rel = 'assets/js/aieo-competitions-admin.js'; $js_abs = defined('AIEO_DIR') ? AIEO_DIR . $js_rel : ''; $js_ver = ($js_abs && file_exists($js_abs)) ? (string) filemtime($js_abs) : (defined('AIEO_VERSION') ? AIEO_VERSION : '1.0'); wp_enqueue_script( $handle, plugins_url($js_rel, AIEO_FILE), array('jquery'), $js_ver, true ); wp_localize_script($handle, 'aieoCompetitions', array( 'ajaxurl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce(self::NONCE), 'i18n' => array( 'select' => __('Select image', 'ai-eshop-optimizer'), 'use' => __('Use this image', 'ai-eshop-optimizer'), 'copied' => __('Copied!', 'ai-eshop-optimizer'), 'saving' => __('Saving…', 'ai-eshop-optimizer'), 'saved' => __('Saved.', 'ai-eshop-optimizer'), 'noname' => __('Please enter a competition name.', 'ai-eshop-optimizer'), 'refreshing' => __('Refreshing…', 'ai-eshop-optimizer'), 'error' => __('Error', 'ai-eshop-optimizer'), 'failed' => __('Network error.', 'ai-eshop-optimizer'), ), )); } public function render_subtab() { $this->maybe_schedule_stats(); rooster_admin_band_open(array('cols' => 2)); list($open, $close) = AIEO_DMM_UI::card(array( 'anchor' => 'competitions-new-competition', 'icon' => 'plus', 'title' => __('New competition', 'ai-eshop-optimizer'), 'description' => __('Create a competition, associate its Gravity Form entry form, add a short description and desktop/mobile creatives, then share it. Each competition becomes a landing page you build with the “Competition” pattern (it inherits these details automatically).', 'ai-eshop-optimizer'), 'col' => 1, 'footer' => '' . '', )); echo $open; echo '
'; echo ''; echo '
'; echo AIEO_Kit::row(__('Name', 'ai-eshop-optimizer'), \Rooster\Admin\Ui::input('', '', array('id' => 'aieo-comp-name', 'medium' => true, 'placeholder' => __('e.g. Summer Giveaway 2026', 'ai-eshop-optimizer'))), '', array('id' => 'aieo-comp-name')); echo AIEO_Kit::row(__('Gravity Form', 'ai-eshop-optimizer'), AIEO_Kit::select('aieo-comp-gf', AIEO_Competition_CPT::gf_form_options(0), '0', array('id' => 'aieo-comp-gf')), __('The Gravity Forms form entrants submit. With none, the competition’s built-in entry form takes the entries.', 'ai-eshop-optimizer'), array('id' => 'aieo-comp-gf')); echo AIEO_Kit::row(__('Terms page', 'ai-eshop-optimizer'), AIEO_Kit::select('aieo-comp-terms', AIEO_Competition_CPT::terms_page_options(), '0', array('id' => 'aieo-comp-terms')), __('The page with the competition’s terms. When one is chosen, the terms box of the built-in entry form links to it.', 'ai-eshop-optimizer'), array('id' => 'aieo-comp-terms')); echo AIEO_Kit::row(__('Short description', 'ai-eshop-optimizer'), \Rooster\Admin\Ui::textarea('', '', array('id' => 'aieo-comp-desc', 'rows' => 3)), '', array('id' => 'aieo-comp-desc')); echo AIEO_Kit::row(__('Creatives', 'ai-eshop-optimizer'), '
' . $this->image_field('desktop', __('Desktop image', 'ai-eshop-optimizer')) . $this->image_field('mobile', __('Mobile image', 'ai-eshop-optimizer')) . '
', '', array('id' => 'aieo-comp-creatives', 'group' => true)); $comp_row = \Rooster\Admin\Fields::compound( array( array('label' => __('Starts', 'ai-eshop-optimizer'), 'id' => 'aieo-comp-start', 'control' => \Rooster\Admin\Ui::input('', '', array('id' => 'aieo-comp-start', 'type' => 'datetime-local'))), array('label' => __('Ends', 'ai-eshop-optimizer'), 'id' => 'aieo-comp-end', 'control' => \Rooster\Admin\Ui::input('', '', array('id' => 'aieo-comp-end', 'type' => 'datetime-local'))), array('label' => __('Winners announced', 'ai-eshop-optimizer'), 'id' => 'aieo-comp-announce', 'control' => \Rooster\Admin\Ui::input('', '', array('id' => 'aieo-comp-announce', 'type' => 'datetime-local'))), ), array('label' => __('Schedule', 'ai-eshop-optimizer')) ); echo AIEO_Kit::row(__('Schedule', 'ai-eshop-optimizer'), $comp_row, __('Optional. Visitors see a “coming soon” notice before the start and an “ended” message after the end; shop managers always see the live form. Leave start/end empty for an always-open competition. “Winners announced” is the date shown to entrants (and used by the Events timeline).', 'ai-eshop-optimizer'), array('id' => 'aieo-comp-schedule', 'group' => true)); echo AIEO_Kit::row(__('Brands', 'ai-eshop-optimizer'), '', __('Optional — associate brands so you can later monitor the products they pull.', 'ai-eshop-optimizer'), array('id' => 'aieo-comp-brands')); echo AIEO_Kit::row(__('Categories', 'ai-eshop-optimizer'), '', __('Optional — associate product categories.', 'ai-eshop-optimizer'), array('id' => 'aieo-comp-categories')); if (class_exists('AIEO_DMM_Product_Picker')) { $comp_picker = AIEO_DMM_Product_Picker::render_field(array( 'name' => 'comp_products', 'mode' => 'include', 'selected' => array(), 'bare' => true, 'empty_text' => __('No products associated.', 'ai-eshop-optimizer'), )); echo AIEO_Kit::row(__('Products', 'ai-eshop-optimizer'), $comp_picker, __('Optional — specific products associated with this competition (to monitor their pull).', 'ai-eshop-optimizer'), array('id' => 'aieo-comp-products', 'group' => true)); } $cur = function_exists('get_woocommerce_currency_symbol') ? html_entity_decode(get_woocommerce_currency_symbol(), ENT_QUOTES, 'UTF-8') : ''; $comp_row = \Rooster\Admin\Fields::compound( array( array('label' => __('Number of winners', 'ai-eshop-optimizer'), 'id' => 'aieo-comp-winners', 'control' => \Rooster\Admin\Ui::input('', '', array('id' => 'aieo-comp-winners', 'type' => 'number', 'xs' => true, 'min' => 0, 'step' => 1))), array('label' => trim(sprintf(__('Prize value per winner %s', 'ai-eshop-optimizer'), $cur !== '' ? '(' . $cur . ')' : '')), 'id' => 'aieo-comp-prize', 'control' => \Rooster\Admin\Ui::input('', '', array('id' => 'aieo-comp-prize', 'type' => 'number', 'short' => true, 'min' => 0, 'step' => 'any'))), ), array('label' => __('Winners and prize', 'ai-eshop-optimizer')) ); echo AIEO_Kit::row(__('Winners and prize', 'ai-eshop-optimizer'), $comp_row, __('e.g. 3 winners, 50 each → total prize pool 150. Use the prize value when winners get a bag of products rather than one SKU.', 'ai-eshop-optimizer'), array('id' => 'aieo-comp-winners-group', 'group' => true)); echo '
'; echo '
'; echo $close; $stats_cache = class_exists('AIEO_Competition_Analytics') ? AIEO_Competition_Analytics::get_cached_stats() : null; $comps = get_posts(array( 'post_type' => AIEO_Competition_CPT::POST_TYPE, 'post_status' => array('publish', 'draft', 'pending', 'future'), 'numberposts' => 200, 'orderby' => 'date', 'order' => 'DESC', )); list($open, $close) = AIEO_DMM_UI::card(array( 'anchor' => 'competitions-your-competitions', 'icon' => 'list', 'title' => __('Your competitions', 'ai-eshop-optimizer'), 'description' => __('Every competition you have created and its status; open one to edit it or draw its winners.', 'ai-eshop-optimizer'), 'col' => 2, 'flush' => true, )); echo $open; $insights_url = add_query_arg( array('page' => AIEO_DMM::MENU_SLUG, 'dmm_tab' => 'competition_insights'), admin_url('admin.php') ); $aieo_comp_where = wp_kses_post(sprintf( __('Entries, entrants who ordered and revenue from entrants are reported under %1$sPerformance → Competition results%2$s.', 'ai-eshop-optimizer'), '', '' )); echo \Rooster\Admin\Guidance::text(wp_strip_all_tags($aieo_comp_where), 'guidance', array('html' => $aieo_comp_where)); if (empty($comps)) { echo \Rooster\Admin\States::state('first-use', array('title' => __('No competitions yet — create one above.', 'ai-eshop-optimizer'))); } else { $rev_map = ($stats_cache && isset($stats_cache['revenue_by_competition'])) ? (array) $stats_cache['revenue_by_competition'] : array(); $sstate_labels = array( 'before' => __('upcoming', 'ai-eshop-optimizer'), 'live' => __('live', 'ai-eshop-optimizer'), 'ended' => __('ended', 'ai-eshop-optimizer'), ); $aieo_cp_rows = array(); foreach ($comps as $c) { $gf_id = (int) get_post_meta($c->ID, AIEO_Competition_CPT::META_GF, true); $sstate = class_exists('AIEO_Competition_CPT') ? AIEO_Competition_CPT::schedule_state($c->ID) : 'live'; $start = AIEO_Competition_CPT::fmt_dt(get_post_meta($c->ID, AIEO_Competition_CPT::META_START, true)); $end = AIEO_Competition_CPT::fmt_dt(get_post_meta($c->ID, AIEO_Competition_CPT::META_END, true)); $m = class_exists('AIEO_Competition_Participants') ? AIEO_Competition_Participants::metrics($c->ID) : array('entries' => 0, 'confirmed' => 0, 'pending' => 0); $inc = isset($rev_map[$c->ID]) ? $rev_map[$c->ID] : null; $share = ''; foreach (AIEO_Competition_CPT::share_links($c->ID) as $net => $url) { if ($url === '') { continue; } $share .= AIEO_Kit::button(ucfirst($net), array( 'variant' => 'ghost', 'size' => 'sm', 'class' => 'aieo-comp-copy', 'attrs' => array( 'data-url' => $url, 'title' => sprintf(__('Copy %s share link', 'ai-eshop-optimizer'), ucfirst($net)), ), )); } $entries_note = (int) $m['confirmed'] . ' ' . __('confirmed', 'ai-eshop-optimizer') . ((int) $m['pending'] > 0 ? ', ' . (int) $m['pending'] . ' ' . __('pending', 'ai-eshop-optimizer') : ''); $aieo_cp_rows[] = array( 'id' => (int) $c->ID, 'name' => get_the_title($c) ?: __('(untitled)', 'ai-eshop-optimizer'), 'edit' => (string) get_edit_post_link($c->ID), 'view' => (string) get_permalink($c->ID), 'dupe' => class_exists('AIEO_Post_Duplicator') ? (string) AIEO_Post_Duplicator::url($c->ID) : '', 'form' => $gf_id ? '#' . $gf_id : '', 'status' => get_post_status($c), 'state' => isset($sstate_labels[$sstate]) ? $sstate_labels[$sstate] : $sstate, 'schedule' => ('' === $start && '' === $end) ? __('Always open', 'ai-eshop-optimizer') : ('' !== $start ? $start : '—') . ' → ' . ('' !== $end ? $end : '—'), 'entries' => (int) $m['entries'], 'entries_sub' => $entries_note, 'income' => ($inc && $inc['revenue'] > 0) ? (float) $inc['revenue'] : null, 'buyers' => ($inc && $inc['revenue'] > 0) ? '(' . (int) $inc['converting'] . ' ' . _n('buyer', 'buyers', (int) $inc['converting'], 'ai-eshop-optimizer') . ')' : '', 'share' => $share, ); } AIEO_Table::render(array( 'page_id' => $this->get_id(), 'row_key' => 'id', 'rows' => $aieo_cp_rows, 'empty' => __('No competitions yet — create one above.', 'ai-eshop-optimizer'), 'columns' => array( array('key' => 'name', 'label' => __('Name', 'ai-eshop-optimizer'), 'type' => 'link', 'format' => array('href' => 'edit')), array('key' => 'form', 'label' => __('Form', 'ai-eshop-optimizer'), 'type' => 'id', 'empty' => __('— none —', 'ai-eshop-optimizer')), array('key' => 'status', 'label' => __('Status', 'ai-eshop-optimizer'), 'type' => 'badge', 'subline' => 'state'), array('key' => 'schedule', 'label' => __('Schedule', 'ai-eshop-optimizer'), 'nowrap' => true), array('key' => 'entries', 'label' => __('Entries', 'ai-eshop-optimizer'), 'type' => 'integer', 'subline' => 'entries_sub'), array('key' => 'income', 'label' => __('Income', 'ai-eshop-optimizer'), 'type' => 'money', 'suffix' => 'buyers', 'nowrap' => true), array('key' => 'share', 'label' => __('Share', 'ai-eshop-optimizer'), 'type' => 'html', 'sortable' => false), ), 'actions' => array( array('id' => 'edit', 'label' => __('Edit', 'ai-eshop-optimizer'), 'href' => static function ($r) { return $r['edit']; }), array('id' => 'view', 'label' => __('View', 'ai-eshop-optimizer'), 'external' => true, 'href' => static function ($r) { return $r['view']; }), array( 'id' => 'duplicate', 'label' => __('Duplicate', 'ai-eshop-optimizer'), 'visible' => static function ($r) { return '' !== $r['dupe']; }, 'href' => static function ($r) { return $r['dupe']; }, ), ), )); } echo $close; $cset = self::get_settings(); $msg_soon = isset($cset['msg_soon']) ? (string) $cset['msg_soon'] : ''; $msg_ended = isset($cset['msg_ended']) ? (string) $cset['msg_ended'] : ''; $msg_announce = isset($cset['msg_announce']) ? (string) $cset['msg_announce'] : ''; $msg_form = 'aieo-card-competitions-page-messages-form'; list($open, $close) = AIEO_DMM_UI::card(array( 'anchor' => 'competitions-page-messages', 'icon' => 'message-square-text', 'title' => __('Competition messages and colours', 'ai-eshop-optimizer'), 'description' => __('Shown on the competition page to visitors. These apply to ALL competitions. Leave a field empty to use the built-in (translatable) default.', 'ai-eshop-optimizer'), 'col' => 2, 'footer' => class_exists('\Rooster\Admin\States') ? \Rooster\Admin\States::save_button($msg_form) : '', )); echo $open; ob_start(); echo '
'; echo AIEO_Kit::row(__('“Coming soon” message', 'ai-eshop-optimizer'), AIEO_Kit::textarea('msg_soon', $msg_soon, array('id' => 'aieo-comp-msg-soon', 'rows' => 2)), __('Shown before a competition starts. Use %s where the start date should appear (optional).', 'ai-eshop-optimizer'), array('id' => 'aieo-comp-msg-soon')); echo AIEO_Kit::row(__('“Ended” message', 'ai-eshop-optimizer'), AIEO_Kit::textarea('msg_ended', $msg_ended, array('id' => 'aieo-comp-msg-ended', 'rows' => 2)), __('Shown after a competition ends.', 'ai-eshop-optimizer'), array('id' => 'aieo-comp-msg-ended')); echo AIEO_Kit::row(__('“Winners announced on” message', 'ai-eshop-optimizer'), AIEO_Kit::textarea('msg_announce', $msg_announce, array('id' => 'aieo-comp-msg-announce', 'rows' => 2)), __('Shown while the winners are still to be revealed, when a competition has a “Winners announced” date set. Use %s where the date should appear.', 'ai-eshop-optimizer'), array('id' => 'aieo-comp-msg-announce')); $col_live = (isset($cset['color_live']) && $cset['color_live'] !== '') ? (string) $cset['color_live'] : '#22c55e'; $col_ended = (isset($cset['color_ended']) && $cset['color_ended'] !== '') ? (string) $cset['color_ended'] : '#ef4444'; $col_before = (isset($cset['color_before']) && $cset['color_before'] !== '') ? (string) $cset['color_before'] : '#f59e0b'; $comp_row = \Rooster\Admin\Fields::compound( array( array('label' => __('Active', 'ai-eshop-optimizer'), 'id' => 'aieo-comp-color-live', 'control' => \Rooster\Admin\Fields::color('color_live', $col_live, array('id' => 'aieo-comp-color-live', 'default' => '#22c55e', 'label' => __('Active', 'ai-eshop-optimizer')))), array('label' => __('Ended', 'ai-eshop-optimizer'), 'id' => 'aieo-comp-color-ended', 'control' => \Rooster\Admin\Fields::color('color_ended', $col_ended, array('id' => 'aieo-comp-color-ended', 'default' => '#ef4444', 'label' => __('Ended', 'ai-eshop-optimizer')))), array('label' => __('Coming soon', 'ai-eshop-optimizer'), 'id' => 'aieo-comp-color-before', 'control' => \Rooster\Admin\Fields::color('color_before', $col_before, array('id' => 'aieo-comp-color-before', 'default' => '#f59e0b', 'label' => __('Coming soon', 'ai-eshop-optimizer')))), ), array('label' => __('Status badge colours', 'ai-eshop-optimizer')) ); echo AIEO_Kit::row(__('Status badge colours', 'ai-eshop-optimizer'), $comp_row, __('Base colour of the glass status badge shown over each competition card (a translucency is applied automatically).', 'ai-eshop-optimizer'), array('id' => 'aieo-comp-colours', 'group' => true)); $consent_preticked = class_exists('AIEO_Competition_Participants') ? AIEO_Competition_Participants::consent_preticked() : true; echo AIEO_Kit::row( __('Newsletter consent box', 'ai-eshop-optimizer'), '' . \Rooster\Admin\Ui::toggle('consent_preticked', $consent_preticked, array('id' => 'aieo-comp-consent', 'hidden' => false, 'label' => __('The consent box starts ticked', 'ai-eshop-optimizer'))), __('Each competition’s entry form carries a box to receive your newsletter, and only entrants who leave it ticked are subscribed; the choice is kept with the entry. Entrants take part whether they tick it or not. Switch this off where the law needs consent to be given actively, as in the EU, where a box that starts ticked is not valid consent.', 'ai-eshop-optimizer'), array('id' => 'aieo-comp-consent') ); echo '
'; echo AIEO_DMM_UI::card_form('aieo_competitions_settings_save', $this->get_id() . '_messages', $msg_form, (string) ob_get_clean(), array('nonce' => wp_create_nonce(self::NONCE), 'nonce_name' => 'nonce')); echo $close; rooster_admin_band_close(); } public function render_insights($pointer = '') { $stats_cache = class_exists('AIEO_Competition_Analytics') ? AIEO_Competition_Analytics::get_cached_stats() : null; if (class_exists('AIEO_Competition_Analytics')) { $agg = ($stats_cache && isset($stats_cache['aggregate'])) ? (array) $stats_cache['aggregate'] : null; $gen = ($stats_cache && !empty($stats_cache['generated_at'])) ? (string) $stats_cache['generated_at'] : ''; $asof = ''; if ($gen !== '') { $asof = sprintf(__('updated %s ago', 'ai-eshop-optimizer'), human_time_diff(strtotime($gen . ' UTC'), current_time('timestamp', true))); } $cadence = self::get_stats_cadence(); $cad_opts = array( 'off' => __('Off', 'ai-eshop-optimizer'), 'hourly' => __('Hourly', 'ai-eshop-optimizer'), 'daily' => __('Daily', 'ai-eshop-optimizer'), 'weekly' => __('Weekly', 'ai-eshop-optimizer'), 'monthly' => __('Monthly', 'ai-eshop-optimizer'), ); $cad_select = \Rooster\Admin\Ui::select('', $cad_opts, (string) $cadence, array('id' => 'aieo-comp-stats-cadence', 'short' => true)); $tools = ' ' . $cad_select . ' ' . esc_html($asof) . '' . ' '; list($open, $close) = AIEO_DMM_UI::card(array( 'anchor' => 'competition-insights-insights', 'icon' => 'chart-column', 'title' => __('Insights', 'ai-eshop-optimizer'), 'description' => __('Results for your competitions: entries, entrants who went on to order, and the revenue they brought.', 'ai-eshop-optimizer'), 'tools' => $tools, 'footer' => '' !== (string) $pointer ? '

' . $pointer . '

' : '', )); echo $open; echo '
'; if (!$agg) { echo \Rooster\Admin\States::state('loading', array('id' => 'aieo-comp-stats-preparing', 'title' => __('Preparing insights… this runs in the background and can take a few seconds the first time. The panel refreshes automatically when it’s ready.', 'ai-eshop-optimizer'))); } else { $pct = !empty($agg['unique_emails']) ? round(100 * $agg['identified'] / $agg['unique_emails'], 1) : 0; echo '
'; echo \Rooster\Admin\Ui::stat(__('Total entries', 'ai-eshop-optimizer'), number_format_i18n((int) $agg['total_participants'])); echo \Rooster\Admin\Ui::stat(__('Unique people', 'ai-eshop-optimizer'), number_format_i18n((int) $agg['unique_emails'])); echo \Rooster\Admin\Ui::stat(__('Existing customers', 'ai-eshop-optimizer'), number_format_i18n((int) $agg['identified']) . ' (' . (string) $pct . '%)'); echo \Rooster\Admin\Ui::stat(__('Confirmed', 'ai-eshop-optimizer'), number_format_i18n((int) $agg['confirmed'])); if (!empty($agg['has_order_data'])) { echo \Rooster\Admin\Ui::stat(__('Entrants who ordered', 'ai-eshop-optimizer'), number_format_i18n((int) $agg['converting_entrants'])); echo \Rooster\Admin\Ui::stat(__('Revenue from entrants', 'ai-eshop-optimizer'), AIEO_DMM_Conversions_Registry::money((float) $agg['entrant_revenue'])); } echo '
'; if (empty($agg['top_participants']) && empty($agg['has_ci'])) { echo \Rooster\Admin\States::state('not-set-up', array('compact' => true, 'title' => __('Top-participant ranking by lifetime value appears once Customer Intelligence data is built.', 'ai-eshop-optimizer'))); } } echo '
'; echo $close; if ($agg && !empty($agg['top_participants'])) { $aieo_cmp_top = array(); foreach ($agg['top_participants'] as $aieo_cmp_tp) { $aieo_cmp_tp = (object) $aieo_cmp_tp; $aieo_cmp_top[] = array( 'email' => (string) $aieo_cmp_tp->email, 'entries' => (int) $aieo_cmp_tp->entries, 'wins' => (int) $aieo_cmp_tp->wins, 'ltv' => (float) $aieo_cmp_tp->ltv_total, ); } AIEO_Table::card(array( 'anchor' => 'competition-insights-top-participants-by-lifetime-value', 'icon' => 'users', 'heading' => 'h2', 'title' => __('Top participants by lifetime value', 'ai-eshop-optimizer'), 'page_id' => 'competition_insights', 'row_key' => 'email', 'rows' => $aieo_cmp_top, 'density' => 'compact', 'columns' => array( array('key' => 'email', 'label' => __('Customer', 'ai-eshop-optimizer')), array('key' => 'entries', 'label' => __('Competitions entered', 'ai-eshop-optimizer'), 'type' => 'integer'), array('key' => 'wins', 'label' => __('Wins', 'ai-eshop-optimizer'), 'type' => 'integer'), array('key' => 'ltv', 'label' => __('Lifetime value', 'ai-eshop-optimizer'), 'type' => 'money'), ), )); } } } public function ajax_save_settings() { if (!check_ajax_referer(self::NONCE, 'nonce', false)) { wp_send_json_error(array('message' => __('Security check failed.', 'ai-eshop-optimizer'))); } if (!current_user_can('edit_posts')) { wp_send_json_error(array('message' => __('You are not allowed to do this.', 'ai-eshop-optimizer'))); } $ended = isset($_POST['msg_ended']) ? sanitize_textarea_field(wp_unslash($_POST['msg_ended'])) : ''; $soon = isset($_POST['msg_soon']) ? sanitize_textarea_field(wp_unslash($_POST['msg_soon'])) : ''; $announce = isset($_POST['msg_announce']) ? sanitize_textarea_field(wp_unslash($_POST['msg_announce'])) : ''; $col_live = isset($_POST['color_live']) ? (string) sanitize_hex_color(wp_unslash($_POST['color_live'])) : ''; $col_ended = isset($_POST['color_ended']) ? (string) sanitize_hex_color(wp_unslash($_POST['color_ended'])) : ''; $col_before = isset($_POST['color_before']) ? (string) sanitize_hex_color(wp_unslash($_POST['color_before'])) : ''; $saved = array_merge(self::get_settings(), array( 'msg_soon' => $soon, 'msg_ended' => $ended, 'msg_announce' => $announce, 'color_live' => $col_live, 'color_ended' => $col_ended, 'color_before' => $col_before, )); if (isset($_POST['consent_preticked_present'])) { $saved['consent_preticked'] = !empty($_POST['consent_preticked']) ? '1' : '0'; } update_option(self::SETTINGS_OPTION, $saved, false); wp_send_json_success(array('saved' => true)); } private function image_field($which, $label) { $id = 'aieo-comp-image-' . $which; return '
' . '' . esc_html($label) . '' . '' . '' . '' . AIEO_Kit::button(__('Select image', 'ai-eshop-optimizer'), array('class' => 'aieo-comp-image-select')) . AIEO_Kit::button(__('Remove', 'ai-eshop-optimizer'), array('variant' => 'ghost', 'size' => 'sm', 'class' => 'aieo-comp-image-remove', 'attrs' => array('hidden' => true))) . '
'; } public function ajax_save() { if (!check_ajax_referer(self::NONCE, 'nonce', false)) { wp_send_json_error(array('message' => __('Security check failed.', 'ai-eshop-optimizer'))); } if (!current_user_can('edit_posts')) { wp_send_json_error(array('message' => __('You are not allowed to do this.', 'ai-eshop-optimizer'))); } if (!class_exists('AIEO_Competition_CPT')) { wp_send_json_error(array('message' => __('Competitions are unavailable.', 'ai-eshop-optimizer'))); } $post_id = isset($_POST['post_id']) ? (int) $_POST['post_id'] : 0; $name = isset($_POST['name']) ? sanitize_text_field(wp_unslash($_POST['name'])) : ''; if ($name === '') { wp_send_json_error(array('message' => __('Please enter a competition name.', 'ai-eshop-optimizer'))); } $gf = isset($_POST['gf_id']) ? (int) $_POST['gf_id'] : 0; $desc = isset($_POST['short_desc']) ? sanitize_textarea_field(wp_unslash($_POST['short_desc'])) : ''; $img_d = isset($_POST['img_desktop']) ? (int) $_POST['img_desktop'] : 0; $img_m = isset($_POST['img_mobile']) ? (int) $_POST['img_mobile'] : 0; $start = isset($_POST['start']) ? sanitize_text_field(wp_unslash($_POST['start'])) : ''; $end = isset($_POST['end']) ? sanitize_text_field(wp_unslash($_POST['end'])) : ''; $announce = isset($_POST['announce']) ? sanitize_text_field(wp_unslash($_POST['announce'])) : ''; $brands = isset($_POST['brands']) ? array_map('intval', (array) wp_unslash($_POST['brands'])) : array(); $categories = isset($_POST['categories']) ? array_map('intval', (array) wp_unslash($_POST['categories'])) : array(); $products = class_exists('AIEO_DMM_Product_Picker') ? AIEO_DMM_Product_Picker::roll_up_to_parents(AIEO_DMM_Product_Picker::collect_ids('comp_products')) : (isset($_POST['comp_products']) ? array_map('intval', (array) wp_unslash($_POST['comp_products'])) : array()); $winners = isset($_POST['winners_count']) ? (int) $_POST['winners_count'] : 0; $prize = isset($_POST['prize_value']) ? (float) $_POST['prize_value'] : 0; $args = array( 'post_type' => AIEO_Competition_CPT::POST_TYPE, 'post_title' => $name, 'post_status' => 'publish', ); if ($post_id > 0) { $args['ID'] = $post_id; $res = wp_update_post($args, true); } else { $res = wp_insert_post($args, true); } if (is_wp_error($res)) { wp_send_json_error(array('message' => $res->get_error_message())); } $post_id = (int) $res; AIEO_Competition_CPT::persist_fields($post_id, $gf, $desc, $img_d, $img_m, $start, $end, $brands, $categories, $products, $winners, $prize, $announce); if (isset($_POST['terms_page'])) { AIEO_Competition_CPT::persist_terms_page($post_id, (int) $_POST['terms_page']); } wp_send_json_success(array( 'post_id' => $post_id, 'title' => get_the_title($post_id), 'edit_url' => get_edit_post_link($post_id, 'raw'), 'view_url' => get_permalink($post_id), 'gf_id' => $gf, 'status' => get_post_status($post_id), 'share' => AIEO_Competition_CPT::share_links($post_id), )); } }