id = 'advanced_shipping_rules_for_woocommerce'; $this->instance_id = absint( $instance_id ); // Support more istances $this->method_title = __( 'Advanced Shipping Rules For WooCommerce', 'advanced-shipping-rules-for-woocommerce' ); $this->method_description = __( 'Calculates shipping costs based on weight or item count.', 'advanced-shipping-rules-for-woocommerce' ); $this->enabled = 'yes'; $this->supports = array( 'shipping-zones', 'instance-settings', // Allows multiple configurable instances ); $this->init(); // Loads the custom title set by the user $this->title = sanitize_text_field( $this->get_option( 'title', __( 'Advanced Shipping Rules For WooCommerce', 'advanced-shipping-rules-for-woocommerce' ) ) ); } /** * Initializes the shipping method settings and hooks. * * - Loads the WooCommerce settings API. * - Calls `init_form_fields()` to define settings fields. * - Calls `init_settings()` to load previously saved settings. * - Hooks into WooCommerce to save settings in the admin panel. * * @return void */ private function init(): void { // Load the settings API $this->init_form_fields(); // This is part of the settings API. Override the method to add your own settings $this->init_settings(); // This is part of the settings API. Loads settings you previously init. // Save settings in admin if you have any defined add_action( 'woocommerce_update_options_shipping_' . $this->instance_id, array( $this, 'process_admin_options' ) ); } /** * Initializes the form fields for the shipping method settings. * * - Checks if WooCommerce taxes are enabled. * - Defines the base form fields, including the shipping method title. * - Adds a 'tax_status' field if WooCommerce taxes are enabled. * * @return void */ public function init_form_fields(): void { // Check if WooCommerce taxes are enabled $tax_enabled = get_option( 'woocommerce_calc_taxes' ) === 'yes'; // Define the form fields $this->instance_form_fields = array( 'title' => array( 'title' => __( 'Method Title', 'advanced-shipping-rules-for-woocommerce' ), 'type' => 'text', 'description' => __( 'This controls the title which the user sees during checkout.', 'advanced-shipping-rules-for-woocommerce' ), 'default' => __( 'Advanced Shipping Rules For WooCommerce', 'advanced-shipping-rules-for-woocommerce' ), 'desc_tip' => true, ), ); // Add the 'tax_status' field only if WooCommerce taxes are enabled if ( $tax_enabled ) { $this->instance_form_fields['tax_status'] = array( 'title' => __( 'Tax Status', 'advanced-shipping-rules-for-woocommerce' ), 'type' => 'select', 'description' => __( 'Determine whether shipping is taxable.', 'advanced-shipping-rules-for-woocommerce' ), 'default' => 'taxable', 'desc_tip' => true, 'options' => array( 'taxable' => __( 'Taxable', 'advanced-shipping-rules-for-woocommerce' ), 'none' => __( 'None', 'advanced-shipping-rules-for-woocommerce' ), ), ); } } /** * Outputs the admin options form for the shipping method. * * - Generates the settings HTML for the defined form fields. * - Displays a custom section for managing shipping groups. * - Retrieves and sanitizes selected shipping group IDs from the database. * - Loads and displays the selected shipping groups. * - Provides a button to add new shipping groups (initially disabled). * * @return void */ public function admin_options(): void { ?> generate_settings_html( $this->instance_form_fields ); ?> __( 'Custom Section', 'advanced-shipping-rules-for-woocommerce' ), ); ?>
instance_id, array() ); // Recupera i gruppi associati $selected_group_ids = is_array( $selected_group_ids ) ? array_map( 'absint', $selected_group_ids ) : array(); if ( ! empty( $selected_group_ids ) ) { $groups = get_posts( array( 'post_type' => 'asrfwoo_group', 'numberposts' => -1, 'orderby' => 'post__in', 'include' => $selected_group_ids, ) ); $has_legacy_groups = false; foreach ( $groups as $g ) { if ( get_post_meta( absint( $g->ID ), '_group_cost', true ) === '' ) { $has_legacy_groups = true; break; } } if ( $has_legacy_groups ) { echo '

' . esc_html__( 'One or more rule groups are missing a Shipping Cost. Enter the cost for each group and save to complete the upgrade.', 'advanced-shipping-rules-for-woocommerce' ) . '

