get_settings(); if ($s['enabled'] !== 'yes') { return; } add_action('wp', array($this, 'set_customer_default_location'), 5); add_action('woocommerce_init', array($this, 'set_customer_default_location_ajax'), 10); add_action('woocommerce_load_cart_from_session', array($this, 'set_customer_default_location'), 5); add_filter('rest_pre_dispatch', array($this, 'maybe_seed_customer_for_storeapi'), 5, 3); add_filter('woocommerce_cart_shipping_packages', array($this, 'set_default_for_empty_address'), 10); add_filter('woocommerce_cart_shipping_packages', array($this, 'fallback_for_unmatched_postcode'), 20); add_filter('option_woocommerce_shipping_cost_requires_address', array($this, 'always_show_shipping')); add_filter('woocommerce_checkout_get_value', array($this, 'hide_default_postcode_field_value'), 10, 2); add_filter('woocommerce_checkout_fields', array($this, 'clear_postcode_defaults_fields'), 999); add_action('woocommerce_before_checkout_form', array($this, 'clear_default_from_customer_display')); add_action('wp_footer', array($this, 'clear_default_postcode_js')); add_filter('woocommerce_package_rates', array($this, 'filter_shipping_rate_labels'), 10000, 2); add_filter('woocommerce_cart_shipping_method_full_label', array($this, 'strip_duplicate_prices_from_label'), 200, 2); add_filter('woocommerce_cart_shipping_method_full_label', array($this, 'filter_shipping_method_label'), 100, 2); } public function get_settings($raw = false) { $stored = get_option(self::OPTION_KEY, array()); if (!is_array($stored)) { $stored = array(); } $legacy = get_option('om_checkout_settings', array()); if (!is_array($legacy)) { $legacy = array(); } return $raw ? (array_merge(array( 'enabled' => 'no', 'default_postcode' => isset($legacy['default_postcode']) ? (string) $legacy['default_postcode'] : '10431', 'default_city' => isset($legacy['default_city']) ? (string) $legacy['default_city'] : 'Athens', 'shipping_display_from' => isset($legacy['shipping_display_from']) ? (string) $legacy['shipping_display_from'] : 'yes', 'from_label' => '', ), $stored)) : self::fill_i18n_defaults(array_merge(array( 'enabled' => 'no', 'default_postcode' => isset($legacy['default_postcode']) ? (string) $legacy['default_postcode'] : '10431', 'default_city' => isset($legacy['default_city']) ? (string) $legacy['default_city'] : 'Athens', 'shipping_display_from' => isset($legacy['shipping_display_from']) ? (string) $legacy['shipping_display_from'] : 'yes', 'from_label' => '', ), $stored)); } private static function i18n_labels() { return array( 'from_label' => __('From', 'ai-eshop-optimizer'), ); } private static function fill_i18n_defaults(array $s) { if (!did_action('init')) { return $s; } foreach (self::i18n_labels() as $k => $v) { if (!isset($s[$k]) || '' === trim((string) $s[$k])) { $s[$k] = $v; } } return $s; } public function ajax_save() { AIEO_DMM::verify_ajax_request(); update_option(self::OPTION_KEY, array( 'enabled' => !empty($_POST['enabled']) ? 'yes' : 'no', 'default_postcode' => isset($_POST['default_postcode']) ? sanitize_text_field(wp_unslash($_POST['default_postcode'])) : '10431', 'default_city' => isset($_POST['default_city']) ? sanitize_text_field(wp_unslash($_POST['default_city'])) : 'Athens', 'shipping_display_from' => !empty($_POST['shipping_display_from']) ? 'yes' : 'no', 'from_label' => isset($_POST['from_label']) ? sanitize_text_field(wp_unslash($_POST['from_label'])) : '', ), false); wp_send_json_success(array('message' => __('Smart shipping label settings saved.', 'ai-eshop-optimizer'))); } private function get_default_postcode() { $s = $this->get_settings(); return (string) $s['default_postcode']; } private function get_default_city() { $s = $this->get_settings(); return (string) $s['default_city']; } public static function normalize_postcode($postcode) { return str_replace(' ', '', trim((string) $postcode)); } private function is_checkout_context() { if (defined('REST_REQUEST') && REST_REQUEST) { $route = ''; if (function_exists('rest_get_url_prefix')) { $uri = isset($_SERVER['REQUEST_URI']) ? (string) sanitize_text_field( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : ''; $route = (string) wp_parse_url($uri, PHP_URL_PATH); } if (strpos($route, '/wc/store/v1/cart') !== false || strpos($route, '/wc/store/v1/checkout') !== false || strpos($route, '/wc/store/cart') !== false || strpos($route, '/wc/store/checkout') !== false) { return true; } return false; } if (!function_exists('is_checkout') || !function_exists('is_cart')) { return false; } if (is_checkout() || is_cart()) { return true; } if (wp_doing_ajax()) { $wc_ajax = isset($_GET['wc-ajax']) ? sanitize_text_field(wp_unslash($_GET['wc-ajax'])) : ''; if (in_array($wc_ajax, array('update_order_review', 'checkout', 'get_refreshed_fragments'), true)) { return true; } $action = isset($_POST['action']) ? sanitize_text_field(wp_unslash($_POST['action'])) : ''; if (strpos($action, 'woocommerce_checkout') !== false || strpos($action, 'wc_') !== false) { return true; } } return false; } private function get_table_rate_minimum($instance_id) { global $wpdb; $table = $wpdb->prefix . 'woocommerce_shipping_table_rates'; $exists = (string) $wpdb->get_var($wpdb->prepare('SHOW TABLES LIKE %s', $table)) === $table; if (!$exists) { return false; } $min = $wpdb->get_var($wpdb->prepare( "SELECT MIN(CAST(rate_cost AS DECIMAL(10,2))) FROM {$table} WHERE shipping_method_id = %d AND rate_cost > 0", (int) $instance_id )); return $min !== null ? (float) $min : false; } private function get_all_table_rate_minimums() { global $wpdb; static $cache = null; if ($cache !== null) { return $cache; } $cache = array(); $rows = $wpdb->get_results( "SELECT zm.instance_id, zm.zone_id FROM {$wpdb->prefix}woocommerce_shipping_zone_methods zm WHERE zm.method_id = 'table_rate' AND zm.is_enabled = 1" ); if (empty($rows)) { return $cache; } foreach ($rows as $r) { $instance_id = (int) $r->instance_id; if (isset($cache[$instance_id])) { continue; } $opt = get_option('woocommerce_table_rate_' . $instance_id . '_settings', array()); $title = (is_array($opt) && !empty($opt['title'])) ? (string) $opt['title'] : __('Table Rate', 'ai-eshop-optimizer'); $min = $this->get_table_rate_minimum($instance_id); if ($min !== false) { $cache[$instance_id] = array( 'title' => $title, 'min_cost' => $min, 'rate_id' => 'table_rate:' . $instance_id, 'zone_id' => (int) $r->zone_id, ); } } return $cache; } private function cart_qualifies_for_free_shipping() { static $q = null; if ($q !== null) { return $q; } $q = false; if (!function_exists('WC') || !WC() || !WC()->cart) { return false; } $subtotal = (float) WC()->cart->get_subtotal(); $zones = WC_Data_Store::load('shipping-zone')->get_zones(); foreach ($zones as $z) { $zone = new WC_Shipping_Zone($z); foreach ($zone->get_shipping_methods(true) as $m) { if ($m->id !== 'free_shipping') { continue; } $min = isset($m->min_amount) ? (float) $m->min_amount : 0; if ($min === 0.0 || $subtotal >= $min) { return $q = true; } } } $z0 = new WC_Shipping_Zone(0); foreach ($z0->get_shipping_methods(true) as $m) { if ($m->id !== 'free_shipping') { continue; } $min = isset($m->min_amount) ? (float) $m->min_amount : 0; if ($min === 0.0 || $subtotal >= $min) { return $q = true; } } return $q; } private function postcode_matches_zone($postcode) { if (empty($postcode)) { return false; } static $cache = array(); if (isset($cache[$postcode])) { return $cache[$postcode]; } if (!class_exists('WC_Shipping_Zones')) { return $cache[$postcode] = true; } $zone = WC_Shipping_Zones::get_zone_matching_package(array( 'destination' => array('country' => 'GR', 'state' => '', 'postcode' => $postcode, 'city' => ''), )); if (!$zone) { return $cache[$postcode] = false; } foreach ($zone->get_shipping_methods(true) as $m) { if (in_array($m->id, array('table_rate', 'flat_rate', 'box_now_delivery'), true) || strpos($m->id, 'box_now') !== false || strpos($m->id, 'courier') !== false || strpos($m->id, 'delivery') !== false) { return $cache[$postcode] = true; } } return $cache[$postcode] = false; } public function set_customer_default_location() { if (is_admin() && !wp_doing_ajax() && !(defined('REST_REQUEST') && REST_REQUEST)) { return; } if (!function_exists('WC') || !WC()->customer) { return; } $is_rest_storeapi = (defined('REST_REQUEST') && REST_REQUEST); if (!$is_rest_storeapi && function_exists('is_checkout') && function_exists('is_cart') && !is_checkout() && !is_cart() && !wp_doing_ajax()) { return; } $cur_pc = WC()->customer->get_shipping_postcode(); $cur_co = WC()->customer->get_shipping_country(); $def_pc = $this->get_default_postcode(); $def_ci = $this->get_default_city(); if ($cur_pc === '' && $cur_co === '') { WC()->customer->set_shipping_country('GR'); WC()->customer->set_shipping_postcode($def_pc); WC()->customer->set_shipping_city($def_ci); $b_pc = WC()->customer->get_billing_postcode(); $b_co = WC()->customer->get_billing_country(); if ($b_pc === '' && $b_co === '') { WC()->customer->set_billing_country('GR'); WC()->customer->set_billing_postcode($def_pc); WC()->customer->set_billing_city($def_ci); } } } public function maybe_seed_customer_for_storeapi($result, $server, $request) { if ($result !== null) { return $result; } $route = $request instanceof WP_REST_Request ? (string) $request->get_route() : ''; if (strpos($route, '/wc/store/') === false) { return $result; } $this->set_customer_default_location(); return $result; } public function set_customer_default_location_ajax() { if (!wp_doing_ajax()) { return; } $wc_ajax = isset($_GET['wc-ajax']) ? sanitize_text_field(wp_unslash($_GET['wc-ajax'])) : ''; $action = isset($_REQUEST['action']) ? sanitize_text_field(wp_unslash($_REQUEST['action'])) : ''; $hits = in_array($wc_ajax, array('update_order_review', 'checkout', 'wc_update_order_review'), true) || in_array($action, array('update_order_review', 'checkout', 'wc_update_order_review'), true) || strpos($action, 'woocommerce') !== false; if ($hits) { $this->set_customer_default_location(); } } public function set_default_for_empty_address($packages) { $def_pc = $this->get_default_postcode(); $def_ci = $this->get_default_city(); if (function_exists('WC') && WC() && WC()->customer) { $cur_pc = WC()->customer->get_shipping_postcode(); $cur_co = WC()->customer->get_shipping_country(); if ($cur_pc === '' && ($cur_co === '' || $cur_co === 'GR')) { WC()->customer->set_shipping_country('GR'); WC()->customer->set_shipping_postcode($def_pc); if (method_exists(WC()->customer, 'set_shipping_city')) { WC()->customer->set_shipping_city($def_ci); } $b_pc = WC()->customer->get_billing_postcode(); $b_co = WC()->customer->get_billing_country(); if ($b_pc === '' && ($b_co === '' || $b_co === 'GR')) { WC()->customer->set_billing_country('GR'); WC()->customer->set_billing_postcode($def_pc); if (method_exists(WC()->customer, 'set_billing_city')) { WC()->customer->set_billing_city($def_ci); } } } } foreach ($packages as $key => $package) { $pc = isset($package['destination']['postcode']) ? trim((string) $package['destination']['postcode']) : ''; $co = isset($package['destination']['country']) ? (string) $package['destination']['country'] : ''; if ($co === 'GR' && $pc !== '') { $packages[$key]['destination']['postcode'] = self::normalize_postcode($pc); } if ($pc === '' && $co === '') { $packages[$key]['destination']['country'] = 'GR'; $packages[$key]['destination']['postcode'] = $def_pc; $packages[$key]['destination']['city'] = $def_ci; } } return $packages; } public function fallback_for_unmatched_postcode($packages) { $def_pc = $this->get_default_postcode(); $def_ci = $this->get_default_city(); foreach ($packages as $key => $package) { $pc = isset($package['destination']['postcode']) ? trim((string) $package['destination']['postcode']) : ''; $co = isset($package['destination']['country']) ? (string) $package['destination']['country'] : ''; if ($co !== 'GR' || $pc === '' || $pc === $def_pc) { continue; } if (!$this->postcode_matches_zone($pc)) { $packages[$key]['om_original_postcode'] = $pc; $packages[$key]['destination']['postcode'] = $def_pc; $packages[$key]['destination']['city'] = $def_ci; } } return $packages; } public function filter_shipping_rate_labels($rates, $package) { if (!$this->is_checkout_context()) { return $rates; } $s = $this->get_settings(); if ($s['shipping_display_from'] !== 'yes') { return $rates; } $tr_min = $this->get_all_table_rate_minimums(); $def_pc = $this->get_default_postcode(); $def_pc_norm = self::normalize_postcode($def_pc); $user_entered_postcode = false; if (!is_user_logged_in()) { if (!empty($_POST)) { foreach (array('billing_postcode','shipping_postcode','postcode','s_postcode') as $f) { if (!empty($_POST[$f])) { $v = self::normalize_postcode((string) wp_unslash($_POST[$f])); if (strlen($v) >= 4 && $v !== $def_pc_norm) { $user_entered_postcode = true; break; } } } } } elseif (function_exists('WC') && WC() && WC()->customer) { $sp = self::normalize_postcode((string) WC()->customer->get_shipping_postcode()); if (strlen($sp) >= 4 && $sp !== $def_pc_norm) { $user_entered_postcode = true; } else { $bp = self::normalize_postcode((string) WC()->customer->get_billing_postcode()); if (strlen($bp) >= 4 && $bp !== $def_pc_norm) { $user_entered_postcode = true; } } } $qualifies_free = $this->cart_qualifies_for_free_shipping(); $show_from = !$user_entered_postcode && !$qualifies_free; $hide_prices = $qualifies_free; $from_label = (string) $s['from_label']; foreach ($rates as $rate_id => $rate) { if (strpos($rate_id, 'table_rate:') === 0) { $parts = explode(':', $rate_id); $instance_id = isset($parts[1]) ? (int) $parts[1] : 0; if (isset($tr_min[$instance_id])) { $title = (string) $tr_min[$instance_id]['title']; if ($show_from) { $min = $tr_min[$instance_id]['min_cost']; $new_label = sprintf('%s (%s %s)', $title, $from_label, html_entity_decode(wp_strip_all_tags(wc_price($min)), ENT_QUOTES | ENT_HTML5, 'UTF-8')); $rate->set_label($new_label); $rate->label = $new_label; $rate->cost = 0; $rate->taxes = array(); } elseif ($hide_prices) { $rate->set_label($title); $rate->label = $title; $rate->cost = 0; $rate->taxes = array(); } else { $rate->set_label($title); $rate->label = $title; } } } if (strpos($rate_id, 'box_now_delivery') !== false) { if ($show_from) { $cost = (float) $rate->get_cost(); $new_label = sprintf(__('Box Now Delivery (%1$s %2$s for all of Greece)', 'ai-eshop-optimizer'), $from_label, html_entity_decode(wp_strip_all_tags(wc_price($cost)), ENT_QUOTES | ENT_HTML5, 'UTF-8')); $rate->set_label($new_label); $rate->cost = 0; $rate->taxes = array(); } elseif ($hide_prices) { $rate->set_label('Box Now Delivery'); $rate->cost = 0; $rate->taxes = array(); } } } if ($hide_prices) { foreach ($rates as $rate_id => $rate) { if (strpos($rate_id, 'free_shipping') !== false) { unset($rates[$rate_id]); } } } $seen = array(); $out = array(); foreach ($rates as $rate_id => $rate) { if (strpos($rate_id, 'local_pickup') === false && strpos($rate_id, 'pickup_location') === false) { $out[$rate_id] = $rate; continue; } $label = $rate->get_label(); if (isset($seen[$label])) { continue; } $seen[$label] = true; $out[$rate_id] = $rate; } return $out; } public function strip_duplicate_prices_from_label($label, $method) { $rate_id = $method->get_id(); if (strpos($rate_id, 'table_rate') !== false && strpos($label, 'Από') === false) { $rate_label = $method->get_label(); if (strpos($rate_label, 'Από') !== false) { $label = $rate_label; } } if (strpos($label, 'Από') !== false || strpos($label, 'από') !== false) { $label = preg_replace('/\)\s*:.*$/s', ')', $label); } return trim((string) $label); } public function filter_shipping_method_label($label, $method) { $rate_id = $method->get_id(); if (strpos($rate_id, 'table_rate:') !== 0) { return $label; } $parts = explode(':', $rate_id); if (!isset($parts[1])) { return $label; } $instance_id = (int) $parts[1]; $tr = $this->get_all_table_rate_minimums(); if (!isset($tr[$instance_id])) { return $label; } if (strpos($label, 'από') !== false || strpos($label, 'Από') !== false) { return $label; } $title = (string) $tr[$instance_id]['title']; $cost = (float) $method->get_cost(); if ($cost > 0) { return sprintf('%s: %s', $title, wc_price($cost)); } return $title; } public function hide_default_postcode_field_value($value, $input) { if (!function_exists('is_checkout') || !is_checkout()) { return $value; } if (is_user_logged_in()) { return $value; } if (in_array($input, array('billing_postcode','shipping_postcode'), true)) { $def = self::normalize_postcode($this->get_default_postcode()); if ($def !== '' && self::normalize_postcode((string) $value) === $def) { return ''; } } if (in_array($input, array('billing_city','shipping_city'), true)) { $def_city = $this->get_default_city(); if ($def_city !== '' && $value !== '' && trim(strtolower((string) $value)) === trim(strtolower($def_city))) { return ''; } } return $value; } public function clear_postcode_defaults_fields($fields) { if (!function_exists('is_checkout') || !is_checkout()) { return $fields; } if (is_user_logged_in()) { return $fields; } foreach (array('billing','shipping') as $section) { if (isset($fields[$section]['postcode'])) { $fields[$section]['postcode']['default'] = ''; $fields[$section]['postcode']['value'] = ''; } if (isset($fields[$section]['city'])) { $fields[$section]['city']['default'] = ''; $fields[$section]['city']['value'] = ''; } } return $fields; } public function clear_default_from_customer_display() { if (!is_checkout() || is_user_logged_in()) { return; } if (!function_exists('WC') || !WC() || !WC()->customer) { return; } $def_pc = $this->get_default_postcode(); $def_ci = $this->get_default_city(); if (WC()->customer->get_billing_postcode() === $def_pc) { WC()->customer->set_billing_postcode(''); } if (WC()->customer->get_billing_city() === $def_ci) { WC()->customer->set_billing_city(''); } if (WC()->customer->get_shipping_postcode() === $def_pc) { WC()->customer->set_shipping_postcode(''); } if (WC()->customer->get_shipping_city() === $def_ci) { WC()->customer->set_shipping_city(''); } } public function clear_default_postcode_js() { if (!function_exists('is_checkout') || !is_checkout()) { return; } if (is_user_logged_in()) { return; } $def = $this->get_default_postcode(); if ($def === '') { return; } ?> is_checkout_context() ? 'no' : $value; } public function render_subtab() { $s = $this->get_settings(); $form_id = 'aieo-card-smart-shipping-label-default-postcode-and-wording-form'; list($open, $close) = AIEO_DMM_UI::card(array( 'anchor' => 'smart-shipping-label-default-postcode-and-wording', 'title' => __('Default postcode and wording', 'ai-eshop-optimizer'), 'description' => sprintf(__('Tackles the "how much will shipping cost?" abandonment driver: shows "Carrier (From %sX.XX)" for guests / users who haven\'t entered a postcode (using the MIN(rate_cost) from your table-rate config). Once a real postcode is entered, exact rates appear. When the cart qualifies for free shipping, all method prices hide and the generic "Free shipping" pseudo-method is removed so the customer picks a real carrier.', 'ai-eshop-optimizer'), html_entity_decode(get_woocommerce_currency_symbol(), ENT_QUOTES | ENT_HTML5, 'UTF-8')), 'footer' => class_exists('\Rooster\Admin\States') ? \Rooster\Admin\States::save_button($form_id) : '', )); echo $open; $aieo_ssl_warning = \Rooster\Admin\Ui::notice( esc_html(trim(str_replace('⚠', '', __('⚠ Touches checkout shipping display in detail. Test on staging first. Make sure the Default postcode is one that actually matches a valid shipping zone with delivery methods — otherwise couriers won\'t appear.', 'ai-eshop-optimizer')))), 'warning', array('inline' => true) ); echo $aieo_ssl_warning; ob_start(); echo AIEO_Kit::row(__('Status', 'ai-eshop-optimizer'), AIEO_Kit::check('enabled', __('Enabled', 'ai-eshop-optimizer'), $s['enabled'] === 'yes', array('id' => 'aieo-ssl-enabled')), '', array('id' => 'aieo-ssl-enabled', 'group' => true)); echo AIEO_Kit::row( __('Default postcode', 'ai-eshop-optimizer'), AIEO_Kit::input('default_postcode', (string) $s['default_postcode'], array('id' => 'aieo-ssl-postcode', 'short' => true, 'code' => true)), __('Used internally for zone matching when no postcode is entered. Pick one that matches a zone with all your delivery methods (typically Athens / your first shipping zone).', 'ai-eshop-optimizer'), array('id' => 'aieo-ssl-postcode') ); echo AIEO_Kit::row(__('Default city', 'ai-eshop-optimizer'), AIEO_Kit::input('default_city', (string) $s['default_city'], array('id' => 'aieo-ssl-city', 'medium' => true)), '', array('id' => 'aieo-ssl-city')); echo AIEO_Kit::row( sprintf(__('Show "From %sX" labels', 'ai-eshop-optimizer'), html_entity_decode(get_woocommerce_currency_symbol(), ENT_QUOTES | ENT_HTML5, 'UTF-8')), AIEO_Kit::check('shipping_display_from', __('Enabled', 'ai-eshop-optimizer'), $s['shipping_display_from'] === 'yes', array('id' => 'aieo-ssl-from')), '', array('id' => 'aieo-ssl-from', 'group' => true) ); echo AIEO_Kit::row( __('"From" label text', 'ai-eshop-optimizer'), AIEO_Kit::input('from_label', (string) $s['from_label'], array('id' => 'aieo-ssl-from-text', 'short' => true)), __('Default "From", translated for your locale. Change the wording to match your shop language.', 'ai-eshop-optimizer'), array('id' => 'aieo-ssl-from-text') ); echo AIEO_DMM_UI::card_form('aieo_dmm_smart_shipping_label_save', $this->get_id(), $form_id, (string) ob_get_clean()); echo $close; } }