'; } function calculator_shortcode() { if ($this->position != 'shortcode') { return '
' . __('Short code is disabled in setting', 'superfrete-product-page-shipping-calculator-woocommerce') . ''; } if (function_exists('is_product') && !is_product()) { return '
' . __('This shortcode will only work on product page', 'superfrete-product-page-shipping-calculator-woocommerce') . ''; } global $product; if (!is_object($product) || $product->is_virtual() || !$product->is_in_stock()) return; ob_start(); self::calculator(); $html = ob_get_contents(); ob_end_clean(); return $html; } static function calculator() { global $product; // Hide calculator for virtual products (they don't need shipping) if (is_object($product) && $product->is_virtual()) { return; } if (apply_filters('superfrete_hide_calculator_on_single_product_page', false, $product)) { return; } if (is_object($product)) { $product_id = $product->get_id(); } else { $product = ""; } $enable_calculator = get_option('superfrete_enable_calculator'); if ($enable_calculator === 'no') return; $button_text = get_option('superfrete_open_drawer_button_text', 'Calcular Entrega'); $update_address_btn_text = get_option('superfrete_update_button_text', 'Calcular'); include plugin_dir_path(__FILE__) . '../../templates/woocommerce/shipping-calculator.php'; } static function hideCalculator($val, $product) { if (is_object($product) && $product->is_virtual()) return true; return $val; } static function applyShipping() { if (!isset($_POST['superfrete_nonce']) || !wp_verify_nonce($_POST['superfrete_nonce'], 'superfrete_nonce')) { wp_send_json_error(['message' => 'Requisição inválida.'], 403); } // Start timing the entire function execution $total_start_time = microtime(true); // Setup logging $log_data = [ 'method' => 'applyShipping', 'steps' => [], 'total_time' => 0 ]; if (!class_exists('WC_Shortcode_Cart')) { include_once WC_ABSPATH . 'includes/shortcodes/class-wc-shortcode-cart.php'; } if (self::doingCalculation()) { $step_start = microtime(true); if ((isset($_POST['action_auto_load']) && self::disableAutoLoadEstimate()) || empty($_POST['calc_shipping_postcode']) || !isset($_POST['calc_shipping_postcode'])) { $return['shipping_methods'] = sprintf('
%s
', esc_html(get_option('superfrete_no_address_added_yet', 'Informe seu Endereço para calcular'))); wp_send_json($return); } $log_data['steps']['initial_validation'] = round((microtime(true) - $step_start) * 1000, 2) . ' ms'; $step_start = microtime(true); $return = array(); \WC_Shortcode_Cart::calculate_shipping(); WC()->cart->calculate_totals(); $log_data['steps']['calculate_shipping'] = round((microtime(true) - $step_start) * 1000, 2) . ' ms'; $step_start = microtime(true); // OPTIMIZATION: Use a lightweight shipping package instead of manipulating the cart // This avoids expensive cart operations that were causing performance issues // Get product ID and variation ID $product_id = filter_input(INPUT_POST, 'product_id', FILTER_VALIDATE_INT); $variation_id = filter_input(INPUT_POST, 'variation_id', FILTER_VALIDATE_INT); $quantity = filter_input(INPUT_POST, 'quantity', FILTER_VALIDATE_INT) ?: 1; // Get product data directly without cart manipulation if ($product_id) { $product = wc_get_product($variation_id ?: $product_id); if ($product) { // Skip calculation for virtual products if ($product->is_virtual()) { $return['shipping_methods'] = sprintf('
%s
', esc_html__('Produtos virtuais não necessitam de frete.', 'superfrete')); wp_send_json($return); } // Get destination from form data (not customer data) $destination_postcode = sanitize_text_field($_POST['calc_shipping_postcode'] ?? ''); $destination_country = sanitize_text_field($_POST['calc_shipping_country'] ?? 'BR'); $destination_state = sanitize_text_field($_POST['calc_shipping_state'] ?? ''); $destination_city = sanitize_text_field($_POST['calc_shipping_city'] ?? ''); // Create a lightweight package for shipping calculation $package = [ 'contents' => [ [ 'data' => $product, 'quantity' => $quantity ] ], 'contents_cost' => $product->get_price() * $quantity, 'applied_coupons' => [], 'user' => [ 'ID' => get_current_user_id(), ], 'destination' => [ 'country' => $destination_country, 'state' => $destination_state, 'postcode' => $destination_postcode, 'city' => $destination_city, ], 'cart_subtotal' => $product->get_price() * $quantity, ]; $log_data['steps']['prepare_package'] = round((microtime(true) - $step_start) * 1000, 2) . ' ms'; $step_start = microtime(true); // Debug: Check all registered shipping methods $wc_shipping = \WC_Shipping::instance(); $all_shipping_methods = $wc_shipping->get_shipping_methods(); $log_data['all_registered_methods'] = array_keys($all_shipping_methods); // Debug: Check all shipping zones $all_zones = \WC_Shipping_Zones::get_zones(); $log_data['all_zones'] = []; foreach ($all_zones as $zone_data) { $zone = new \WC_Shipping_Zone($zone_data['zone_id']); $zone_methods = []; foreach ($zone->get_shipping_methods() as $method) { $zone_methods[] = [ 'id' => $method->id, 'enabled' => $method->enabled, 'title' => $method->get_title(), ]; } $log_data['all_zones'][] = [ 'id' => $zone->get_id(), 'name' => $zone->get_zone_name(), 'methods' => $zone_methods, ]; } // Calculate shipping rates directly with our package $shipping_zone = \WC_Shipping_Zones::get_zone_matching_package($package); // Add detailed debugging for shipping zone $log_data['package_destination'] = $package['destination']; $log_data['shipping_zone'] = [ 'id' => $shipping_zone->get_id(), 'name' => $shipping_zone->get_zone_name(), 'locations' => $shipping_zone->get_zone_locations(), ]; // Detailed logging for shipping calculation $shipping_start = microtime(true); // Log shipping providers being used $active_shipping = []; $all_methods = $shipping_zone->get_shipping_methods(true); foreach ($all_methods as $method) { $active_shipping[] = [ 'id' => $method->id, 'enabled' => $method->is_enabled() ? 'yes' : 'no', 'title' => $method->get_title(), 'instance_id' => $method->get_instance_id(), ]; } $log_data['active_shipping_methods'] = $active_shipping; $log_data['total_methods_found'] = count($all_methods); // Calculate shipping directly $rates = []; $method_times = []; foreach ($shipping_zone->get_shipping_methods(true) as $method) { $method_start = microtime(true); $method_id = $method->id; // Log method details $log_data['shipping_methods'][] = [ 'id' => $method_id, 'title' => $method->get_title(), 'enabled' => $method->is_enabled() ? 'yes' : 'no', 'class' => get_class($method), 'instance_id' => $method->get_instance_id(), ]; // Time each individual method try { // For SuperFrete shipping method, call calculate_shipping directly if ($method_id === 'superfrete_shipping') { $method->calculate_shipping($package); $method_rates = $method->rates; } else { $method_rates = $method->get_rates_for_package($package); } $log_data['method_results'][$method_id] = [ 'rates_count' => is_array($method_rates) ? count($method_rates) : 0, 'rates' => $method_rates ? array_keys($method_rates) : [], ]; } catch (Exception $e) { $log_data['method_errors'][$method_id] = $e->getMessage(); $method_rates = []; } $method_time = round((microtime(true) - $method_start) * 1000, 2); $method_times[$method_id] = $method_time . ' ms'; // Log any methods taking over 500ms if ($method_time > 500) { $log_data['slow_methods'][$method_id] = $method_time . ' ms'; } if ($method_rates) { $rates = array_merge($rates, $method_rates); } } // Add method timing to logs $log_data['method_times'] = $method_times; // Calculate shipping time first $shipping_time = round((microtime(true) - $shipping_start) * 1000, 2); // Check for common API bottlenecks if ($shipping_time > 1000) { // Get the HTTP stats to see external API calls global $wp_version; $http_counts = [ 'total_requests' => 0, 'total_time' => 0, 'average_time' => 0 ]; // If using WordPress HTTP API, we can check the stats if (function_exists('_get_http_stats')) { $http_stats = _get_http_stats(); if (isset($http_stats['requests'])) { $http_counts['total_requests'] = count($http_stats['requests']); // Sum up all request times foreach ($http_stats['requests'] as $request) { if (isset($request['args']['timeout'])) { // Log timeout values $http_counts['timeouts'][] = $request['args']['timeout']; } if (isset($request['end_time']) && isset($request['start_time'])) { $request_time = $request['end_time'] - $request['start_time']; $http_counts['total_time'] += $request_time; // Log slow individual requests (over 500ms) if ($request_time > 0.5) { $http_counts['slow_requests'][] = [ 'url' => isset($request['url']) ? preg_replace('/\?.*/', '', $request['url']) : 'unknown', 'time' => round($request_time * 1000) . ' ms' ]; } } } if ($http_counts['total_requests'] > 0) { $http_counts['average_time'] = round(($http_counts['total_time'] / $http_counts['total_requests']) * 1000, 2) . ' ms'; $http_counts['total_time'] = round($http_counts['total_time'] * 1000, 2) . ' ms'; } } } $log_data['http_api'] = $http_counts; } $log_data['steps']['calculate_shipping_only'] = $shipping_time . ' ms'; // Log potential slow API calls if ($shipping_time > 2000) { // If over 2 seconds $log_data['warning'] = 'Shipping calculation is slow - may indicate API rate limiting or network issues'; } $log_data['steps']['calculate_test_shipping'] = round((microtime(true) - $step_start) * 1000, 2) . ' ms'; $step_start = microtime(true); // Sort rates by price (lowest to highest) before formatting uasort($rates, function($a, $b) { $cost_a = floatval($a->cost); $cost_b = floatval($b->cost); // Free shipping (cost = 0) should come first if ($cost_a == 0 && $cost_b > 0) return -1; if ($cost_b == 0 && $cost_a > 0) return 1; // Both free or both paid - sort by cost ascending (lowest first) if ($cost_a == $cost_b) { // If costs are equal, sort by delivery time (faster first) $time_a = isset($a->meta_data['delivery_time']) ? intval($a->meta_data['delivery_time']) : 999; $time_b = isset($b->meta_data['delivery_time']) ? intval($b->meta_data['delivery_time']) : 999; return $time_a <=> $time_b; } return $cost_a <=> $cost_b; }); // Format shipping methods (now sorted by price) $shipping_methods = []; foreach ($rates as $rate_id => $rate) { $title = wc_cart_totals_shipping_method_label($rate); $title = self::modifiedTitle($title, $rate); $shipping_methods[$rate_id] = apply_filters('superfrete_ppscw_shipping_method_name', $title, $rate, $product_id, $variation_id); } $log_data['steps']['get_shipping_methods'] = round((microtime(true) - $step_start) * 1000, 2) . ' ms'; $step_start = microtime(true); $return['error'] = ''; // Empty the error to prevent notifications $return['shipping_methods'] = self::messageTemplate($shipping_methods); $log_data['steps']['format_methods_html'] = round((microtime(true) - $step_start) * 1000, 2) . ' ms'; // No need to restore cart or remove items - we never changed it $log_data['steps']['restore_cart'] = 0; } else { $return['error'] = __('Produto não encontrado.', 'superfrete'); $return['shipping_methods'] = ''; } } else { $return['error'] = __('ID do produto não fornecido.', 'superfrete'); $return['shipping_methods'] = ''; } // Calculate total execution time $log_data['total_time'] = round((microtime(true) - $total_start_time) * 1000, 2) . ' ms'; // Add log data to the return for debugging $return['performance_log'] = $log_data; // Log to the WordPress error log error_log('SuperFrete Performance Log: ' . wp_json_encode($log_data)); echo wp_json_encode($return); } wp_die(); } static function is_product_present_in_cart() { return false; } static function noShippingLocationInserted() { $country = WC()->customer->get_shipping_country(); if (empty($country) || $country == 'default') return true; return false; } static function onlyPassErrorNotice($notice_type) { if (self::doingCalculation()) { return array('error'); } return $notice_type; } static function doingCalculation() { if (wp_verify_nonce($_POST['superfrete_nonce'], 'superfrete_nonce') && !empty($_POST['calc_shipping']) ) { return true; } return false; } static function addTestProductForProperShippingCost() { $product_id = filter_input(INPUT_POST, 'product_id'); $quantity = filter_input(INPUT_POST, 'quantity'); if (empty($quantity)) $quantity = 1; if ($product_id) { $variation_id = filter_input(INPUT_POST, 'variation_id'); if (!$variation_id) { $variation_id = 0; } $item_key = self::addProductToCart($product_id, $variation_id, $quantity); } else { $item_key = ""; } return $item_key; } static function addProductToCart($product_id, $variation_id, $quantity = 1) { $consider_product_quantity = apply_filters('superfrete_ppscw_consider_quantity_in_shipping_calculation', get_option('superfrete_consider_quantity_field', 'dont-consider-quantity-field'), $product_id, $variation_id, $quantity); if ($consider_product_quantity == 'dont-consider-quantity-field') { if (self::productExistInCart($product_id, $variation_id)) return ""; $quantity = 1; } if (!empty($variation_id)) { $variation = self::getVariationAttributes($variation_id); } else { $variation = array(); } $item_key = WC()->cart->add_to_cart( $product_id, $quantity, $variation_id, $variation, array( 'superfrete_test_product_for_calculation' => '1', ) ); return $item_key; } static function getVariationAttributes($product_id) { if (empty($product_id)) return array(); $product = wc_get_product($product_id); if (!is_object($product)) return array(); $variation = array(); $type = $product->get_type(); if ($type == 'variation') { $parent_id = $product->get_parent_id(); $parent_obj = wc_get_product($parent_id); $default_attributes = $parent_obj->get_default_attributes(); $variation_attributes = $product->get_variation_attributes(); // Get all parent attributes, needed to fetch attribute options. $parent_attributes = $parent_obj->get_attributes(); $variation = self::getAttributes($variation_attributes, $default_attributes, $parent_attributes); return $variation; } return $variation; } static function getAttributes($variation_attributes, $default_attributes, $parent_attributes) { $list = array(); foreach ($variation_attributes as $name => $value) { $att_name = str_replace('attribute_', "", $name); if (empty($value)) { $value = isset($default_attributes[$att_name]) ? $default_attributes[$att_name] : ""; if (empty($value) && isset($parent_attributes[$att_name])) { $attribute_obj = $parent_attributes[$att_name]; if ($attribute_obj->get_variation()) { $options = $attribute_obj->get_options(); if (!empty($options)) { // If taxonomy based, options are term IDs so convert the first one to slug. if ($attribute_obj->is_taxonomy()) { $term = get_term($options[0]); if (!is_wp_error($term) && $term) { $value = $term->slug; } else { $value = 'x'; } } else { // For custom attributes, simply use the first option. $value = $options[0]; } } else { $value = 'x'; // Fallback if no options found. } } else { $value = 'x'; // Fallback if attribute is not variation-enabled. } } } $list[$name] = $value; } return $list; } static function productExistInCart($product_id, $variation_id) { if (!WC()->cart->is_empty()) { foreach (WC()->cart->get_cart() as $cart_item) { if ($cart_item['product_id'] == $product_id && $cart_item['variation_id'] == $variation_id) { return true; } } } return false; } static function get_shipping_packages() { return array( array( 'contents' => array(), 'contents_cost' => 0, 'applied_coupons' => '', 'user' => array( 'ID' => get_current_user_id(), ), 'destination' => array( 'country' => self::get_customer()->get_shipping_country(), 'state' => self::get_customer()->get_shipping_state(), 'postcode' => self::get_customer()->get_shipping_postcode(), 'city' => self::get_customer()->get_shipping_city(), ), 'cart_subtotal' => 0, ), ); } static function get_customer() { return WC()->customer; } static function getShippingMethods($packages) { $shipping_methods = array(); $product_id = filter_input(INPUT_POST, 'product_id'); $variation_id = filter_input(INPUT_POST, 'variation_id'); foreach ($packages as $package) { if (empty($package['rates']) || !is_array($package['rates'])) break; foreach ($package['rates'] as $id => $rate) { $title = wc_cart_totals_shipping_method_label($rate); $title = self::modifiedTitle($title, $rate); $shipping_methods[$id] = apply_filters('superfrete_ppscw_shipping_method_name', $title, $rate, $product_id, $variation_id); } } return $shipping_methods; } static function noMethodAvailableMsg() { if (self::noShippingLocationInserted()) { return wp_kses_post(get_option('superfrete_no_address_added_yet', 'Informe seu Endereço para calcular')); } else { return wp_kses_post(get_option('superfrete_no_shipping_methods_msg', 'Nenhum método de envio encontrado')); } } static function disableAutoLoadEstimate() { $auto_loading = get_option('superfrete_auto_calculation', 'disabled'); // Changed default to disabled if ($auto_loading == 'enabled') return false; return true; } static function messageTemplate($shipping_methods) { $html = ''; if (!empty($shipping_methods)) { $html .= '
'; $html .= '

' . __('Opções de Entrega', 'superfrete') . '

'; foreach ($shipping_methods as $method_id => $method_name) { // Extract shipping method name and price $html .= '
'; // Get just the method name (everything before the colon) $method_parts = explode(':', $method_name, 2); $method_label = trim(strip_tags($method_parts[0])); // Add method name to the HTML $html .= '' . esc_html($method_label) . ''; // Handle price display - keeping the full HTML structure for WooCommerce price formatting if (count($method_parts) > 1 && !empty($method_parts[1])) { // Check if the price part contains HTML or not if (strpos($method_parts[1], ''; } else { // Add simple text price $html .= '' . esc_html(trim($method_parts[1])) . ''; } } else { // No price found, show as Free $html .= 'Gratuito'; } $html .= '
'; } $html .= '
'; } else { $message = get_option('superfrete_no_rates_message', 'Desculpe, não encontramos métodos de envio para [country]. Por favor, verifique seu endereço ou entre em contato conosco.'); $country = __('Brasil', 'superfrete'); if (isset(WC()->customer)) { $country_code = self::get_customer()->get_shipping_country(); if (!empty($country_code) && isset(WC()->countries) && $country_code !== 'default') { $country = WC()->countries->countries[$country_code]; } } $find_replace = [ '[country]' => $country ]; $message = str_replace(array_keys($find_replace), array_values($find_replace), $message); $html .= '
' . esc_html($message) . '
'; } return $html; } static function shortCode($message) { $country = __('Country', 'superfrete-product-page-shipping-calculator-woocommerce'); if (isset(WC()->customer)) { $country_code = self::get_customer()->get_shipping_country(); if (!empty($country_code) && isset(WC()->countries) && $country_code !== 'default') { $country = WC()->countries->countries[$country_code]; } } $find_replace = array( '[country]' => $country ); $message = str_replace(array_keys($find_replace), array_values($find_replace), $message); return $message; } function enableShippingCalculationWithoutAddress($val) { if (wp_verify_nonce($_POST['superfrete_nonce'], 'superfrete_nonce') && ((isset($_POST['action']) && $_POST['action'] === 'superfrete_cal_shipping') || (isset($_POST['action']) && $_POST['action'] === 'superfrete_save_address_form'))) { return null; } return $val; } static function modifiedTitle($title, $rate) { if (isset($rate->cost) && $rate->cost == 0) { $free_display_type = get_option('superfrete_free_shipping_price', 'nothing'); if ($free_display_type == 'nothing') return $title; if ($free_display_type == 'zero') { $label = $rate->get_label(); $title = $label . ': ' . wc_price($rate->cost); } } return $title; } static function loadLocation() { $location = ['calc_shipping_country' => '', 'calc_shipping_state' => '', 'calc_shipping_city' => '', 'calc_shipping_postcode' => '']; if (function_exists('WC') && isset(WC()->customer) && is_object(WC()->customer)) { $location['calc_shipping_country'] = WC()->customer->get_shipping_country(); $location['calc_shipping_state'] = WC()->customer->get_shipping_state(); $location['calc_shipping_city'] = WC()->customer->get_shipping_city(); $location['calc_shipping_postcode'] = WC()->customer->get_shipping_postcode(); } wp_send_json($location); } /** * Exibe o formulário de cálculo de frete na página do produto. */ public function display_calculator_form() { include plugin_dir_path(__FILE__) . '../../templates/woocommerce/shipping-calculator.php'; } /** * Adiciona os scripts necessários */ public function enqueue_scripts() { $plugin_file = plugin_dir_path(__FILE__) . '../../superfrete.php'; // Inclui função get_plugin_data se ainda não estiver disponível if (!function_exists('get_plugin_data')) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } $plugin_data = get_plugin_data($plugin_file); $plugin_version = $plugin_data['Version']; // Enqueue JavaScript wp_enqueue_script( 'superfrete-calculator', plugin_dir_url(__FILE__) . '../../assets/scripts/superfrete-calculator.js', ['jquery'], $plugin_version, // Versão do script true ); // Enqueue CSS wp_enqueue_style( 'superfrete-calculator-style', plugin_dir_url(__FILE__) . '../../assets/styles/superfrete-calculator.css', [], $plugin_version ); wp_localize_script('superfrete-calculator', 'superfrete_ajax', [ 'ajax_url' => admin_url('admin-ajax.php'), ]); // Localize script with additional settings wp_localize_script('superfrete-calculator', 'superfrete_setting', [ 'wc_ajax_url' => add_query_arg(['wc-ajax' => '%%endpoint%%'], home_url('/')), 'auto_load' => true, 'country_code' => 'BR', 'i18n' => [ 'state_label' => __('Estado', 'superfrete'), 'select_state_text' => __('Selecione um estado', 'superfrete') ] ]); } }