'; } $allowed_html = array( 'div' => array( 'class' => array(), 'data-group-id' => array(), ), 'select' => array( 'name' => array(), 'class' => array(), ), 'option' => array( 'value' => array(), 'selected' => array(), 'disabled' => array(), ), 'input' => array( 'type' => array(), 'name' => array(), 'class' => array(), 'value' => array(), 'min' => array(), 'step' => array(), 'placeholder' => array(), 'data-group-id' => array(), ), 'button' => array( 'type' => array(), 'class' => array(), 'data-group-id' => array(), 'disabled' => array(), ), '!--' => array(), // Allow HTML comments ); $index = 0; foreach ( $groups as $group ) { if ( $index > 0 ) { echo wp_kses_post( Helper::generate_html_shipping_separator_or() ); } echo wp_kses( Helper::generate_html_shipping_group( $group ), $allowed_html ); ++$index; } } ?>
instance_id ), array() ); $original_saved_group_ids = is_array( $original_saved_group_ids ) ? array_map( 'absint', $original_saved_group_ids ) : array(); // Unified ordered list: values are "existing_ID" or "new_PLACEHOLDER". $raw_ordered = isset( $_POST['asrfwoo_shipping_group_order'] ) && is_array( $_POST['asrfwoo_shipping_group_order'] ) ? array_map( 'sanitize_key', wp_unslash( $_POST['asrfwoo_shipping_group_order'] ) ) : array(); // Collect real IDs of existing groups still present in the form (for deletion detection). $posted_existing_ids = array(); foreach ( $raw_ordered as $entry ) { if ( substr( $entry, 0, 9 ) === 'existing_' ) { $id = absint( substr( $entry, 9 ) ); if ( $id > 0 ) { $posted_existing_ids[] = $id; } } } // Delete groups (and their conditions) that were removed from the form. foreach ( array_diff( $original_saved_group_ids, $posted_existing_ids ) as $group_id ) { $conditions_to_delete = get_post_meta( $group_id, '_group_conditions', true ) ?: array(); $conditions_to_delete = is_array( $conditions_to_delete ) ? array_map( 'absint', $conditions_to_delete ) : array(); foreach ( $conditions_to_delete as $condition_id ) { wp_delete_post( intval( $condition_id ), true ); } wp_delete_post( intval( $group_id ), true ); } // Pass 1: insert new groups; map placeholder_id => real_group_id. $new_group_map = array(); foreach ( $raw_ordered as $entry ) { if ( substr( $entry, 0, 4 ) !== 'new_' ) { continue; } $placeholder_id = absint( substr( $entry, 4 ) ); if ( ! isset( $_POST['asrfwoo_shipping_condition'][ $placeholder_id ] ) || ! is_array( $_POST['asrfwoo_shipping_condition'][ $placeholder_id ] ) || empty( $_POST['asrfwoo_shipping_condition'][ $placeholder_id ] ) ) { continue; } $asrfwoo_shipping_condition = map_deep( wp_unslash( $_POST['asrfwoo_shipping_condition'][ $placeholder_id ] ), 'sanitize_text_field' ); $group_id = wp_insert_post( array( 'post_type' => 'asrfwoo_group', 'post_status' => 'publish', 'post_title' => '', ) ); if ( ! $group_id ) { continue; } // Save group-level cost $raw_group_cost = isset( $_POST['asrfwoo_shipping_group_cost'][ $placeholder_id ] ) ? sanitize_text_field( wp_unslash( $_POST['asrfwoo_shipping_group_cost'][ $placeholder_id ] ) ) : ''; $group_cost_value = $raw_group_cost !== '' ? max( 0, floatval( $raw_group_cost ) ) : ''; update_post_meta( $group_id, '_group_cost', $group_cost_value ); $selected_conditions = array(); foreach ( $asrfwoo_shipping_condition as $condition ) { $condition_type = isset( $condition['condition-type'] ) ? sanitize_key( $condition['condition-type'] ) : ''; if ( ! array_key_exists( $condition_type, $condition_types ) ) { continue; } $valid_operators = $condition_types[ $condition_type ]['operators']; $condition_operator = isset( $condition['condition-operator'] ) ? sanitize_key( $condition['condition-operator'] ) : ''; if ( ! array_key_exists( $condition_operator, $valid_operators ) ) { continue; } $input_type = sanitize_key( $condition_types[ $condition_type ]['input_type'] ); if ( $input_type === 'number' ) { $condition_value = isset( $condition['condition-value'] ) && $condition['condition-value'] !== '' ? max( 0, floatval( $condition['condition-value'] ) ) : ''; } else { $condition_value = isset( $condition['condition-value'] ) ? sanitize_key( $condition['condition-value'] ) : ''; } if ( $condition_value === '' ) { continue; } $condition_id = wp_insert_post( array( 'post_type' => 'asrfwoo_condition', 'post_status' => 'publish', 'post_title' => '', ) ); if ( $condition_id ) { wp_update_post( array( 'ID' => $condition_id, // Translators: %s represents the condition ID. 'post_title' => sprintf( esc_html__( 'Condition #%s', 'advanced-shipping-rules-for-woocommerce' ), $condition_id ), ) ); update_post_meta( $condition_id, '_condition_type', $condition_type ); update_post_meta( $condition_id, '_operator', $condition_operator ); update_post_meta( $condition_id, '_value', $condition_value ); $selected_conditions[] = $condition_id; } } if ( ! empty( $selected_conditions ) ) { update_post_meta( $group_id, '_group_conditions', $selected_conditions ); $new_group_map[ $placeholder_id ] = $group_id; } else { wp_delete_post( $group_id, true ); } } // Pass 2: update existing groups; collect successfully saved IDs. $updated_group_ids = array(); foreach ( $raw_ordered as $entry ) { if ( substr( $entry, 0, 9 ) !== 'existing_' ) { continue; } $group_id = absint( substr( $entry, 9 ) ); // Security: only process groups belonging to this instance. if ( ! in_array( $group_id, $original_saved_group_ids, true ) ) { continue; } if ( ! isset( $_POST['asrfwoo_shipping_condition'][ $group_id ] ) || ! is_array( $_POST['asrfwoo_shipping_condition'][ $group_id ] ) ) { continue; } // Save group-level cost $raw_group_cost = isset( $_POST['asrfwoo_shipping_group_cost'][ $group_id ] ) ? sanitize_text_field( wp_unslash( $_POST['asrfwoo_shipping_group_cost'][ $group_id ] ) ) : ''; $group_cost_value = $raw_group_cost !== '' ? max( 0, floatval( $raw_group_cost ) ) : ''; update_post_meta( $group_id, '_group_cost', $group_cost_value ); $selected_conditions = array(); $existing_conditions = get_post_meta( $group_id, '_group_conditions', true ) ?: array(); $existing_conditions = is_array( $existing_conditions ) ? array_map( 'absint', $existing_conditions ) : array(); $asrfwoo_shipping_condition = map_deep( wp_unslash( $_POST['asrfwoo_shipping_condition'][ $group_id ] ), 'sanitize_text_field' ); foreach ( $asrfwoo_shipping_condition as $condition_id => $condition ) { $condition_id = absint( sanitize_text_field( $condition_id ) ); $condition_type = isset( $condition['condition-type'] ) ? sanitize_key( $condition['condition-type'] ) : ''; if ( ! array_key_exists( $condition_type, $condition_types ) ) { continue; } $valid_operators = $condition_types[ $condition_type ]['operators']; $condition_operator = isset( $condition['condition-operator'] ) ? sanitize_key( $condition['condition-operator'] ) : ''; if ( ! array_key_exists( $condition_operator, $valid_operators ) ) { continue; } $input_type = sanitize_key( $condition_types[ $condition_type ]['input_type'] ); if ( $input_type === 'number' ) { $condition_value = isset( $condition['condition-value'] ) && $condition['condition-value'] !== '' ? max( 0, floatval( $condition['condition-value'] ) ) : ''; } else { $condition_value = isset( $condition['condition-value'] ) ? sanitize_key( $condition['condition-value'] ) : ''; } if ( $condition_value === '' ) { if ( ! isset( $condition['condition-is-new'] ) || intval( $condition['condition-is-new'] ) !== 1 ) { wp_delete_post( $condition_id, true ); } continue; } if ( isset( $condition['condition-is-new'] ) && intval( $condition['condition-is-new'] ) === 1 ) { $new_condition_id = wp_insert_post( array( 'post_type' => 'asrfwoo_condition', 'post_status' => 'publish', 'post_title' => '', ) ); if ( $new_condition_id ) { update_post_meta( $new_condition_id, '_condition_type', $condition_type ); update_post_meta( $new_condition_id, '_operator', $condition_operator ); update_post_meta( $new_condition_id, '_value', $condition_value ); $selected_conditions[] = $new_condition_id; } } else { update_post_meta( $condition_id, '_condition_type', $condition_type ); update_post_meta( $condition_id, '_operator', $condition_operator ); update_post_meta( $condition_id, '_value', $condition_value ); $selected_conditions[] = $condition_id; } } // Remove conditions that were deleted from the form. foreach ( array_diff( $existing_conditions, $selected_conditions ) as $cid ) { wp_delete_post( $cid, true ); } if ( ! empty( $selected_conditions ) ) { update_post_meta( $group_id, '_group_conditions', $selected_conditions ); $updated_group_ids[ $group_id ] = $group_id; } } // Pass 3: reconstruct $saved_group_ids in the user's original display order. $saved_group_ids = array(); foreach ( $raw_ordered as $entry ) { if ( substr( $entry, 0, 4 ) === 'new_' ) { $placeholder_id = absint( substr( $entry, 4 ) ); if ( isset( $new_group_map[ $placeholder_id ] ) ) { $saved_group_ids[] = $new_group_map[ $placeholder_id ]; } } elseif ( substr( $entry, 0, 9 ) === 'existing_' ) { $group_id = absint( substr( $entry, 9 ) ); if ( isset( $updated_group_ids[ $group_id ] ) ) { $saved_group_ids[] = $group_id; } } } update_option( 'asrfwoo_shipping_groups_' . sanitize_key( $this->instance_id ), $saved_group_ids ); } /** * Calculates and applies shipping rates based on defined conditions. * * This function evaluates shipping conditions such as total cart weight, * number of products, and cart total, applying the appropriate shipping * cost based on predefined rules. * * @param array $package The package details (default is an empty array). * * @return void */ public function calculate_shipping( $package = array() ): void { $cart_weight = WC()->cart->get_cart_contents_weight(); $cart_items = WC()->cart->get_cart_contents_count(); $cart_total = floatval( WC()->cart->get_subtotal() ); $shipping_cost = 0; $condition_types = Helper::get_condition_types(); $selected_group_ids = get_option( 'asrfwoo_shipping_groups_' . sanitize_key( $this->instance_id ), array() ); if ( empty( $selected_group_ids ) ) { return; } /** * Filters the condition handler callbacks used during shipping calculation. * * Each handler is a callable with signature: * function( string $operator, mixed $value, float $cost, float &$group_cost, bool &$is_condition_met, float $cart_weight, int $cart_items, float $cart_total ) * * Set &$is_condition_met = true and add to &$group_cost when the condition passes. * * @param array $handlers Associative array of condition_type (string) => callable. */ $condition_handlers = apply_filters( 'asrfwoo_condition_handlers', array( 'total_weight' => function ( $operator, $value, $cost, &$group_cost, &$is_condition_met, $cart_weight, $cart_items, $cart_total ) { if ( $operator === 'greater_than' && $cart_weight > $value ) { $group_cost += $cost; $is_condition_met = true; } elseif ( $operator === 'greater_than_or_equal' && $cart_weight >= $value - 0.0001 ) { $group_cost += $cost; $is_condition_met = true; } elseif ( $operator === 'less_than' && $cart_weight < $value ) { $group_cost += $cost; $is_condition_met = true; } elseif ( $operator === 'less_than_or_equal' && $cart_weight <= $value + 0.0001 ) { $group_cost += $cost; $is_condition_met = true; } elseif ( $operator === 'equal' && abs( $cart_weight - $value ) < 0.0001 ) { $group_cost += $cost; $is_condition_met = true; } }, 'products_number' => function ( $operator, $value, $cost, &$group_cost, &$is_condition_met, $cart_weight, $cart_items, $cart_total ) { if ( $operator === 'greater_than' && $cart_items > $value ) { $group_cost += $cost; $is_condition_met = true; } elseif ( $operator === 'greater_than_or_equal' && $cart_items >= (int) $value ) { $group_cost += $cost; $is_condition_met = true; } elseif ( $operator === 'less_than' && $cart_items < $value ) { $group_cost += $cost; $is_condition_met = true; } elseif ( $operator === 'less_than_or_equal' && $cart_items <= (int) $value ) { $group_cost += $cost; $is_condition_met = true; } elseif ( $operator === 'equal' && $cart_items === (int) $value ) { $group_cost += $cost; $is_condition_met = true; } }, 'cart_total' => function ( $operator, $value, $cost, &$group_cost, &$is_condition_met, $cart_weight, $cart_items, $cart_total ) { if ( $operator === 'greater_than' && $cart_total > $value ) { $group_cost += $cost; $is_condition_met = true; } elseif ( $operator === 'greater_than_or_equal' && $cart_total >= $value - 0.0001 ) { $group_cost += $cost; $is_condition_met = true; } elseif ( $operator === 'less_than' && $cart_total < $value ) { $group_cost += $cost; $is_condition_met = true; } elseif ( $operator === 'less_than_or_equal' && $cart_total <= $value + 0.0001 ) { $group_cost += $cost; $is_condition_met = true; } elseif ( $operator === 'equal' && abs( $cart_total - $value ) < 0.0001 ) { $group_cost += $cost; $is_condition_met = true; } }, ) ); $valid_group_costs = array(); // for each group of conditions, check if conditions are satisfied, and eventually calculate group cost. foreach ( $selected_group_ids as $group_id ) { $group_id = absint( $group_id ); $conditions = get_post_meta( $group_id, '_group_conditions', true ); $conditions = is_array( $conditions ) ? array_map( 'intval', $conditions ) : array(); $group_valid = true; $group_cost = 0; foreach ( $conditions as $condition_id ) { $condition_type = sanitize_text_field( get_post_meta( $condition_id, '_condition_type', true ) ); $operator = sanitize_text_field( get_post_meta( $condition_id, '_operator', true ) ); // Validate condition type if ( ! isset( $condition_types[ $condition_type ] ) ) { $group_valid = false; break; } $valid_operators = array_keys( $condition_types[ $condition_type ]['operators'] ); // Validate operator if ( ! in_array( $operator, $valid_operators, true ) ) { $group_valid = false; break; } $input_type = sanitize_key( $condition_types[ $condition_type ]['input_type'] ); $value = get_post_meta( $condition_id, '_value', true ); // Validate value based on input type if ( $input_type === 'number' ) { $value = max( 0, floatval( $value ) ); } else { $value = sanitize_text_field( $value ); } $cost = max( 0, floatval( get_post_meta( $condition_id, '_cost', true ) ) ); $is_condition_met = false; // If a handler exists for this condition, execute it if ( isset( $condition_handlers[ $condition_type ] ) ) { $condition_handlers[ $condition_type ]( $operator, $value, $cost, $group_cost, $is_condition_met, $cart_weight, $cart_items, $cart_total ); } if ( ! $is_condition_met ) { $group_valid = false; break; } } if ( $group_valid ) { $raw_group_cost = get_post_meta( $group_id, '_group_cost', true ); if ( $raw_group_cost !== '' ) { $group_cost = max( 0, floatval( $raw_group_cost ) ); } $valid_group_costs[] = $group_cost; } } if ( ! empty( $valid_group_costs ) ) { $shipping_cost = max( $valid_group_costs ); } /** * Filters the final shipping cost before it is added as a rate. * * @param float $shipping_cost The computed shipping cost. * @param array $package The shipping package. * @param \WC_Shipping_Method $method This shipping method instance. */ $shipping_cost = apply_filters( 'asrfwoo_shipping_cost', $shipping_cost, $package, $this ); if ( $shipping_cost > 0 ) { $rate = array( 'id' => sanitize_key( $this->id ), 'label' => sanitize_text_field( $this->title ), 'cost' => $shipping_cost, ); // Check if WooCommerce taxes are enabled (if disabled, force no tax) $tax_enabled = get_option( 'woocommerce_calc_taxes' ) === 'yes'; if ( $tax_enabled ) { $tax_status = sanitize_text_field( $this->get_option( 'tax_status', 'taxable' ) ); // If tax status is 'none', disable taxes explicitly if ( $tax_status === 'none' ) { $rate['taxes'] = false; } } $this->add_rate( $rate ); } } } }