namespace, '/' . $this->rest_base . '/zones', array( array( 'methods' => \WP_REST_Server::READABLE, 'callback' => array( $this, 'get_zones' ), 'permission_callback' => array( $this, 'check_permission' ), ), ) ); // Toggle shipping method register_rest_route( $this->namespace, '/' . $this->rest_base . '/zones/(?P\d+)/methods/(?P\d+)/toggle', array( array( 'methods' => \WP_REST_Server::CREATABLE, 'callback' => array( $this, 'toggle_method' ), 'permission_callback' => array( $this, 'check_permission' ), 'args' => array( 'zone_id' => array( 'required' => true, 'type' => 'integer', ), 'instance_id' => array( 'required' => true, 'type' => 'integer', ), 'enabled' => array( 'required' => true, 'type' => 'boolean', ), ), ), ) ); // Get available shipping methods register_rest_route( $this->namespace, '/' . $this->rest_base . '/methods/available', array( array( 'methods' => \WP_REST_Server::READABLE, 'callback' => array( $this, 'get_available_methods' ), 'permission_callback' => array( $this, 'check_permission' ), ), ) ); // Add shipping method to zone register_rest_route( $this->namespace, '/' . $this->rest_base . '/zones/(?P\d+)/methods', array( array( 'methods' => \WP_REST_Server::CREATABLE, 'callback' => array( $this, 'add_method' ), 'permission_callback' => array( $this, 'check_permission' ), 'args' => array( 'zone_id' => array( 'required' => true, 'type' => 'integer', ), 'method_id' => array( 'required' => true, 'type' => 'string', ), ), ), ) ); // Delete shipping method from zone register_rest_route( $this->namespace, '/' . $this->rest_base . '/zones/(?P\d+)/methods/(?P\d+)', array( array( 'methods' => \WP_REST_Server::DELETABLE, 'callback' => array( $this, 'delete_method' ), 'permission_callback' => array( $this, 'check_permission' ), 'args' => array( 'zone_id' => array( 'required' => true, 'type' => 'integer', ), 'instance_id' => array( 'required' => true, 'type' => 'integer', ), ), ), ) ); // Get method settings register_rest_route( $this->namespace, '/' . $this->rest_base . '/zones/(?P\d+)/methods/(?P\d+)/settings', array( array( 'methods' => \WP_REST_Server::READABLE, 'callback' => array( $this, 'get_method_settings' ), 'permission_callback' => array( $this, 'check_permission' ), ), ) ); // Update method settings register_rest_route( $this->namespace, '/' . $this->rest_base . '/zones/(?P\d+)/methods/(?P\d+)/settings', array( array( 'methods' => \WP_REST_Server::EDITABLE, 'callback' => array( $this, 'update_method_settings' ), 'permission_callback' => array( $this, 'check_permission' ), ), ) ); } /** * Get all shipping zones with their methods */ public function get_zones( WP_REST_Request $request ) { try { $zones_data = array(); // Get all shipping zones $zones = WC_Shipping_Zones::get_zones(); foreach ( $zones as $zone_data ) { $zone = WC_Shipping_Zones::get_zone( $zone_data['id'] ); // Get zone locations (regions) $locations = $zone->get_zone_locations(); $regions = array(); foreach ( $locations as $location ) { if ( $location->type === 'country' ) { $countries = WC()->countries->get_countries(); $regions[] = $countries[ $location->code ] ?? $location->code; } elseif ( $location->type === 'state' ) { $states = WC()->countries->get_states( substr( $location->code, 0, 2 ) ); $regions[] = $states[ substr( $location->code, 3 ) ] ?? $location->code; } elseif ( $location->type === 'continent' ) { $continents = WC()->countries->get_continents(); $regions[] = $continents[ $location->code ]['name'] ?? $location->code; } elseif ( $location->type === 'postcode' ) { $regions[] = 'Postcode: ' . $location->code; } } // Get shipping methods for this zone (false = get all, not just enabled) $shipping_methods = $zone->get_shipping_methods( false ); $rates = array(); foreach ( $shipping_methods as $method ) { // Get fresh settings from database $settings = get_option( $method->get_instance_option_key(), array() ); $is_enabled = isset( $settings['enabled'] ) && $settings['enabled'] === 'yes'; $rate = array( 'id' => $method->id . ':' . $method->instance_id, 'instance_id' => $method->instance_id, 'method_id' => $method->id, 'name' => $method->get_title(), 'enabled' => $is_enabled, ); // Get cost if available if ( isset( $method->cost ) && $method->cost !== '' ) { $rate['price'] = wc_price( $method->cost ); } elseif ( $method->id === 'free_shipping' ) { $rate['price'] = __( 'Free', 'woonoow' ); } else { $rate['price'] = __( 'Calculated', 'woonoow' ); } // Get method description if available if ( method_exists( $method, 'get_method_description' ) ) { $rate['description'] = $method->get_method_description(); } $rates[] = $rate; } $zones_data[] = array( 'id' => $zone_data['id'], 'name' => $zone->get_zone_name(), 'order' => $zone->get_zone_order(), 'regions' => ! empty( $regions ) ? implode( ', ', $regions ) : __( 'No regions', 'woonoow' ), 'rates' => $rates, ); } // Add "Rest of the World" zone (zone 0) $zone_0 = new \WC_Shipping_Zone( 0 ); $shipping_methods = $zone_0->get_shipping_methods( false ); $rates = array(); foreach ( $shipping_methods as $method ) { // Get fresh settings from database $settings = get_option( $method->get_instance_option_key(), array() ); $is_enabled = isset( $settings['enabled'] ) && $settings['enabled'] === 'yes'; $rate = array( 'id' => $method->id . ':' . $method->instance_id, 'instance_id' => $method->instance_id, 'method_id' => $method->id, 'name' => $method->get_title(), 'enabled' => $is_enabled, ); if ( isset( $method->cost ) && $method->cost !== '' ) { $rate['price'] = wc_price( $method->cost ); } elseif ( $method->id === 'free_shipping' ) { $rate['price'] = __( 'Free', 'woonoow' ); } else { $rate['price'] = __( 'Calculated', 'woonoow' ); } $rates[] = $rate; } if ( ! empty( $rates ) ) { $zones_data[] = array( 'id' => 0, 'name' => __( 'Rest of the World', 'woonoow' ), 'order' => 9999, 'regions' => __( 'Locations not covered by other zones', 'woonoow' ), 'rates' => $rates, ); } return new WP_REST_Response( $zones_data, 200 ); } catch ( \Exception $e ) { return new WP_REST_Response( array( 'error' => 'fetch_failed', 'message' => $e->getMessage(), ), 500 ); } } /** * Toggle shipping method enabled/disabled */ public function toggle_method( WP_REST_Request $request ) { try { $zone_id = $request->get_param( 'zone_id' ); $instance_id = $request->get_param( 'instance_id' ); $enabled = $request->get_param( 'enabled' ); // Convert to boolean (handles both bool and string "true"/"false") $enabled = filter_var( $enabled, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE ); if ( $enabled === null ) { return new WP_REST_Response( array( 'error' => 'invalid_enabled_value', 'message' => __( 'The "enabled" parameter must be a boolean', 'woonoow' ), ), 400 ); } // Get the zone $zone = \WC_Shipping_Zones::get_zone( $zone_id ); if ( ! $zone ) { return new WP_REST_Response( array( 'error' => 'zone_not_found', 'message' => __( 'Shipping zone not found', 'woonoow' ), ), 404 ); } // CRITICAL: Get ALL shipping methods (both enabled and disabled) $shipping_methods = $zone->get_shipping_methods( false ); $method_found = false; foreach ( $shipping_methods as $method ) { if ( $method->instance_id == $instance_id ) { $method_found = true; // Get current settings from database $option_key = $method->get_instance_option_key(); $current_settings = get_option( $option_key, array() ); // Update the enabled value $new_enabled_value = $enabled ? 'yes' : 'no'; $current_settings['enabled'] = $new_enabled_value; // Save to wp_options table update_option( $option_key, $current_settings, 'yes' ); // Also update the zone_methods table (where WooCommerce admin reads from) global $wpdb; $table_name = $wpdb->prefix . 'woocommerce_shipping_zone_methods'; $wpdb->update( $table_name, array( 'is_enabled' => $enabled ? 1 : 0 ), array( 'instance_id' => $instance_id ), array( '%d' ), array( '%d' ) ); // Clear the zone's method cache so it reloads from DB delete_transient( 'wc_shipping_method_count' ); wp_cache_delete( 'shipping_zones', 'woocommerce' ); wp_cache_delete( $zone_id, 'shipping_zones' ); \WC_Cache_Helper::get_transient_version( 'shipping', true ); // Also update the in-memory property $method->enabled = $new_enabled_value; // Fire action hook for other plugins/code do_action( 'woocommerce_shipping_zone_method_status_toggled', $instance_id, $method->id, $zone_id, $enabled ); break; } } if ( ! $method_found ) { return new WP_REST_Response( array( 'error' => 'method_not_found', 'message' => __( 'Shipping method not found', 'woonoow' ), ), 404 ); } // Clear shipping cache \WC_Cache_Helper::get_transient_version( 'shipping', true ); wp_cache_flush(); return new WP_REST_Response( array( 'success' => true, 'message' => $enabled ? __( 'Shipping method enabled', 'woonoow' ) : __( 'Shipping method disabled', 'woonoow' ), ), 200 ); } catch ( \Exception $e ) { error_log( sprintf( '[WooNooW] Toggle exception: %s', $e->getMessage() ) ); return new WP_REST_Response( array( 'error' => 'toggle_failed', 'message' => $e->getMessage(), ), 500 ); } } /** * Get available shipping methods */ public function get_available_methods( WP_REST_Request $request ) { try { $shipping = \WC()->shipping(); $methods = $shipping->get_shipping_methods(); $available = array(); $seen_titles = array(); foreach ( $methods as $method ) { $title = $method->get_method_title(); $id = $method->id; // Deduplicate by adding ID suffix if title already exists if ( isset( $seen_titles[ $title ] ) ) { $title .= ' (' . $id . ')'; } $seen_titles[ $title ] = true; $available[] = array( 'id' => $id, 'title' => $title, 'description' => $method->get_method_description(), ); } return new WP_REST_Response( $available, 200 ); } catch ( \Exception $e ) { return new WP_REST_Response( array( 'error' => 'fetch_failed', 'message' => $e->getMessage(), ), 500 ); } } /** * Add shipping method to zone */ public function add_method( WP_REST_Request $request ) { try { $zone_id = $request->get_param( 'zone_id' ); $method_id = $request->get_param( 'method_id' ); $zone = \WC_Shipping_Zones::get_zone( $zone_id ); if ( ! $zone ) { return new WP_REST_Response( array( 'error' => 'zone_not_found', 'message' => __( 'Shipping zone not found', 'woonoow' ), ), 404 ); } // Add method to zone $instance_id = $zone->add_shipping_method( $method_id ); if ( ! $instance_id ) { return new WP_REST_Response( array( 'error' => 'add_failed', 'message' => __( 'Failed to add shipping method', 'woonoow' ), ), 500 ); } // Clear caches \WC_Cache_Helper::get_transient_version( 'shipping', true ); wp_cache_flush(); return new WP_REST_Response( array( 'success' => true, 'instance_id' => $instance_id, 'message' => __( 'Shipping method added successfully', 'woonoow' ), ), 200 ); } catch ( \Exception $e ) { return new WP_REST_Response( array( 'error' => 'add_failed', 'message' => $e->getMessage(), ), 500 ); } } /** * Delete shipping method from zone */ public function delete_method( WP_REST_Request $request ) { try { $zone_id = $request->get_param( 'zone_id' ); $instance_id = $request->get_param( 'instance_id' ); $zone = \WC_Shipping_Zones::get_zone( $zone_id ); if ( ! $zone ) { return new WP_REST_Response( array( 'error' => 'zone_not_found', 'message' => __( 'Shipping zone not found', 'woonoow' ), ), 404 ); } // Delete method from zone $zone->delete_shipping_method( $instance_id ); // Clear caches \WC_Cache_Helper::get_transient_version( 'shipping', true ); wp_cache_flush(); return new WP_REST_Response( array( 'success' => true, 'message' => __( 'Shipping method deleted successfully', 'woonoow' ), ), 200 ); } catch ( \Exception $e ) { return new WP_REST_Response( array( 'error' => 'delete_failed', 'message' => $e->getMessage(), ), 500 ); } } /** * Get method settings (form fields) */ public function get_method_settings( WP_REST_Request $request ) { try { $zone_id = $request->get_param( 'zone_id' ); $instance_id = $request->get_param( 'instance_id' ); $zone = \WC_Shipping_Zones::get_zone( $zone_id ); if ( ! $zone ) { return new WP_REST_Response( array( 'error' => 'zone_not_found', 'message' => __( 'Shipping zone not found', 'woonoow' ), ), 404 ); } $shipping_methods = $zone->get_shipping_methods( false ); foreach ( $shipping_methods as $method ) { if ( $method->instance_id == $instance_id ) { // Get form fields $form_fields = $method->get_instance_form_fields(); // Get current settings $settings = get_option( $method->get_instance_option_key(), array() ); // Format fields with current values $formatted_fields = array(); foreach ( $form_fields as $key => $field ) { $formatted_fields[ $key ] = array( 'id' => $key, 'type' => $field['type'] ?? 'text', 'title' => $field['title'] ?? '', 'description' => $field['description'] ?? '', 'default' => $field['default'] ?? '', 'value' => $settings[ $key ] ?? ( $field['default'] ?? '' ), 'placeholder' => $field['placeholder'] ?? '', 'options' => $field['options'] ?? array(), ); } return new WP_REST_Response( array( 'method_id' => $method->id, 'method_title' => $method->get_method_title(), 'instance_id' => $instance_id, 'settings' => $formatted_fields, ), 200 ); } } return new WP_REST_Response( array( 'error' => 'method_not_found', 'message' => __( 'Shipping method not found', 'woonoow' ), ), 404 ); } catch ( \Exception $e ) { return new WP_REST_Response( array( 'error' => 'fetch_failed', 'message' => $e->getMessage(), ), 500 ); } } /** * Update method settings */ public function update_method_settings( WP_REST_Request $request ) { try { $zone_id = $request->get_param( 'zone_id' ); $instance_id = $request->get_param( 'instance_id' ); $settings = $request->get_param( 'settings' ); if ( empty( $settings ) || ! is_array( $settings ) ) { return new WP_REST_Response( array( 'error' => 'invalid_settings', 'message' => __( 'Invalid settings data', 'woonoow' ), ), 400 ); } $zone = \WC_Shipping_Zones::get_zone( $zone_id ); if ( ! $zone ) { return new WP_REST_Response( array( 'error' => 'zone_not_found', 'message' => __( 'Shipping zone not found', 'woonoow' ), ), 404 ); } $shipping_methods = $zone->get_shipping_methods( false ); foreach ( $shipping_methods as $method ) { if ( $method->instance_id == $instance_id ) { // Get current settings $option_key = $method->get_instance_option_key(); $current_settings = get_option( $option_key, array() ); // Merge with new settings $new_settings = array_merge( $current_settings, $settings ); // Save to database update_option( $option_key, $new_settings, 'yes' ); // Clear caches \WC_Cache_Helper::get_transient_version( 'shipping', true ); wp_cache_flush(); return new WP_REST_Response( array( 'success' => true, 'message' => __( 'Shipping method settings updated successfully', 'woonoow' ), ), 200 ); } } return new WP_REST_Response( array( 'error' => 'method_not_found', 'message' => __( 'Shipping method not found', 'woonoow' ), ), 404 ); } catch ( \Exception $e ) { return new WP_REST_Response( array( 'error' => 'update_failed', 'message' => $e->getMessage(), ), 500 ); } } /** * Check if user has permission to manage shipping */ public function check_permission() { return current_user_can( 'manage_woocommerce' ); } }