get_settings(); if ($s['enabled'] !== 'yes') { return; } if ($s['precheck_create_account'] === 'yes') { add_filter('woocommerce_create_account_default_checked', '__return_true'); add_action('wp_enqueue_scripts', array($this, 'enqueue_block_precheck_script'), 30); } if ($s['skip_account_for_existing_email'] === 'yes') { add_action('woocommerce_checkout_process', array($this, 'skip_create_account_for_existing_email')); add_filter('rest_pre_dispatch', array($this, 'block_checkout_skip_create_for_existing_email'), 10, 3); } if ($s['assign_guest_to_existing_user'] === 'yes') { add_action('woocommerce_checkout_update_order_meta', array($this, 'assign_guest_order_to_existing_user')); add_action('woocommerce_store_api_checkout_order_processed', array($this, 'assign_guest_order_to_existing_user_block')); } } public function get_settings() { $stored = get_option(self::OPTION_KEY, array()); if (!is_array($stored)) { $stored = array(); } return array_merge(array( 'enabled' => 'yes', 'precheck_create_account' => 'yes', 'skip_account_for_existing_email' => 'yes', 'assign_guest_to_existing_user' => 'yes', 'auto_login_excluded_roles' => 'administrator,shop_manager,editor,author', ), $stored); } public function ajax_save() { AIEO_DMM::verify_ajax_request(); $patch = array( 'enabled' => !empty($_POST['enabled']) ? 'yes' : 'no', 'precheck_create_account' => !empty($_POST['precheck_create_account']) ? 'yes' : 'no', 'skip_account_for_existing_email' => !empty($_POST['skip_account_for_existing_email']) ? 'yes' : 'no', 'assign_guest_to_existing_user' => !empty($_POST['assign_guest_to_existing_user']) ? 'yes' : 'no', 'auto_login_excluded_roles' => isset($_POST['auto_login_excluded_roles']) ? sanitize_text_field(wp_unslash($_POST['auto_login_excluded_roles'])) : '', ); update_option(self::OPTION_KEY, $patch, false); wp_send_json_success(array('message' => __('Account handling settings saved.', 'ai-eshop-optimizer'))); } public function ajax_save_repeat_order() { AIEO_DMM::verify_ajax_request(); $statuses = isset($_POST['repeat_order_allowed_statuses']) ? array_map('sanitize_text_field', (array) wp_unslash($_POST['repeat_order_allowed_statuses'])) : array(); $all = AIEO_DMM_Settings::get_all(); $all['repeat_order_enabled'] = !empty($_POST['repeat_order_enabled']); $all['repeat_order_show_on_order_list'] = !empty($_POST['repeat_order_show_on_order_list']); $all['repeat_order_create_order_note'] = !empty($_POST['repeat_order_create_order_note']); $all['repeat_order_reactivate_order_again'] = !empty($_POST['repeat_order_reactivate_order_again']); $all['repeat_order_show_reorder_from_column'] = !empty($_POST['repeat_order_show_reorder_from_column']); $all['repeat_order_cart_notice'] = isset($_POST['repeat_order_cart_notice']) ? sanitize_text_field(wp_unslash($_POST['repeat_order_cart_notice'])) : ''; $all['repeat_order_button_label'] = isset($_POST['repeat_order_button_label']) ? sanitize_text_field(wp_unslash($_POST['repeat_order_button_label'])) : ''; $all['repeat_order_allowed_statuses'] = $this->sanitize_ro_statuses($statuses); AIEO_DMM_Settings::replace($all); wp_send_json_success(array('message' => __('Repeat Order settings saved.', 'ai-eshop-optimizer'))); } private function sanitize_ro_statuses($input) { $valid = array('pending', 'processing', 'on-hold', 'completed', 'cancelled', 'refunded', 'failed'); $clean = array(); foreach ((array) $input as $st) { $st = sanitize_key((string) $st); if (in_array($st, $valid, true)) { $clean[] = $st; } } return !empty($clean) ? $clean : array('completed', 'processing'); } public function enqueue_block_precheck_script() { if (!function_exists('is_checkout') || !is_checkout()) { return; } if (function_exists('is_wc_endpoint_url') && is_wc_endpoint_url('order-received')) { return; } if (is_user_logged_in()) { return; } if (class_exists('Automattic\\WooCommerce\\Blocks\\Utils\\CartCheckoutUtils') && !\Automattic\WooCommerce\Blocks\Utils\CartCheckoutUtils::is_checkout_block_default()) { return; } wp_register_script('aieo-account-handling-precheck', '', array('wp-data', 'wc-blocks-checkout'), '1.0', true); wp_enqueue_script('aieo-account-handling-precheck'); wp_add_inline_script('aieo-account-handling-precheck', " (function () { if (typeof wp === 'undefined' || !wp.data) { return; } var STORE = 'wc/store/checkout'; var attempts = 0, MAX = 40, applied = false; function tickIfPossible() { if (applied) { return true; } var dispatch = wp.data.dispatch(STORE); var select = wp.data.select(STORE); if (!dispatch || !select) { return false; } var setter = dispatch.__internalSetShouldCreateAccount || dispatch.setShouldCreateAccount; if (typeof setter !== 'function') { return false; } // Don't override a customer who already toggled it. var current = typeof select.getShouldCreateAccount === 'function' ? select.getShouldCreateAccount() : false; if (current === true) { applied = true; return true; } setter(true); applied = true; return true; } if (!tickIfPossible()) { var iv = setInterval(function () { attempts++; if (tickIfPossible() || attempts >= MAX) { clearInterval(iv); } }, 250); } })(); "); } public function skip_create_account_for_existing_email() { if (!isset($_POST['billing_email'])) { return; } $email = sanitize_email(wp_unslash($_POST['billing_email'])); if ($email && email_exists($email)) { $_POST['createaccount'] = false; } } public function block_checkout_skip_create_for_existing_email($result, $server, $request) { if (is_wp_error($result) || $result instanceof \WP_REST_Response) { return $result; } if (!is_object($request)) { return $result; } $route = (string) $request->get_route(); if (strpos($route, '/wc/store/v1/checkout') !== 0) { return $result; } if ($request->get_method() !== 'POST') { return $result; } $email = ''; $billing = $request->get_param('billing_address'); if (is_array($billing) && !empty($billing['email'])) { $email = sanitize_email((string) $billing['email']); } if ($email === '') { $cust = $request->get_param('customer_email'); if (!empty($cust)) { $email = sanitize_email((string) $cust); } } if ($email === '' || !email_exists($email)) { return $result; } $request->set_param('should_create_account', false); return $result; } public function assign_guest_order_to_existing_user($order_or_id, $auto_login = true) { $order = is_object($order_or_id) ? $order_or_id : wc_get_order($order_or_id); if (!$order || !is_a($order, 'WC_Order')) { return; } if ((int) $order->get_customer_id() > 0) { return; } $email = (string) $order->get_billing_email(); if ($email === '') { return; } $user_id = email_exists($email); if (!$user_id) { return; } $order->set_customer_id($user_id); $order->save(); if (!$auto_login) { return; } $user = get_userdata($user_id); if (!$user) { return; } $s = $this->get_settings(); $excluded = array_filter(array_map('trim', explode(',', (string) $s['auto_login_excluded_roles']))); $roles = (array) $user->roles; if (array_intersect($roles, $excluded)) { return; } wp_set_current_user($user_id); wp_set_auth_cookie($user_id); } public function assign_guest_order_to_existing_user_block($order) { $this->assign_guest_order_to_existing_user($order, false); } public function render_subtab() { $s = $this->get_settings(); $has_states = class_exists('\Rooster\Admin\States'); $ah_form = 'aieo-card-account-handling-overview-form'; list($open, $close) = AIEO_DMM_UI::card(array( 'anchor' => 'account-handling-overview', 'title' => __('Checkout account rules', 'ai-eshop-optimizer'), 'description' => __('Three behaviours that prevent the most common checkout friction: duplicate-account errors, orphaned guest orders, and accidental "create account" un-checks. All three default ON; toggle individually if you need to.', 'ai-eshop-optimizer'), 'footer' => $has_states ? \Rooster\Admin\States::save_button($ah_form) : '', )); echo $open; $switch = static function ($name, $on, $label, $sentence) { $id = 'aieo-ah-' . 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 account-handling tweaks', 'ai-eshop-optimizer'), __('Master toggle for the three behaviours below', 'ai-eshop-optimizer')); $rows .= $switch('precheck_create_account', $s['precheck_create_account'] === 'yes', __('Pre-check "Create an account"', 'ai-eshop-optimizer'), __('Ticked by default on classic checkout', 'ai-eshop-optimizer')); $rows .= $switch('skip_account_for_existing_email', $s['skip_account_for_existing_email'] === 'yes', __('Skip account creation if email already registered', 'ai-eshop-optimizer'), __('Avoids the duplicate-email error mid-checkout', 'ai-eshop-optimizer')); $rows .= $switch('assign_guest_to_existing_user', $s['assign_guest_to_existing_user'] === 'yes', __('Assign guest order to existing user', 'ai-eshop-optimizer'), __('Reassigns + auto-logs-in (except for elevated roles below)', 'ai-eshop-optimizer')); $rows .= \Rooster\Admin\Ui::field_row(array( 'id' => 'aieo-ah-excluded-roles', 'label' => __('Auto-login excluded roles', 'ai-eshop-optimizer'), 'control' => \Rooster\Admin\Ui::input('auto_login_excluded_roles', (string) $s['auto_login_excluded_roles'], array('id' => 'aieo-ah-excluded-roles', 'medium' => true)), 'help' => __('Comma-separated WP role slugs that NEVER get auto-logged-in via guest checkout — security guardrail.', 'ai-eshop-optimizer'), )); echo AIEO_DMM_UI::card_form('aieo_dmm_account_handling_save', 'account_handling', $ah_form, '
' . $rows . '
'); echo $close; $ro = AIEO_DMM_Settings::get_all(); $ro_statuses = isset($ro['repeat_order_allowed_statuses']) && is_array($ro['repeat_order_allowed_statuses']) ? $ro['repeat_order_allowed_statuses'] : array('completed', 'processing'); $ro_form = 'aieo-card-account-handling-repeat-order-settings-form'; list($open, $close) = AIEO_DMM_UI::card(array( 'anchor' => 'account-handling-repeat-order-settings', 'icon' => 'repeat', 'title' => __('"Order again" action', 'ai-eshop-optimizer'), 'description' => __('Controls the "Order again" action shown on the customer\'s My-Account orders list (and the related admin/column extras). Settings are read by the Repeat Order feature.', 'ai-eshop-optimizer'), 'footer' => $has_states ? \Rooster\Admin\States::save_button($ro_form) : '', )); echo $open; $ro_switch = static function ($name, $label, $sentence) use ($ro) { $id = 'aieo-ro-' . str_replace('_', '-', $name); return \Rooster\Admin\Ui::field_row(array( 'id' => $id, 'label' => $label, 'control' => \Rooster\Admin\Ui::toggle($name, !empty($ro[$name]), array('id' => $id, 'hidden' => false, 'label' => $sentence)), )); }; $rows = $ro_switch('repeat_order_enabled', __('Enable Repeat Order', 'ai-eshop-optimizer'), __('Master toggle for the "Order again" feature', 'ai-eshop-optimizer')); $rows .= $ro_switch('repeat_order_show_on_order_list', __('Show on orders list', 'ai-eshop-optimizer'), __('Add the "Order again" action button to the My-Account orders table', 'ai-eshop-optimizer')); $rows .= $ro_switch('repeat_order_create_order_note', __('Create order note', 'ai-eshop-optimizer'), __('Note the new order with a link back to the original order', 'ai-eshop-optimizer')); $rows .= $ro_switch('repeat_order_reactivate_order_again', __('Reactivate "Order again" on order details', 'ai-eshop-optimizer'), __('Show the core WooCommerce "Order again" button under the View-Order details table', 'ai-eshop-optimizer')); $rows .= $ro_switch('repeat_order_show_reorder_from_column', __('Show "Reorder from" column', 'ai-eshop-optimizer'), __('Add a column to the orders list linking each reorder to its original order', 'ai-eshop-optimizer')); $ro_options = array(); if (function_exists('wc_get_order_statuses')) { foreach (wc_get_order_statuses() as $status_key => $status_label) { $ro_options[preg_replace('/^wc-/', '', (string) $status_key)] = $status_label; } } $rows .= \Rooster\Admin\Ui::field_row(array( 'id' => 'aieo-ro-statuses', 'label' => __('Allowed order statuses', 'ai-eshop-optimizer'), 'control' => \Rooster\Admin\Ui::combo('repeat_order_allowed_statuses', $ro_options, array_map('strval', $ro_statuses), array('id' => 'aieo-ro-statuses', 'multiple' => true, 'medium' => true)), 'help' => __('Only orders in these statuses get the "Order again" action. Default: Completed + Processing.', 'ai-eshop-optimizer'), )); $rows .= \Rooster\Admin\Ui::field_row(array( 'id' => 'aieo-ro-button-label', 'label' => __('Button label', 'ai-eshop-optimizer'), 'control' => \Rooster\Admin\Ui::input('repeat_order_button_label', (string) (isset($ro['repeat_order_button_label']) ? $ro['repeat_order_button_label'] : ''), array('id' => 'aieo-ro-button-label', 'medium' => true)), 'help' => __('Custom label for the "Order again" action button. Empty = the default "Order again" label.', 'ai-eshop-optimizer'), )); $rows .= \Rooster\Admin\Ui::field_row(array( 'id' => 'aieo-ro-cart-notice', 'label' => __('Cart notice', 'ai-eshop-optimizer'), 'control' => \Rooster\Admin\Ui::input('repeat_order_cart_notice', (string) (isset($ro['repeat_order_cart_notice']) ? $ro['repeat_order_cart_notice'] : ''), array('id' => 'aieo-ro-cart-notice')), 'help' => __('Optional notice shown in the cart after a customer clicks "Order again". Empty = no notice.', 'ai-eshop-optimizer'), )); echo AIEO_DMM_UI::card_form('aieo_dmm_repeat_order_save', 'account_handling_repeat_order', $ro_form, '
' . $rows . '
'); echo $close; } }