post_type !== self::POST_TYPE) { return; } if (!defined('AIEO_FILE')) { return; } wp_enqueue_media(); wp_enqueue_script( 'aieo-competitions-admin', plugins_url('assets/js/aieo-competitions-admin.js', AIEO_FILE), array('jquery'), defined('AIEO_VERSION') ? AIEO_VERSION : '1.0', true ); wp_localize_script('aieo-competitions-admin', 'aieoCompetitions', array( 'ajaxurl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('aieo_competitions'), 'i18n' => array( 'select' => __('Select image', 'ai-eshop-optimizer'), 'use' => __('Use this image', 'ai-eshop-optimizer'), 'copied' => __('Copied!', 'ai-eshop-optimizer'), 'saving' => __('Drawing…', 'ai-eshop-optimizer'), 'removeWinner' => __('Remove winner', 'ai-eshop-optimizer'), ), )); self::enqueue_picker_bundle(); } private static function enqueue_picker_bundle() { if (!defined('AIEO_URI') || !defined('AIEO_DIR')) { return; } foreach (array('select2', 'selectWoo') as $h) { if (wp_script_is($h, 'registered')) { wp_enqueue_script($h); break; } } foreach (array('select2', 'selectWoo') as $h) { if (wp_style_is($h, 'registered')) { wp_enqueue_style($h); break; } } wp_enqueue_script('jquery-ui-sortable'); $dmm_js = AIEO_DIR . 'assets/js/dmm.js'; wp_enqueue_script('aieo-dmm', AIEO_URI . 'assets/js/dmm.js', array('jquery', 'jquery-ui-sortable'), file_exists($dmm_js) ? (string) filemtime($dmm_js) : (defined('AIEO_VERSION') ? AIEO_VERSION : '1.0'), true); $dmm_css = AIEO_DIR . 'assets/css/dmm.css'; if (file_exists($dmm_css)) { wp_enqueue_style('aieo-dmm', AIEO_URI . 'assets/css/dmm.css', array(), (string) filemtime($dmm_css)); } if (class_exists('AIEO_DMM_Product_Picker')) { AIEO_DMM_Product_Picker::enqueue_assets(); } wp_localize_script('aieo-dmm', 'aieoDmm', array( 'ajaxurl' => admin_url('admin-ajax.php'), 'nonce' => wp_create_nonce('aieo_dmm_nonce'), 'activeTab' => '', 'i18n' => array( 'pickBrands' => __('Pick brand(s)…', 'ai-eshop-optimizer'), 'pickCategories' => __('Pick categories…', 'ai-eshop-optimizer'), 'pickNeeds' => __('Pick needs…', 'ai-eshop-optimizer'), 'searchByNameSku' => __('Search by name or SKU…', 'ai-eshop-optimizer'), 'picker' => array( 'deleted' => __('deleted', 'ai-eshop-optimizer'), 'draft' => __('draft', 'ai-eshop-optimizer'), 'variations' => __('variations', 'ai-eshop-optimizer'), 'searching' => __('Searching…', 'ai-eshop-optimizer'), 'none' => __('No products found.', 'ai-eshop-optimizer'), 'failed' => __('Search failed.', 'ai-eshop-optimizer'), 'loadingV' => __('Loading variations…', 'ai-eshop-optimizer'), 'noV' => __('No variations.', 'ai-eshop-optimizer'), 'failV' => __('Failed to load variations.', 'ai-eshop-optimizer'), 'cancel' => __('Cancel', 'ai-eshop-optimizer'), 'done' => __('Done', 'ai-eshop-optimizer'), 'ph' => __('Search products by name or SKU…', 'ai-eshop-optimizer'), 'selOne' => __('item selected', 'ai-eshop-optimizer'), 'selMany' => __('items selected', 'ai-eshop-optimizer'), 'titleInc' => __('Choose products to INCLUDE', 'ai-eshop-optimizer'), 'titleExc' => __('Choose products to EXCLUDE', 'ai-eshop-optimizer'), 'remove' => __('Remove', 'ai-eshop-optimizer'), ), ), )); } private static function term_select($name, $css_class, $selected_ids, $describedby = '') { $out = ''; return $out; } public static function register_cpt() { register_post_type(self::POST_TYPE, array( 'labels' => array( 'name' => __('Competitions', 'ai-eshop-optimizer'), 'singular_name' => __('Competition', 'ai-eshop-optimizer'), 'add_new_item' => __('Add Competition', 'ai-eshop-optimizer'), 'edit_item' => __('Edit Competition', 'ai-eshop-optimizer'), 'menu_name' => __('Competitions', 'ai-eshop-optimizer'), ), 'public' => true, 'show_in_rest' => true, 'has_archive' => true, 'menu_icon' => 'dashicons-awards', 'show_in_menu' => false, 'supports' => array('title', 'editor', 'thumbnail', 'excerpt', 'custom-fields', 'page-attributes'), 'rewrite' => array('slug' => 'competitions', 'with_front' => false), )); register_post_meta(self::POST_TYPE, self::META_GF, array( 'type' => 'integer', 'single' => true, 'show_in_rest' => true, 'auth_callback' => static function () { return current_user_can('edit_posts'); }, )); register_post_meta(self::POST_TYPE, self::META_TERMS, array( 'type' => 'integer', 'single' => true, 'show_in_rest' => true, 'auth_callback' => static function () { return current_user_can('edit_posts'); }, )); register_post_meta(self::POST_TYPE, self::META_SHORTDESC, array( 'type' => 'string', 'single' => true, 'show_in_rest' => true, 'auth_callback' => static function () { return current_user_can('edit_posts'); }, )); foreach (array(self::META_IMG_DESK, self::META_IMG_MOB) as $key) { register_post_meta(self::POST_TYPE, $key, array( 'type' => 'integer', 'single' => true, 'show_in_rest' => true, 'auth_callback' => static function () { return current_user_can('edit_posts'); }, )); } foreach (array(self::META_START, self::META_END, self::META_ANNOUNCE_AT) as $key) { register_post_meta(self::POST_TYPE, $key, array( 'type' => 'string', 'single' => true, 'show_in_rest' => true, 'auth_callback' => static function () { return current_user_can('edit_posts'); }, )); } register_post_meta(self::POST_TYPE, self::META_WINNERS_ANNOUNCE, array( 'type' => 'string', 'single' => true, 'show_in_rest' => true, 'sanitize_callback' => static function ($v) { return wp_kses_post((string) $v); }, 'auth_callback' => static function () { return current_user_can('edit_posts'); }, )); foreach (array(self::META_BRANDS, self::META_CATEGORIES, self::META_PRODUCTS) as $key) { register_post_meta(self::POST_TYPE, $key, array( 'type' => 'array', 'single' => true, 'show_in_rest' => array('schema' => array('type' => 'array', 'items' => array('type' => 'integer'))), 'auth_callback' => static function () { return current_user_can('edit_posts'); }, )); } register_post_meta(self::POST_TYPE, self::META_WINNERS_COUNT, array( 'type' => 'integer', 'single' => true, 'show_in_rest' => true, 'auth_callback' => static function () { return current_user_can('edit_posts'); }, )); register_post_meta(self::POST_TYPE, self::META_PRIZE_VALUE, array( 'type' => 'number', 'single' => true, 'show_in_rest' => true, 'auth_callback' => static function () { return current_user_can('edit_posts'); }, )); if (!get_option('aieo_competition_cpt_flushed')) { update_option('aieo_competition_cpt_flushed', 1, false); flush_rewrite_rules(false); } } public static function add_metabox() { add_meta_box( 'aieo-competition-details', __('Competition details', 'ai-eshop-optimizer'), array(__CLASS__, 'render_metabox'), self::POST_TYPE, 'normal', 'high' ); add_meta_box( 'aieo-competition-announcement', __('Winners announcement', 'ai-eshop-optimizer'), array(__CLASS__, 'render_announcement_metabox'), self::POST_TYPE, 'normal', 'high' ); add_meta_box( 'aieo-competition-winners', __('Competition winners', 'ai-eshop-optimizer'), array(__CLASS__, 'render_winners_metabox'), self::POST_TYPE, 'side', 'default' ); add_meta_box( 'aieo-competition-insights', __('Competition insights', 'ai-eshop-optimizer'), array(__CLASS__, 'render_insights_metabox'), self::POST_TYPE, 'side', 'low' ); } public static function render_insights_metabox($post) { if (!class_exists('AIEO_Competition_Analytics')) { echo '

' . esc_html__('Analytics unavailable.', 'ai-eshop-optimizer') . '

'; return; } $i = AIEO_Competition_Analytics::insights($post->ID); $m = $i['metrics']; $cur = function_exists('get_woocommerce_currency_symbol') ? get_woocommerce_currency_symbol() : ''; $entries = (int) $m['entries']; $conv = $entries > 0 ? round(100 * (int) $i['converting_entrants'] / $entries, 1) : 0; echo ''; } public static function render_winners_metabox($post) { if (!class_exists('AIEO_Competition_Participants')) { echo '

' . esc_html__('Participants are unavailable.', 'ai-eshop-optimizer') . '

'; return; } $target = max(1, (int) get_post_meta($post->ID, self::META_WINNERS_COUNT, true)); $m = AIEO_Competition_Participants::metrics($post->ID); $eligible = method_exists('AIEO_Competition_Participants', 'eligible_count') ? AIEO_Competition_Participants::eligible_count($post->ID) : (int) $m['confirmed']; $winners = AIEO_Competition_Participants::get_winners($post->ID); echo '
'; echo '

' . esc_html(sprintf( __('%1$d eligible of %2$d confirmed. Winners exclude anyone who has already won and are weighted by how many competitions each person entered (more entries → more chances).', 'ai-eshop-optimizer'), (int) $eligible, (int) $m['confirmed'] )) . '

'; echo '

'; echo ''; echo '

' . esc_html__('Drawing again replaces the current winners. Set the number of winners in “Competition details”. Write the public message in the “Winners announcement” box.', 'ai-eshop-optimizer') . '

'; echo '
'; } private static function winner_li($w) { $name = trim((string) ($w->first_name ?? '') . ' ' . (string) ($w->last_name ?? '')); return '
  • ' . '' . esc_html($name !== '' ? $name : '—') . ' ' . '' . esc_html((string) $w->email) . ' ' . '' . '
  • '; } public static function render_announcement_metabox($post) { $announce_html = (string) get_post_meta($post->ID, self::META_WINNERS_ANNOUNCE, true); echo '

    ' . esc_html__('Shown on the public competition page once it has ENDED, below the winners list. Congratulate the winners, add photos, prize details, claim/redemption instructions, etc. Draw the winners in the “Competition winners” box (right) first.', 'ai-eshop-optimizer') . '

    '; wp_editor($announce_html, 'aieocompwinnersannouncement', array( 'textarea_name' => 'aieo_comp_winners_announcement', 'textarea_rows' => 8, 'media_buttons' => true, 'teeny' => false, 'tinymce' => true, 'quicktags' => true, )); } public static function render_metabox($post) { wp_nonce_field('aieo_comp_meta', 'aieo_comp_meta_nonce'); $gf_id = (int) get_post_meta($post->ID, self::META_GF, true); $desc = (string) get_post_meta($post->ID, self::META_SHORTDESC, true); $img_d = (int) get_post_meta($post->ID, self::META_IMG_DESK, true); $img_m = (int) get_post_meta($post->ID, self::META_IMG_MOB, true); $start = (string) get_post_meta($post->ID, self::META_START, true); $end = (string) get_post_meta($post->ID, self::META_END, true); $announce = (string) get_post_meta($post->ID, self::META_ANNOUNCE_AT, true); if (class_exists('AIEO_Competition_Entry_Form')) { echo '

    ' . esc_html(AIEO_Competition_Entry_Form::in_use_note($post->ID)) . '

    '; } echo '


    '; echo self::gf_form_select('aieo_comp_gf', $gf_id, 'aieo-comp-gf-help'); echo '
    ' . esc_html__('The Gravity Forms form entrants submit. With none, the competition’s built-in entry form takes the entries.', 'ai-eshop-optimizer') . '

    '; echo '


    '; echo self::terms_page_select('aieo_comp_terms', (int) get_post_meta($post->ID, self::META_TERMS, true), 'aieo-comp-terms-help'); echo '
    ' . esc_html__('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') . '

    '; echo '


    '; echo '

    '; echo '
    '; self::render_image_field('desktop', 'aieo_comp_img_desktop', __('Desktop image', 'ai-eshop-optimizer'), $img_d); self::render_image_field('mobile', 'aieo_comp_img_mobile', __('Mobile image', 'ai-eshop-optimizer'), $img_m); echo '
    '; echo '

    '; echo ''; echo ''; echo ''; echo '

    '; echo '

    ' . esc_html__('Optional. Before the start, visitors see a “coming soon” notice; after the end, an “ended” message. Shop managers always see the live form (preview). Leave start/end empty for an always-open competition. “Winners announced” drives the “winners will be announced on …” message and the Events timeline.', 'ai-eshop-optimizer') . '

    '; $brands = (array) get_post_meta($post->ID, self::META_BRANDS, true); $cats = (array) get_post_meta($post->ID, self::META_CATEGORIES, true); $prods = array_map('intval', (array) get_post_meta($post->ID, self::META_PRODUCTS, true)); $winners = (int) get_post_meta($post->ID, self::META_WINNERS_COUNT, true); $prize = (string) get_post_meta($post->ID, self::META_PRIZE_VALUE, true); $cur = function_exists('get_woocommerce_currency_symbol') ? get_woocommerce_currency_symbol() : ''; echo '
    '; echo '

    ' . esc_html__('Associated brands / categories / products', 'ai-eshop-optimizer') . '
    ' . esc_html__('Optional. Used to monitor which products this competition drives (its “pull”). The same pickers as the discount pages.', 'ai-eshop-optimizer') . '

    '; echo '


    '; echo self::term_select('aieo_comp_brands', 'aieo-dmm-brand-select', $brands, 'aieo-comp-assoc-help'); echo '

    '; echo '


    '; echo self::term_select('aieo_comp_categories', 'aieo-dmm-category-select', $cats, 'aieo-comp-assoc-help'); echo '

    '; if (class_exists('AIEO_DMM_Product_Picker')) { echo '

    '; echo AIEO_DMM_Product_Picker::render_field(array( 'name' => 'aieo_comp_products', 'mode' => 'include', 'selected' => $prods, 'help' => __('Specific products associated with this competition.', 'ai-eshop-optimizer'), 'empty_text' => __('No products associated.', 'ai-eshop-optimizer'), )); } echo '

    '; echo ''; echo ''; echo '

    '; echo '

    ' . esc_html__('e.g. 3 winners, prize value 50 each → total prize pool 150. Use the prize value when the prize is a bag of products rather than one SKU.', 'ai-eshop-optimizer') . '

    '; echo '

    ' . esc_html__('Tip: the public winners announcement (rich text) is in its own “Winners announcement” box, just below this one.', 'ai-eshop-optimizer') . '

    '; if ($post->ID && get_post_status($post) === 'publish') { $links = self::share_links($post->ID); echo '

    ' . esc_html__('Share links', 'ai-eshop-optimizer') . '

    '; } } private static function render_image_field($which, $name, $label, $attachment_id) { $url = $attachment_id ? wp_get_attachment_image_url($attachment_id, 'medium') : ''; echo '
    '; echo '
    '; echo '
    '; echo ''; echo ' '; echo ''; echo '
    '; } public static function save_metabox($post_id, $post) { if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) { return; } if (!isset($_POST['aieo_comp_meta_nonce']) || !wp_verify_nonce(sanitize_text_field(wp_unslash($_POST['aieo_comp_meta_nonce'])), 'aieo_comp_meta')) { return; } if (!current_user_can('edit_post', $post_id)) { return; } $gf = isset($_POST['aieo_comp_gf']) ? (int) $_POST['aieo_comp_gf'] : 0; $desc = isset($_POST['aieo_comp_desc']) ? sanitize_textarea_field(wp_unslash($_POST['aieo_comp_desc'])) : ''; $img_d = isset($_POST['aieo_comp_img_desktop']) ? (int) $_POST['aieo_comp_img_desktop'] : 0; $img_m = isset($_POST['aieo_comp_img_mobile']) ? (int) $_POST['aieo_comp_img_mobile'] : 0; $start = isset($_POST['aieo_comp_start']) ? sanitize_text_field(wp_unslash($_POST['aieo_comp_start'])) : ''; $end = isset($_POST['aieo_comp_end']) ? sanitize_text_field(wp_unslash($_POST['aieo_comp_end'])) : ''; $announce = isset($_POST['aieo_comp_announce']) ? sanitize_text_field(wp_unslash($_POST['aieo_comp_announce'])) : ''; $brands = isset($_POST['aieo_comp_brands']) ? array_map('intval', (array) wp_unslash($_POST['aieo_comp_brands'])) : array(); $categories = isset($_POST['aieo_comp_categories']) ? array_map('intval', (array) wp_unslash($_POST['aieo_comp_categories'])) : array(); $products = class_exists('AIEO_DMM_Product_Picker') ? AIEO_DMM_Product_Picker::roll_up_to_parents(AIEO_DMM_Product_Picker::collect_ids('aieo_comp_products')) : (isset($_POST['aieo_comp_products']) ? array_map('intval', (array) wp_unslash($_POST['aieo_comp_products'])) : array()); $winners = isset($_POST['aieo_comp_winners_count']) ? (int) $_POST['aieo_comp_winners_count'] : 0; $prize = isset($_POST['aieo_comp_prize_value']) ? (float) $_POST['aieo_comp_prize_value'] : 0; self::persist_fields($post_id, $gf, $desc, $img_d, $img_m, $start, $end, $brands, $categories, $products, $winners, $prize, $announce); if (isset($_POST['aieo_comp_terms'])) { self::persist_terms_page($post_id, (int) $_POST['aieo_comp_terms']); } if (isset($_POST['aieo_comp_winners_announcement'])) { update_post_meta($post_id, self::META_WINNERS_ANNOUNCE, wp_kses_post(wp_unslash($_POST['aieo_comp_winners_announcement']))); } } public static function persist_fields($post_id, $gf_id, $short_desc, $img_desktop, $img_mobile, $start = '', $end = '', $brands = array(), $categories = array(), $products = array(), $winners_count = 0, $prize_value = 0, $announce_at = null) { $gf_id = (int) $gf_id; if ($gf_id > 0) { update_post_meta($post_id, self::META_GF, $gf_id); } else { delete_post_meta($post_id, self::META_GF); } update_post_meta($post_id, self::META_SHORTDESC, (string) $short_desc); $img_desktop = (int) $img_desktop; $img_mobile = (int) $img_mobile; if ($img_desktop > 0) { update_post_meta($post_id, self::META_IMG_DESK, $img_desktop); } else { delete_post_meta($post_id, self::META_IMG_DESK); } if ($img_mobile > 0) { update_post_meta($post_id, self::META_IMG_MOB, $img_mobile); } else { delete_post_meta($post_id, self::META_IMG_MOB); } if ($img_desktop > 0) { set_post_thumbnail($post_id, $img_desktop); } $start = self::normalize_dt($start); $end = self::normalize_dt($end); if ($start !== '') { update_post_meta($post_id, self::META_START, $start); } else { delete_post_meta($post_id, self::META_START); } if ($end !== '') { update_post_meta($post_id, self::META_END, $end); } else { delete_post_meta($post_id, self::META_END); } if ($announce_at !== null) { $announce_at = self::normalize_dt($announce_at); if ($announce_at !== '') { update_post_meta($post_id, self::META_ANNOUNCE_AT, $announce_at); } else { delete_post_meta($post_id, self::META_ANNOUNCE_AT); } } $clean = static function ($v) { return array_values(array_unique(array_filter(array_map('intval', (array) $v)))); }; $brands = $clean($brands); $categories = $clean($categories); $products = $clean($products); if ($brands) { update_post_meta($post_id, self::META_BRANDS, $brands); } else { delete_post_meta($post_id, self::META_BRANDS); } if ($categories) { update_post_meta($post_id, self::META_CATEGORIES, $categories); } else { delete_post_meta($post_id, self::META_CATEGORIES); } if ($products) { update_post_meta($post_id, self::META_PRODUCTS, $products); } else { delete_post_meta($post_id, self::META_PRODUCTS); } $winners_count = max(0, (int) $winners_count); $prize_value = max(0, (float) $prize_value); update_post_meta($post_id, self::META_WINNERS_COUNT, $winners_count); if ($prize_value > 0) { update_post_meta($post_id, self::META_PRIZE_VALUE, $prize_value); } else { delete_post_meta($post_id, self::META_PRIZE_VALUE); } } public static function normalize_dt($v) { $v = str_replace('T', ' ', trim((string) $v)); if ($v === '') { return ''; } if (preg_match('/^(\d{4}-\d{2}-\d{2}) (\d{2}:\d{2})(:\d{2})?$/', $v, $m)) { return $m[1] . ' ' . $m[2] . ((isset($m[3]) && $m[3] !== '') ? $m[3] : ':00'); } return ''; } public static function to_input($mysql_dt) { $mysql_dt = trim((string) $mysql_dt); if ($mysql_dt === '') { return ''; } return str_replace(' ', 'T', substr($mysql_dt, 0, 16)); } public static function fmt_dt($mysql_dt) { $mysql_dt = trim((string) $mysql_dt); if ($mysql_dt === '') { return ''; } $ts = strtotime($mysql_dt); return $ts ? date_i18n(get_option('date_format') . ' ' . get_option('time_format'), $ts) : $mysql_dt; } public static function winner_public_name($w) { $fn = trim((string) (is_object($w) && isset($w->first_name) ? $w->first_name : '')); $ln = trim((string) (is_object($w) && isset($w->last_name) ? $w->last_name : '')); if ($fn !== '') { return $ln !== '' ? $fn . ' ' . mb_substr($ln, 0, 1) . '.' : $fn; } return __('A lucky winner', 'ai-eshop-optimizer'); } public static function schedule_state($post_id) { $start = (string) get_post_meta($post_id, self::META_START, true); $end = (string) get_post_meta($post_id, self::META_END, true); $now = current_time('mysql'); if ($start !== '' && $now < $start) { return 'before'; } if ($end !== '' && $now > $end) { return 'ended'; } return 'live'; } public static function gf_form_options($selected_id) { $out = array('0' => __('— none —', 'ai-eshop-optimizer')); if (class_exists('GFAPI')) { foreach ((array) GFAPI::get_forms(true, false) as $f) { if (!isset($f['id'])) { continue; } $id = (int) $f['id']; $out[(string) $id] = '#' . $id . ' — ' . (string) ($f['title'] ?? ('#' . $id)); } } else { $out[(string) (int) $selected_id] = sprintf(__('Gravity Forms inactive (saved form #%d)', 'ai-eshop-optimizer'), (int) $selected_id); } return $out; } public static function terms_page_select($name, $selected_id, $describedby = '') { $html = (string) wp_dropdown_pages(array( 'name' => $name, 'id' => $name, 'selected' => (int) $selected_id, 'show_option_none' => __('— none —', 'ai-eshop-optimizer'), 'option_none_value' => '0', 'echo' => 0, )); if ('' === $html) { $html = ''; } return '' !== $describedby ? (string) preg_replace('/^'; $pick = (string) (int) $selected_id; foreach (self::gf_form_options($selected_id) as $value => $label) { $out .= ''; } $out .= ''; return $out; } public static function share_links($post_id) { $permalink = get_permalink($post_id); if (!$permalink) { return array('facebook' => '', 'tiktok' => '', 'instagram' => ''); } $tag = static function ($src) use ($permalink) { return add_query_arg(array('utm_source' => $src, 'utm_medium' => 'social', 'utm_campaign' => 'competition'), $permalink); }; return array( 'facebook' => 'https://www.facebook.com/sharer/sharer.php?u=' . rawurlencode($tag('facebook')), 'tiktok' => $tag('tiktok'), 'instagram' => $tag('instagram'), ); } }