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;
}
}
}