get_settings(); if ($s['enabled'] !== 'yes') { return; } add_filter('body_class', array($this, 'add_body_classes')); add_action('wp_enqueue_scripts', array($this, 'enqueue_assets')); if (trim((string) $s['landing_message']) !== '') { add_action('woocommerce_before_checkout_form', array($this, 'render_landing_message'), 5); add_filter('the_content', array($this, 'prepend_landing_message_to_content'), 5); } add_action('wp_head', array($this, 'inject_layout_config'), 5); if ($s['distraction_free_mode'] === 'ab_test') { add_action('template_redirect', array($this, 'maybe_assign_ab_variant')); add_action('woocommerce_checkout_create_order', array($this, 'stamp_variant_on_classic_order'), 10, 2); add_action('woocommerce_store_api_checkout_order_processed', array($this, 'stamp_variant_on_block_order'), 10, 1); } } public function get_settings() { $stored = get_option(self::OPTION_KEY, array()); if (!is_array($stored)) { $stored = array(); } return array_merge(array( 'enabled' => 'no', 'hide_company_field' => 'no', 'company_in_invoice_fields' => 'no', 'distraction_free_mode' => 'off', 'hide_side_cart_on_checkout' => 'no', 'landing_message' => '', 'landing_message_class' => '', ), $stored); } public function ajax_save() { AIEO_DMM::verify_ajax_request(); $mode_in = isset($_POST['distraction_free_mode']) ? sanitize_key(wp_unslash($_POST['distraction_free_mode'])) : 'off'; if (!in_array($mode_in, array('off', 'on', 'ab_test'), true)) { $mode_in = 'off'; } $patch = array( 'enabled' => !empty($_POST['enabled']) ? 'yes' : 'no', 'hide_company_field' => !empty($_POST['hide_company_field']) ? 'yes' : 'no', 'company_in_invoice_fields' => !empty($_POST['company_in_invoice_fields']) ? 'yes' : 'no', 'distraction_free_mode' => $mode_in, 'hide_side_cart_on_checkout' => !empty($_POST['hide_side_cart_on_checkout']) ? 'yes' : 'no', 'landing_message' => isset($_POST['landing_message']) ? wp_kses_post(wp_unslash($_POST['landing_message'])) : '', 'landing_message_class' => isset($_POST['landing_message_class']) ? sanitize_html_class((string) wp_unslash($_POST['landing_message_class'])) : '', ); update_option(self::OPTION_KEY, $patch, false); wp_send_json_success(array('message' => __('Checkout layout saved.', 'ai-eshop-optimizer'))); } public function add_body_classes($classes) { if (!function_exists('is_checkout') || !is_checkout()) { return $classes; } if (function_exists('is_wc_endpoint_url') && is_wc_endpoint_url('order-received')) { return $classes; } $s = $this->get_settings(); if ($s['company_in_invoice_fields'] === 'yes') { $classes[] = 'aieo-checkout-company-in-invoice'; } elseif ($s['hide_company_field'] === 'yes') { $classes[] = 'aieo-checkout-hide-company'; } if ($s['hide_side_cart_on_checkout'] === 'yes') { $classes[] = 'aieo-checkout-hide-side-cart'; } $variant = $this->effective_variant(); if ($variant === 'clean') { $classes[] = 'aieo-checkout-distraction-free'; } return $classes; } public function effective_variant() { $s = $this->get_settings(); $mode = $s['distraction_free_mode']; if ($mode === 'on') { return 'clean'; } if ($mode === 'off') { return 'control'; } $cookie = isset($_COOKIE[self::COOKIE_NAME]) ? sanitize_key($_COOKIE[self::COOKIE_NAME]) : ''; return ($cookie === 'clean') ? 'clean' : 'control'; } public function maybe_assign_ab_variant() { if (!function_exists('is_checkout') || !is_checkout()) { return; } if (function_exists('is_wc_endpoint_url') && is_wc_endpoint_url('order-received')) { return; } if (headers_sent()) { return; } if (!empty($_COOKIE[self::COOKIE_NAME])) { return; } $variant = (wp_rand(0, 1) === 0) ? 'clean' : 'control'; $params = array( 'expires' => time() + self::COOKIE_TTL, 'path' => COOKIEPATH ? COOKIEPATH : '/', 'domain' => COOKIE_DOMAIN ? COOKIE_DOMAIN : '', 'secure' => is_ssl(), 'httponly' => false, 'samesite' => 'Lax', ); if (PHP_VERSION_ID >= 70300) { setcookie(self::COOKIE_NAME, $variant, $params); } else { setcookie(self::COOKIE_NAME, $variant, $params['expires'], $params['path'], $params['domain'], $params['secure'], $params['httponly']); } $_COOKIE[self::COOKIE_NAME] = $variant; self::log_impression('distraction_free', $variant, array('surface' => 'checkout')); } private static function ensure_impressions_table() { global $wpdb; $table = $wpdb->prefix . 'aieo_ab_impressions'; $current_version = (int) get_option('aieo_ab_impressions_db_version', 0); $target_version = 2; if ($current_version >= $target_version) { return $table; } if (!AIEO_Schema_Lock::acquire('aieo_ab_impressions_db_version')) { return $table; } require_once ABSPATH . 'wp-admin/includes/upgrade.php'; $charset = aieo_get_charset_collate(); $sql = "CREATE TABLE {$table} ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT, test_id VARCHAR(64) NOT NULL DEFAULT 'distraction_free', surface VARCHAR(64) NOT NULL DEFAULT 'checkout', variant VARCHAR(32) NOT NULL, session_hash CHAR(64) NOT NULL, user_id BIGINT UNSIGNED DEFAULT NULL, device VARCHAR(16) DEFAULT NULL, meta LONGTEXT DEFAULT NULL, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id), UNIQUE KEY uniq_session (test_id, session_hash), KEY idx_test_variant_created (test_id, variant, created_at), KEY idx_test_surface (test_id, surface), KEY idx_user (user_id) ) {$charset};"; dbDelta($sql); update_option('aieo_ab_impressions_db_version', $target_version, false); return $table; } public static function log_impression($test_id, $variant, $opts = array()) { $cookie_name = isset($opts['cookie_name']) ? (string) $opts['cookie_name'] : self::COOKIE_NAME; $cookie_val = isset($_COOKIE[$cookie_name]) ? (string) $_COOKIE[$cookie_name] : ''; if ($cookie_val === '') { return; } $payload = array( 'test_id' => sanitize_key((string) $test_id), 'surface' => isset($opts['surface']) ? sanitize_key((string) $opts['surface']) : 'checkout', 'variant' => sanitize_key((string) $variant), 'session_hash' => hash('sha256', $cookie_val . '|' . wp_salt('auth')), 'user_id' => (int) get_current_user_id(), 'device' => AIEO_Device::current(), 'meta' => (isset($opts['meta']) && is_array($opts['meta']) && !empty($opts['meta'])) ? wp_json_encode($opts['meta']) : null, 'created_at' => current_time('mysql', true), ); register_shutdown_function(array(__CLASS__, 'shutdown_write_impression'), $payload); } public static function shutdown_write_impression($payload) { if (function_exists('fastcgi_finish_request')) { @fastcgi_finish_request(); } else { if (!headers_sent()) { @header('Connection: close'); @header('Content-Length: ' . ob_get_length()); } while (ob_get_level() > 0) { @ob_end_flush(); } @flush(); } global $wpdb; $table = self::ensure_impressions_table(); $wpdb->query($wpdb->prepare( "INSERT IGNORE INTO {$table} (test_id, surface, variant, session_hash, user_id, device, meta, created_at) VALUES (%s, %s, %s, %s, %d, %s, %s, %s)", $payload['test_id'], $payload['surface'], $payload['variant'], $payload['session_hash'], $payload['user_id'], $payload['device'], $payload['meta'], $payload['created_at'] )); } public function enqueue_assets() { if (!function_exists('is_checkout') || !is_checkout()) { return; } $rel = 'assets/css/aieo-checkout-layout.css'; $abs = AIEO_DIR . $rel; if (!file_exists($abs)) { return; } wp_enqueue_style( 'aieo-checkout-layout', AIEO_URI . $rel, array(), (string) filemtime($abs) ); } public function inject_layout_config() { if (!function_exists('is_checkout') || !is_checkout()) { return; } $s = $this->get_settings(); $cfg = array( 'companyConditional' => ($s['company_in_invoice_fields'] === 'yes'), 'variant' => $this->effective_variant(), ); echo "\n"; } public function render_landing_message() { $s = $this->get_settings(); $msg = trim((string) $s['landing_message']); if ($msg === '') { return; } echo $this->landing_message_html($msg); } public function prepend_landing_message_to_content($content) { if (!function_exists('is_checkout') || !is_checkout()) { return $content; } if (function_exists('is_wc_endpoint_url') && is_wc_endpoint_url('order-received')) { return $content; } static $rendered = false; if ($rendered) { return $content; } $rendered = true; $s = $this->get_settings(); $msg = trim((string) $s['landing_message']); if ($msg === '') { return $content; } return $this->landing_message_html($msg) . $content; } private function landing_message_html($msg) { $s = $this->get_settings(); $extra_class = isset($s['landing_message_class']) ? trim((string) $s['landing_message_class']) : ''; $class_attr = 'aieo-checkout-landing-message alignwide'; if ($extra_class !== '') { $class_attr .= ' ' . $extra_class; } return '
' . wp_kses_post($msg) . '
'; } public function stamp_variant_on_classic_order($order, $data) { if (!is_a($order, 'WC_Order')) { return; } $order->update_meta_data('_aieo_checkout_variant', $this->effective_variant()); } public function stamp_variant_on_block_order($order) { if (!is_a($order, 'WC_Order')) { return; } $order->update_meta_data('_aieo_checkout_variant', $this->effective_variant()); $order->save(); } public function get_ab_results() { global $wpdb; $rows = $wpdb->get_results( "SELECT meta_value AS variant, COUNT(*) AS orders FROM {$wpdb->postmeta} pm INNER JOIN {$wpdb->posts} p ON p.ID = pm.post_id WHERE pm.meta_key = '_aieo_checkout_variant' AND p.post_status NOT IN ('trash','auto-draft') AND p.post_type IN ('shop_order','shop_order_placehold') GROUP BY meta_value", ARRAY_A ); if ((!is_array($rows) || empty($rows)) && class_exists('Automattic\WooCommerce\Utilities\OrderUtil') && \Automattic\WooCommerce\Utilities\OrderUtil::custom_orders_table_usage_is_enabled()) { $orders_meta_table = $wpdb->prefix . 'wc_orders_meta'; $orders_table = $wpdb->prefix . 'wc_orders'; $rows = $wpdb->get_results( "SELECT om.meta_value AS variant, COUNT(*) AS orders FROM {$orders_meta_table} om INNER JOIN {$orders_table} o ON o.id = om.order_id WHERE om.meta_key = '_aieo_checkout_variant' AND o.status NOT IN ('trash','auto-draft') GROUP BY om.meta_value", ARRAY_A ); } $orders = array('clean' => 0, 'control' => 0); foreach ((array) $rows as $r) { $v = isset($r['variant']) ? (string) $r['variant'] : ''; if ($v === 'clean' || $v === 'control') { $orders[$v] = (int) $r['orders']; } } $imp_table = self::ensure_impressions_table(); $imp_rows = $wpdb->get_results( $wpdb->prepare( "SELECT variant, COUNT(*) AS impressions FROM {$imp_table} WHERE test_id = %s GROUP BY variant", 'distraction_free' ), ARRAY_A ); $imp_clean = 0; $imp_control = 0; foreach ((array) $imp_rows as $r) { $v = isset($r['variant']) ? (string) $r['variant'] : ''; if ($v === 'clean') { $imp_clean = (int) $r['impressions']; } if ($v === 'control') { $imp_control = (int) $r['impressions']; } } return array( 'clean' => array( 'impressions' => $imp_clean, 'orders' => $orders['clean'], 'rate' => $imp_clean > 0 ? ($orders['clean'] / $imp_clean) : 0.0, ), 'control' => array( 'impressions' => $imp_control, 'orders' => $orders['control'], 'rate' => $imp_control > 0 ? ($orders['control'] / $imp_control) : 0.0, ), ); } public function ajax_reset_ab_counters() { AIEO_DMM::verify_ajax_request(); global $wpdb; $table = self::ensure_impressions_table(); $wpdb->query($wpdb->prepare( "DELETE FROM {$table} WHERE test_id = %s", 'distraction_free' )); wp_send_json_success(array('message' => __('A/B impression counters reset for the distraction-free test. Order history kept.', 'ai-eshop-optimizer'))); } public function render_subtab() { $s = $this->get_settings(); $nonce = wp_create_nonce(AIEO_DMM::NONCE_ACTION); $ajax = admin_url('admin-ajax.php'); wp_enqueue_editor(); $form_id = 'aieo-card-checkout-layout-checkout-layout-and-fields-form'; list($open, $close) = AIEO_DMM_UI::card(array( 'anchor' => 'checkout-layout-checkout-layout-and-fields', 'title' => __('Checkout layout and fields', 'ai-eshop-optimizer'), 'description' => __('Distraction-free checkout + company-field controls + side-cart hiding. Default OFF — opt in deliberately.', 'ai-eshop-optimizer'), 'footer' => class_exists('\Rooster\Admin\States') ? \Rooster\Admin\States::save_button($form_id) : '', )); echo $open; $switch = static function ($name, $on, $label, $sentence) { $id = 'aieo-cl-' . str_replace('_', '-', $name); return \Rooster\Admin\Ui::field_row(array( 'id' => $id, 'label' => $label, 'control' => \Rooster\Admin\Ui::toggle($name, (bool) $on, array('id' => $id, 'hidden' => false, 'label' => $sentence)), )); }; $rows = $switch('enabled', $s['enabled'] === 'yes', __('Enable layout tweaks', 'ai-eshop-optimizer'), __('Master toggle', 'ai-eshop-optimizer')); $rows .= $switch('hide_company_field', $s['hide_company_field'] === 'yes', __('Hide company field', 'ai-eshop-optimizer'), __('Never show the company field — fewer fields, higher mobile completion', 'ai-eshop-optimizer')); $rows .= $switch('company_in_invoice_fields', $s['company_in_invoice_fields'] === 'yes', __('Company field with invoice', 'ai-eshop-optimizer'), __('Show the company field only when "I want an invoice" is ticked. Recommended: ON if you turn the previous toggle OFF.', 'ai-eshop-optimizer')); $rows .= $switch('hide_side_cart_on_checkout', $s['hide_side_cart_on_checkout'] === 'yes', __('Hide side cart on /checkout', 'ai-eshop-optimizer'), __('Side-cart trigger reappears automatically on the order-received page. On mobile the trigger doubles as the burger menu, so hiding it removes the strongest visual exit point during checkout.', 'ai-eshop-optimizer')); $rows .= \Rooster\Admin\Ui::field_row(array( 'id' => 'aieo-cl-mode', 'label' => __('Distraction-free layout', 'ai-eshop-optimizer'), 'control' => \Rooster\Admin\Ui::select('distraction_free_mode', array( 'off' => __('Off — keep the full header & footer', 'ai-eshop-optimizer'), 'on' => __('On — minimal layout for everyone (logo + page title + checkout + tracking-only footer)', 'ai-eshop-optimizer'), 'ab_test' => __('A/B test — 50/50 random per session, sticky for 30 days', 'ai-eshop-optimizer'), ), (string) $s['distraction_free_mode'], array('id' => 'aieo-cl-mode')), 'help' => __('A/B mode stamps the variant on every resulting order as `_aieo_checkout_variant` meta (values: clean, control). Pull the conversion split via SQL: SELECT meta_value, COUNT(*) FROM wp_postmeta WHERE meta_key="_aieo_checkout_variant" GROUP BY meta_value.', 'ai-eshop-optimizer'), )); ob_start(); wp_editor((string) $s['landing_message'], 'aieo_dmm_cl_landing_message', array( 'textarea_name' => 'landing_message', 'textarea_rows' => 6, 'media_buttons' => false, 'tinymce' => array( 'toolbar1' => 'bold,italic,underline,bullist,numlist,link,unlink,alignleft,aligncenter,alignright,formatselect,removeformat', 'toolbar2' => '', 'block_formats' => 'Paragraph=p;Heading=h3;Sub-heading=h4', ), 'quicktags' => true, )); $rows .= \Rooster\Admin\Ui::field_row(array( 'id' => 'aieo_dmm_cl_landing_message', 'label' => __('Landing message', 'ai-eshop-optimizer'), 'control' => (string) ob_get_clean(), 'help' => __('Rendered above the checkout form, BEFORE the customer commits to the purchase. Use the toolbar for bold / italic / alignment / lists / links. Default tint background #fcf0f1 + border #f46767 — override via the custom class below or theme CSS.', 'ai-eshop-optimizer'), 'help_id' => 'aieo-cl-message-help', )); $rows .= \Rooster\Admin\Ui::field_row(array( 'id' => 'aieo-cl-class', 'label' => __('Landing message custom class', 'ai-eshop-optimizer'), 'control' => \Rooster\Admin\Ui::input('landing_message_class', (string) $s['landing_message_class'], array('id' => 'aieo-cl-class', 'medium' => true, 'code' => true, 'placeholder' => 'my-promo-banner')), 'help' => __('Optional CSS class added to the message wrapper alongside `.aieo-checkout-landing-message`. Lets you style this specific message via theme CSS without touching the plugin defaults.', 'ai-eshop-optimizer'), )); ob_start(); echo '
' . $rows . '
'; echo ''; echo AIEO_DMM_UI::card_form('aieo_dmm_checkout_layout_save', $this->get_id(), $form_id, (string) ob_get_clean()); echo $close; $r = $this->get_ab_results(); $imp_total = $r['clean']['impressions'] + $r['control']['impressions']; $ord_total = $r['clean']['orders'] + $r['control']['orders']; $has_history = ($imp_total > 0 || $ord_total > 0); if ($s['distraction_free_mode'] === 'ab_test' || $has_history) { list($open, $close) = AIEO_DMM_UI::card(array( 'anchor' => 'checkout-layout-a-b-test-results', 'icon' => 'scale', 'title' => __('A/B test results', 'ai-eshop-optimizer'), 'description' => __('Conversion rate per variant. Impressions = unique sessions assigned to that variant (deduped via the 30-day cookie). Orders = placed orders stamped with the variant meta. Lift = relative improvement of clean over control.', 'ai-eshop-optimizer'), )); echo $open; $clean_rate = $r['clean']['rate']; $control_rate = $r['control']['rate']; $lift_pct = ''; if ($control_rate > 0) { $lift_pct = number_format((($clean_rate - $control_rate) / $control_rate) * 100, 1) . '%'; } $min_orders = min($r['clean']['orders'], $r['control']['orders']); if ($min_orders >= 400) { $conf_label = __('High', 'ai-eshop-optimizer'); $conf_tone = 'success'; } elseif ($min_orders >= 100) { $conf_label = __('Moderate', 'ai-eshop-optimizer'); $conf_tone = 'warning'; } elseif ($min_orders >= 30) { $conf_label = __('Noisy', 'ai-eshop-optimizer'); $conf_tone = 'destructive'; } else { $conf_label = __('Insufficient sample', 'ai-eshop-optimizer'); $conf_tone = 'mono'; } $cl_variant = static function ($label, $code) { return array('title' => $label, 'sub' => '_aieo_checkout_variant=' . $code); }; $cl_table = \Rooster\Admin\Ui::table( array( array('key' => 'variant', 'label' => __('Variant', 'ai-eshop-optimizer'), 'type' => 'text'), array('key' => 'impressions', 'label' => __('Impressions', 'ai-eshop-optimizer'), 'type' => 'num'), array('key' => 'orders', 'label' => __('Orders', 'ai-eshop-optimizer'), 'type' => 'num'), array('key' => 'conversion_rate', 'label' => __('Conversion rate', 'ai-eshop-optimizer'), 'type' => 'num'), array('key' => 'lift_vs_control', 'label' => __('Lift vs. control', 'ai-eshop-optimizer'), 'type' => 'num'), ), array( array( 'variant' => $cl_variant(__('Clean', 'ai-eshop-optimizer'), 'clean'), 'impressions' => number_format_i18n($r['clean']['impressions']), 'orders' => number_format_i18n($r['clean']['orders']), 'conversion_rate' => number_format($clean_rate * 100, 2) . '%', 'lift_vs_control' => ($lift_pct !== '') ? array('html' => \Rooster\Admin\Ui::badge($lift_pct, ($clean_rate >= $control_rate) ? 'success' : 'destructive')) : '—', ), array( 'variant' => $cl_variant(__('Control', 'ai-eshop-optimizer'), 'control'), 'impressions' => number_format_i18n($r['control']['impressions']), 'orders' => number_format_i18n($r['control']['orders']), 'conversion_rate' => number_format($control_rate * 100, 2) . '%', 'lift_vs_control' => __('baseline', 'ai-eshop-optimizer'), ), ), array('id' => 'aieo-checkout-layout-results', 'caption' => esc_html__('Layout test results by variant', 'ai-eshop-optimizer')) ); echo '

'; echo '' . esc_html__('Sample-size confidence:', 'ai-eshop-optimizer') . ' '; echo \Rooster\Admin\Ui::badge($conf_label, $conf_tone); echo ' ' . esc_html(sprintf(__('(min orders per arm: %d)', 'ai-eshop-optimizer'), $min_orders)) . ''; echo '

'; echo \Rooster\Admin\Guidance::text(__('Rule of thumb: 30+ orders per arm before drawing any conclusion. 100+ for moderate confidence. 400+ for high. For statistical significance use a proper z-test on a calculator.', 'ai-eshop-optimizer'), 'guidance', array('about' => __('Sample-size confidence', 'ai-eshop-optimizer'), 'question' => __('How sure is it?', 'ai-eshop-optimizer'))); echo $cl_table; $reset_dialog = \Rooster\Admin\States::confirm( 'aieo-cl-reset-confirm', 'reset', __('Reset both impression counters?', 'ai-eshop-optimizer'), '

' . sprintf(esc_html(_n('%s impression counted so far is cleared; the order-history attribution stays.', '%s impressions counted so far are cleared; the order-history attribution stays.', $imp_total, 'ai-eshop-optimizer')), esc_html(number_format_i18n($imp_total))) . '

', array('confirm' => __('Reset counters', 'ai-eshop-optimizer')) ); echo $reset_dialog; echo '

'; echo \Rooster\Admin\Ui::button(__('Reset impression counters', 'ai-eshop-optimizer'), array('attrs' => array('id' => 'aieo-dmm-cl-reset-ab') + \Rooster\Admin\States::confirm_attrs('aieo-cl-reset-confirm'))); echo ' '; echo '

'; echo \Rooster\Admin\Guidance::text(__('Clears the impression counters but KEEPS the order-history attribution. Useful when starting a fresh test cycle.', 'ai-eshop-optimizer'), 'guidance', array('about' => __('Reset impression counters', 'ai-eshop-optimizer'), 'question' => __('What does a reset clear?', 'ai-eshop-optimizer'))); echo ''; echo $close; } } }