diff --git a/includes/abstracts/abstract-wc-data.php b/includes/abstracts/abstract-wc-data.php index c526057fde2..1e8b0bcec57 100644 --- a/includes/abstracts/abstract-wc-data.php +++ b/includes/abstracts/abstract-wc-data.php @@ -480,6 +480,14 @@ abstract class WC_Data { $this->object_read = (bool) $read; } + /** + * Get object read property. + * @return boolean + */ + public function get_object_read() { + return (bool) $this->object_read; + } + /** * Set a collection of props in one go, collect any errors, and return the result. * Only sets using public methods. diff --git a/includes/admin/settings/class-wc-settings-shipping.php b/includes/admin/settings/class-wc-settings-shipping.php index 90fb4e54d37..2ad44230293 100644 --- a/includes/admin/settings/class-wc-settings-shipping.php +++ b/includes/admin/settings/class-wc-settings-shipping.php @@ -235,7 +235,7 @@ class WC_Settings_Shipping extends WC_Settings_Page { wp_localize_script( 'wc-shipping-zone-methods', 'shippingZoneMethodsLocalizeScript', array( 'methods' => $zone->get_shipping_methods(), 'zone_name' => $zone->get_zone_name(), - 'zone_id' => $zone->get_zone_id(), + 'zone_id' => $zone->get_id(), 'wc_shipping_zones_nonce' => wp_create_nonce( 'wc_shipping_zones_nonce' ), 'strings' => array( 'unload_confirmation_msg' => __( 'Your changed data will be lost if you leave this page without saving.', 'woocommerce' ), diff --git a/includes/admin/settings/views/html-admin-page-shipping-zone-methods.php b/includes/admin/settings/views/html-admin-page-shipping-zone-methods.php index dc1274cd6af..34d3d483fd2 100644 --- a/includes/admin/settings/views/html-admin-page-shipping-zone-methods.php +++ b/includes/admin/settings/views/html-admin-page-shipping-zone-methods.php @@ -13,7 +13,7 @@ if ( ! defined( 'ABSPATH' ) ) { - get_zone_id() ) : ?> + get_id() ) : ?> diff --git a/includes/class-wc-ajax.php b/includes/class-wc-ajax.php index 29e6c799453..5a40218db67 100644 --- a/includes/class-wc-ajax.php +++ b/includes/class-wc-ajax.php @@ -2469,7 +2469,7 @@ class WC_AJAX { wp_send_json_success( array( 'instance_id' => $instance_id, - 'zone_id' => $zone->get_zone_id(), + 'zone_id' => $zone->get_id(), 'zone_name' => $zone->get_zone_name(), 'methods' => $zone->get_shipping_methods(), ) ); @@ -2567,7 +2567,7 @@ class WC_AJAX { $zone->save(); wp_send_json_success( array( - 'zone_id' => $zone->get_zone_id(), + 'zone_id' => $zone->get_id(), 'zone_name' => $zone->get_zone_name(), 'methods' => $zone->get_shipping_methods(), ) ); @@ -2599,7 +2599,7 @@ class WC_AJAX { $shipping_method->process_admin_options(); wp_send_json_success( array( - 'zone_id' => $zone->get_zone_id(), + 'zone_id' => $zone->get_id(), 'zone_name' => $zone->get_zone_name(), 'methods' => $zone->get_shipping_methods(), 'errors' => $shipping_method->get_errors(), diff --git a/includes/class-wc-data-store.php b/includes/class-wc-data-store.php index bec1419fe2b..08d7a0d05bc 100644 --- a/includes/class-wc-data-store.php +++ b/includes/class-wc-data-store.php @@ -29,13 +29,14 @@ class WC_Data_Store { */ private $stores = array( 'coupon' => 'WC_Coupon_Data_Store_CPT', - 'payment-token' => 'WC_Payment_Token_Data_Store_Table', 'product' => 'WC_Product_Data_Store_CPT', 'product_grouped' => 'WC_Product_Grouped_Data_Store_CPT', 'product_variable' => 'WC_Product_Variable_Data_Store_CPT', 'product_variation' => 'WC_Product_Variation_Data_Store_CPT', 'customer' => 'WC_Customer_Data_Store', 'customer-session' => 'WC_Customer_Data_Store_Session', + 'payment-token' => 'WC_Payment_Token_Data_Store', + 'shipping-zone' => 'WC_Shipping_Zone_Data_Store', ); /** diff --git a/includes/class-wc-shipping-zone.php b/includes/class-wc-shipping-zone.php index 32a25abe056..9a6f9d6ad38 100644 --- a/includes/class-wc-shipping-zone.php +++ b/includes/class-wc-shipping-zone.php @@ -1,4 +1,5 @@ read( $zone ); + $this->set_id( $zone ); } elseif ( is_object( $zone ) ) { $this->set_id( $zone->zone_id ); - $this->set_zone_name( $zone->zone_name ); - $this->set_zone_order( $zone->zone_order ); - $this->read_zone_locations( $zone->zone_id ); } elseif ( 0 === $zone || "0" === $zone ) { $this->set_id( 0 ); - $this->set_zone_name( __( 'Rest of the World', 'woocommerce' ) ); - $this->read_zone_locations( 0 ); } else { $this->set_zone_name( __( 'Zone', 'woocommerce' ) ); + $this->set_object_read( true ); + } + + $this->data_store = WC_Data_Store::load( 'shipping-zone' ); + if ( false === $this->get_object_read() ) { + $this->data_store->read( $this ); } } - /** - * Returns all data for this object. - * @return array + /* + |-------------------------------------------------------------------------- + | Getters + |-------------------------------------------------------------------------- */ - public function get_data() { - return array_merge( array( 'id' => $this->get_id(), 'zone_id' => $this->get_id() ), $this->data, array( 'meta_data' => $this->get_meta_data() ) ); - } /** - * Insert zone into the database - */ - public function create() { - global $wpdb; - $wpdb->insert( $wpdb->prefix . 'woocommerce_shipping_zones', array( - 'zone_name' => $this->get_zone_name(), - 'zone_order' => $this->get_zone_order(), - ) ); - $this->set_id( $wpdb->insert_id ); - } - - /** - * Read zone. - * @param int ID to read from DB - */ - public function read( $id ) { - global $wpdb; - - if ( $zone_data = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_shipping_zones WHERE zone_id = %d LIMIT 1;", $id ) ) ) { - $this->set_id( $zone_data->zone_id ); - $this->set_zone_name( $zone_data->zone_name ); - $this->set_zone_order( $zone_data->zone_order ); - $this->read_zone_locations( $zone_data->zone_id ); - } - } - - /** - * Update zone in the database - */ - public function update() { - global $wpdb; - - if ( $this->get_id() ) { - $wpdb->update( $wpdb->prefix . 'woocommerce_shipping_zones', array( - 'zone_name' => $this->get_zone_name(), - 'zone_order' => $this->get_zone_order(), - ), array( 'zone_id' => $this->get_id() ) ); - } - } - - /** - * Delete a zone. - * @since 2.6.0 - */ - public function delete( $force_delete = false ) { - if ( $this->get_id() ) { - global $wpdb; - $wpdb->delete( $wpdb->prefix . 'woocommerce_shipping_zone_methods', array( 'zone_id' => $this->get_id() ) ); - $wpdb->delete( $wpdb->prefix . 'woocommerce_shipping_zone_locations', array( 'zone_id' => $this->get_id() ) ); - $wpdb->delete( $wpdb->prefix . 'woocommerce_shipping_zones', array( 'zone_id' => $this->get_id() ) ); - WC_Cache_Helper::incr_cache_prefix( 'shipping_zones' ); - $this->set_id( null ); - } - } - - /** - * Save zone data to the database. - */ - public function save() { - $name = $this->get_zone_name(); - - if ( empty( $name ) ) { - $this->set_zone_name( $this->generate_zone_name() ); - } - - if ( null === $this->get_id() ) { - $this->create(); - } else { - $this->update(); - } - - $this->save_locations(); - WC_Cache_Helper::incr_cache_prefix( 'shipping_zones' ); - - // Increments the transient version to invalidate cache. - WC_Cache_Helper::get_transient_version( 'shipping', true ); - } - - /** - * Set ID. - * @param int|null $id - */ - public function set_id( $id ) { - $this->id = $id; - } - - /** - * Get ID - * @return int|null Null if the zone does not exist. 0 is the default zone. - */ - public function get_id() { - return is_null( $this->id ) ? null : absint( $this->id ); - } - - /** - * Get zone ID - * @return int|null Null if the zone does not exist. 0 is the default zone. - */ - public function get_zone_id() { - return $this->get_id(); - } - - /** - * Get zone name + * Get zone name. + * + * @param string $context * @return string */ - public function get_zone_name() { - return $this->data['zone_name']; + public function get_zone_name( $context = 'view' ) { + return $this->get_prop( 'zone_name', $context ); } /** - * Get zone order + * Get zone order. + * + * @param string $context * @return int */ - public function get_zone_order() { - return absint( $this->data['zone_order'] ); + public function get_zone_order( $context = 'view' ) { + return $this->get_prop( 'zone_order', $context ); } /** - * Get zone locations + * Get zone locations. + * + * @param string $context * @return array of zone objects */ - public function get_zone_locations() { - return $this->data['zone_locations']; - } - - /** - * Generate a zone name based on location. - * @return string - */ - protected function generate_zone_name() { - $zone_name = $this->get_formatted_location(); - - if ( empty( $zone_name ) ) { - $zone_name = __( 'Zone', 'woocommerce' ); - } - - return $zone_name; + public function get_zone_locations( $context = 'view' ) { + return $this->get_prop( 'zone_locations', $context ); } /** * Return a text string representing what this zone is for. + * + * @param int $max + * @param string $context * @return string */ - public function get_formatted_location( $max = 10 ) { + public function get_formatted_location( $max = 10, $context = 'view' ) { $location_parts = array(); $all_continents = WC()->countries->get_continents(); $all_countries = WC()->countries->get_countries(); $all_states = WC()->countries->get_states(); - $locations = $this->get_zone_locations(); + $locations = $this->get_zone_locations( $context ); $continents = array_filter( $locations, array( $this, 'location_is_continent' ) ); $countries = array_filter( $locations, array( $this, 'location_is_country' ) ); $states = array_filter( $locations, array( $this, 'location_is_state' ) ); @@ -251,19 +143,17 @@ class WC_Shipping_Zone extends WC_Data { } /** - * Get shipping methods linked to this zone + * Get shipping methods linked to this zone. + * * @param bool Only return enabled methods. * @return array of objects */ public function get_shipping_methods( $enabled_only = false ) { - global $wpdb; - if ( null === $this->get_id() ) { return array(); } - $raw_methods_sql = $enabled_only ? "SELECT method_id, method_order, instance_id, is_enabled FROM {$wpdb->prefix}woocommerce_shipping_zone_methods WHERE zone_id = %d AND is_enabled = 1;" : "SELECT method_id, method_order, instance_id, is_enabled FROM {$wpdb->prefix}woocommerce_shipping_zone_methods WHERE zone_id = %d;"; - $raw_methods = $wpdb->get_results( $wpdb->prepare( $raw_methods_sql, $this->get_id() ) ); + $raw_methods = $this->data_store->get_methods( $this->get_id(), $enabled_only ); $wc_shipping = WC_Shipping::instance(); $allowed_classes = $wc_shipping->get_shipping_method_class_names(); $methods = array(); @@ -302,8 +192,84 @@ class WC_Shipping_Zone extends WC_Data { return apply_filters( 'woocommerce_shipping_zone_shipping_methods', $methods, $raw_methods, $allowed_classes, $this ); } + /* + |-------------------------------------------------------------------------- + | Setters + |-------------------------------------------------------------------------- + */ + /** - * Location type detection + * Set zone name. + * + * @param string $set + */ + public function set_zone_name( $set ) { + $this->set_prop( 'zone_name', wc_clean( $set ) ); + } + + /** + * Set zone order. + * + * @param int $set + */ + public function set_zone_order( $set ) { + $this->set_prop( 'zone_order', absint( $set ) ); + } + + /** + * Set zone locations. + * + * @since 2.7.0 + * @param array + */ + public function set_zone_locations( $locations ) { + $this->set_prop( 'zone_locations', $locations ); + } + + /* + |-------------------------------------------------------------------------- + | Other Methods + |-------------------------------------------------------------------------- + */ + + /** + * Save zone data to the database. + * + * @return int + */ + public function save() { + $name = $this->get_zone_name(); + if ( empty( $name ) ) { + $this->set_zone_name( $this->generate_zone_name() ); + } + if ( $this->data_store ) { + if ( null === $this->get_id() ) { + $this->data_store->create( $this ); + } else { + $this->data_store->update( $this ); + } + return $this->get_id(); + } + } + + /** + * Generate a zone name based on location. + * + * @return string + */ + protected function generate_zone_name() { + $zone_name = $this->get_formatted_location(); + + if ( empty( $zone_name ) ) { + $zone_name = __( 'Zone', 'woocommerce' ); + } + + return $zone_name; + } + + /** + * Location type detection. + * * @param object $location * @return boolean */ @@ -312,7 +278,8 @@ class WC_Shipping_Zone extends WC_Data { } /** - * Location type detection + * Location type detection. + * * @param object $location * @return boolean */ @@ -321,7 +288,8 @@ class WC_Shipping_Zone extends WC_Data { } /** - * Location type detection + * Location type detection. + * * @param object $location * @return boolean */ @@ -330,7 +298,8 @@ class WC_Shipping_Zone extends WC_Data { } /** - * Location type detection + * Location type detection. + * * @param object $location * @return boolean */ @@ -338,24 +307,9 @@ class WC_Shipping_Zone extends WC_Data { return 'postcode' === $location->type; } - /** - * Set zone name - * @param string $set - */ - public function set_zone_name( $set ) { - $this->data['zone_name'] = wc_clean( $set ); - } - - /** - * Set zone order - * @param int $set - */ - public function set_zone_order( $set ) { - $this->data['zone_order'] = absint( $set ); - } - /** * Is passed location type valid? + * * @param string $type * @return boolean */ @@ -365,6 +319,7 @@ class WC_Shipping_Zone extends WC_Data { /** * Add location (state or postcode) to a zone. + * * @param string $code * @param string $type state or postcode */ @@ -377,85 +332,50 @@ class WC_Shipping_Zone extends WC_Data { 'code' => wc_clean( $code ), 'type' => wc_clean( $type ), ); - $this->data['zone_locations'][] = (object) $location; - $this->locations_changed = true; + $zone_locations = $this->get_prop( 'zone_locations', 'edit' ); + $zone_locations[] = (object) $location; + $this->set_prop( 'zone_locations', $zone_locations ); } } + /** * Clear all locations for this zone. + * * @param array|string $types of location to clear */ public function clear_locations( $types = array( 'postcode', 'state', 'country', 'continent' ) ) { if ( ! is_array( $types ) ) { $types = array( $types ); } - foreach ( $this->data['zone_locations'] as $key => $values ) { + $zone_locations = $this->get_prop( 'zone_locations', 'edit' ); + foreach ( $zone_locations as $key => $values ) { if ( in_array( $values->type, $types ) ) { - unset( $this->data['zone_locations'][ $key ] ); - $this->locations_changed = true; + unset( $zone_locations[ $key ] ); } } + $this->set_prop( 'zone_locations', $zone_locations ); } /** - * Set locations + * Set locations. + * * @param array $locations Array of locations */ public function set_locations( $locations = array() ) { $this->clear_locations(); - foreach ( $locations as $location ) { $this->add_location( $location['code'], $location['type'] ); } - - $this->locations_changed = true; - } - - /** - * Read location data from the database - * @param int $zone_id - */ - private function read_zone_locations( $zone_id ) { - global $wpdb; - - if ( $locations = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_shipping_zone_locations WHERE zone_id = %d;", $zone_id ) ) ) { - foreach ( $locations as $location ) { - $this->add_location( $location->location_code, $location->location_type ); - } - } - $this->locations_changed = false; - } - - /** - * Save locations to the DB. - * - * This function clears old locations, then re-inserts new if any changes are found. - */ - private function save_locations() { - if ( ! $this->locations_changed || null === $this->get_id() ) { - return false; - } - global $wpdb; - $wpdb->delete( $wpdb->prefix . 'woocommerce_shipping_zone_locations', array( 'zone_id' => $this->get_id() ) ); - - foreach ( $this->get_zone_locations() as $location ) { - $wpdb->insert( $wpdb->prefix . 'woocommerce_shipping_zone_locations', array( - 'zone_id' => $this->get_id(), - 'location_code' => $location->code, - 'location_type' => $location->type, - ) ); - } } /** * Add a shipping method to this zone. + * * @param string $type shipping method type * @return int new instance_id, 0 on failure */ public function add_shipping_method( $type ) { - global $wpdb; - if ( null === $this->get_id() ) { $this->save(); } @@ -463,23 +383,10 @@ class WC_Shipping_Zone extends WC_Data { $instance_id = 0; $wc_shipping = WC_Shipping::instance(); $allowed_classes = $wc_shipping->get_shipping_method_class_names(); - $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}woocommerce_shipping_zone_methods WHERE zone_id = %d", $this->get_id() ) ); + $count = $this->data_store->get_method_count( $this->get_id() ); if ( in_array( $type, array_keys( $allowed_classes ) ) ) { - $wpdb->insert( - $wpdb->prefix . 'woocommerce_shipping_zone_methods', - array( - 'method_id' => $type, - 'zone_id' => $this->get_id(), - 'method_order' => ( $count + 1 ), - ), - array( - '%s', - '%d', - '%d', - ) - ); - $instance_id = $wpdb->insert_id; + $instance_id = $this->data_store->add_method( $this->get_id(), $type, $count + 1 ); } if ( $instance_id ) { @@ -493,17 +400,16 @@ class WC_Shipping_Zone extends WC_Data { /** * Delete a shipping method from a zone. + * * @param int $instance_id * @return True on success, false on failure */ public function delete_shipping_method( $instance_id ) { - global $wpdb; - if ( null === $this->get_id() ) { return false; } - $wpdb->delete( $wpdb->prefix . 'woocommerce_shipping_zone_methods', array( 'instance_id' => $instance_id ) ); + $this->data_store->delete_method( $instance_id ); do_action( 'woocommerce_shipping_zone_method_deleted', $instance_id, $this->get_id() ); WC_Cache_Helper::get_transient_version( 'shipping', true ); diff --git a/includes/class-wc-shipping-zones.php b/includes/class-wc-shipping-zones.php index 003c510e564..d64d82b5075 100644 --- a/includes/class-wc-shipping-zones.php +++ b/includes/class-wc-shipping-zones.php @@ -9,10 +9,10 @@ if ( ! defined( 'ABSPATH' ) ) { * * @class WC_Shipping_Zones * @since 2.6.0 - * @version 2.6.0 + * @version 2.7.0 * @package WooCommerce/Classes * @category Class - * @author WooThemes + * @author WooCommerce */ class WC_Shipping_Zones { @@ -22,16 +22,16 @@ class WC_Shipping_Zones { * @return array of arrays */ public static function get_zones() { - global $wpdb; - - $raw_zones = $wpdb->get_results( "SELECT zone_id, zone_name, zone_order FROM {$wpdb->prefix}woocommerce_shipping_zones order by zone_order ASC;" ); - $zones = array(); + $data_store = WC_Data_Store::load( 'shipping-zone' ); + $raw_zones = $data_store->get_zones(); + $zones = array(); foreach ( $raw_zones as $raw_zone ) { $zone = new WC_Shipping_Zone( $raw_zone ); - $zones[ $zone->get_zone_id() ] = $zone->get_data(); - $zones[ $zone->get_zone_id() ]['formatted_zone_location'] = $zone->get_formatted_location(); - $zones[ $zone->get_zone_id() ]['shipping_methods'] = $zone->get_shipping_methods(); + $zones[ $zone->get_id() ] = $zone->get_data(); + $zones[ $zone->get_id() ]['zone_id'] = $zone->get_id(); + $zones[ $zone->get_id() ]['formatted_zone_location'] = $zone->get_formatted_location(); + $zones[ $zone->get_id() ]['shipping_methods'] = $zone->get_shipping_methods(); } return $zones; @@ -55,28 +55,25 @@ class WC_Shipping_Zones { * @return WC_Shipping_Zone|bool */ public static function get_zone_by( $by = 'zone_id', $id = 0 ) { - global $wpdb; - - $raw_zone = false; - switch ( $by ) { case 'zone_id' : - if ( 0 === $id ) { - return new WC_Shipping_Zone( 0 ); - } else { - $raw_zone = $wpdb->get_row( $wpdb->prepare( "SELECT zone_id, zone_name, zone_order FROM {$wpdb->prefix}woocommerce_shipping_zones WHERE zone_id = %d LIMIT 1;", $id ) ); - } + $zone_id = $id; break; case 'instance_id' : - $zone_id = $wpdb->get_var( $wpdb->prepare( "SELECT zone_id FROM {$wpdb->prefix}woocommerce_shipping_zone_methods as methods WHERE methods.instance_id = %d LIMIT 1;", $id ) ); - - if ( false !== $zone_id ) { - return self::get_zone_by( 'zone_id', absint( $zone_id ) ); - } + $data_store = WC_Data_Store::load( 'shipping-zone' ); + $zone_id = $data_store->get_zone_id_by_instance_id( $id ); break; } - return $raw_zone ? new WC_Shipping_Zone( $raw_zone ) : false; + if ( false !== $zone_id ) { + try { + return new WC_Shipping_Zone( $zone_id ); + } catch ( Exception $e ) { + return false; + } + } + + return false; } /** @@ -85,8 +82,8 @@ class WC_Shipping_Zones { * @return WC_Shipping_Meethod|bool */ public static function get_shipping_method( $instance_id ) { - global $wpdb; - $raw_shipping_method = $wpdb->get_row( $wpdb->prepare( "SELECT instance_id, method_id FROM {$wpdb->prefix}woocommerce_shipping_zone_methods WHERE instance_id = %d LIMIT 1;", $instance_id ) ); + $data_store = WC_Data_Store::load( 'shipping-zone' ); + $raw_shipping_method = $data_store->get_method( $instance_id ); $wc_shipping = WC_Shipping::instance(); $allowed_classes = $wc_shipping->get_shipping_method_class_names(); @@ -118,8 +115,6 @@ class WC_Shipping_Zones { * @return WC_Shipping_Zone */ public static function get_zone_matching_package( $package ) { - global $wpdb; - $country = strtoupper( wc_clean( $package['destination']['country'] ) ); $state = strtoupper( wc_clean( $package['destination']['state'] ) ); $continent = strtoupper( wc_clean( WC()->countries->get_continent_code_for_country( $country ) ) ); @@ -128,35 +123,8 @@ class WC_Shipping_Zones { $matching_zone_id = wp_cache_get( $cache_key, 'shipping_zones' ); if ( false === $matching_zone_id ) { - - // Work out criteria for our zone search - $criteria = array(); - $criteria[] = $wpdb->prepare( "( ( location_type = 'country' AND location_code = %s )", $country ); - $criteria[] = $wpdb->prepare( "OR ( location_type = 'state' AND location_code = %s )", $country . ':' . $state ); - $criteria[] = $wpdb->prepare( "OR ( location_type = 'continent' AND location_code = %s )", $continent ); - $criteria[] = "OR ( location_type IS NULL ) )"; - - // Postcode range and wildcard matching - $postcode_locations = $wpdb->get_results( "SELECT zone_id, location_code FROM {$wpdb->prefix}woocommerce_shipping_zone_locations WHERE location_type = 'postcode';" ); - - if ( $postcode_locations ) { - $zone_ids_with_postcode_rules = array_map( 'absint', wp_list_pluck( $postcode_locations, 'zone_id' ) ); - $matches = wc_postcode_location_matcher( $postcode, $postcode_locations, 'zone_id', 'location_code', $country ); - $do_not_match = array_unique( array_diff( $zone_ids_with_postcode_rules, array_keys( $matches ) ) ); - - if ( ! empty( $do_not_match ) ) { - $criteria[] = "AND zones.zone_id NOT IN (" . implode( ',', $do_not_match ) . ")"; - } - } - - // Get matching zones - $matching_zone_id = $wpdb->get_var( " - SELECT zones.zone_id FROM {$wpdb->prefix}woocommerce_shipping_zones as zones - LEFT OUTER JOIN {$wpdb->prefix}woocommerce_shipping_zone_locations as locations ON zones.zone_id = locations.zone_id AND location_type != 'postcode' - WHERE " . implode( ' ', $criteria ) . " - ORDER BY zone_order ASC LIMIT 1 - " ); - + $data_store = WC_Data_Store::load( 'shipping-zone' ); + $matching_zone_id = $data_store->get_zone_id_from_package( $package ); wp_cache_set( $cache_key, $matching_zone_id, 'shipping_zones' ); } diff --git a/includes/data-stores/class-wc-payment-token-data-store-table.php b/includes/data-stores/class-wc-payment-token-data-store.php similarity index 98% rename from includes/data-stores/class-wc-payment-token-data-store-table.php rename to includes/data-stores/class-wc-payment-token-data-store.php index 442ac1844a5..bbb1a8f3d4b 100644 --- a/includes/data-stores/class-wc-payment-token-data-store-table.php +++ b/includes/data-stores/class-wc-payment-token-data-store.php @@ -10,7 +10,7 @@ if ( ! defined( 'ABSPATH' ) ) { * @category Class * @author WooThemes */ -class WC_Payment_Token_Data_Store_Table implements WC_Payment_Token_Data_Store_Interface, WC_Object_Data_Store { +class WC_Payment_Token_Data_Store implements WC_Payment_Token_Data_Store_Interface, WC_Object_Data_Store { /** * Create a new payment token in the database. diff --git a/includes/data-stores/class-wc-shipping-zone-data-store.php b/includes/data-stores/class-wc-shipping-zone-data-store.php new file mode 100644 index 00000000000..04bd3d6ebbb --- /dev/null +++ b/includes/data-stores/class-wc-shipping-zone-data-store.php @@ -0,0 +1,284 @@ +insert( $wpdb->prefix . 'woocommerce_shipping_zones', array( + 'zone_name' => $zone->get_zone_name(), + 'zone_order' => $zone->get_zone_order(), + ) ); + $zone->set_id( $wpdb->insert_id ); + $zone->save_meta_data(); + $this->save_locations( $zone ); + $zone->apply_changes(); + WC_Cache_Helper::incr_cache_prefix( 'shipping_zones' ); + WC_Cache_Helper::get_transient_version( 'shipping', true ); + } + + /** + * Update zone in the database. + * + * @since 2.7.0 + * @param WC_Shipping_Zone + */ + public function update( &$zone ) { + global $wpdb; + if ( $zone->get_id() ) { + $wpdb->update( $wpdb->prefix . 'woocommerce_shipping_zones', array( + 'zone_name' => $zone->get_zone_name(), + 'zone_order' => $zone->get_zone_order(), + ), array( 'zone_id' => $zone->get_id() ) ); + } + $zone->save_meta_data(); + $this->save_locations( $zone ); + $zone->apply_changes(); + WC_Cache_Helper::incr_cache_prefix( 'shipping_zones' ); + WC_Cache_Helper::get_transient_version( 'shipping', true ); + } + + /** + * Method to read a shipping zone from the database. + * + * @since 2.7.0 + * @param WC_Shipping_Zone + */ + public function read( &$zone ) { + global $wpdb; + if ( 0 === $zone->get_id() || "0" === $zone->get_id() ) { + $this->read_zone_locations( $zone ); + $zone->set_zone_name( __( 'Rest of the World', 'woocommerce' ) ); + $zone->read_meta_data(); + $zone->set_object_read( true ); + do_action( 'woocommerce_shipping_zone_loaded', $zone ); + } elseif ( $zone_data = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_shipping_zones WHERE zone_id = %d LIMIT 1;", $zone->get_id() ) ) ) { + $zone->set_zone_name( $zone_data->zone_name ); + $zone->set_zone_order( $zone_data->zone_order ); + $this->read_zone_locations( $zone ); + $zone->read_meta_data(); + $zone->set_object_read( true ); + do_action( 'woocommerce_shipping_zone_loaded', $zone ); + } else { + throw new Exception( __( 'Invalid data store.', 'woocommerce' ) ); + } + } + + /** + * Deletes a shipping zone from the database. + * + * @since 2.7.0 + * @param WC_Shipping_Zone + * @param array $args Array of args to pass to the delete method. + * @return bool result + */ + public function delete( &$zone, $args = array() ) { + if ( $zone->get_id() ) { + global $wpdb; + $wpdb->delete( $wpdb->prefix . 'woocommerce_shipping_zone_methods', array( 'zone_id' => $zone->get_id() ) ); + $wpdb->delete( $wpdb->prefix . 'woocommerce_shipping_zone_locations', array( 'zone_id' => $zone->get_id() ) ); + $wpdb->delete( $wpdb->prefix . 'woocommerce_shipping_zones', array( 'zone_id' => $zone->get_id() ) ); + WC_Cache_Helper::incr_cache_prefix( 'shipping_zones' ); + $zone->set_id( null ); + } + } + + /** + * Get a list of shipping methods for a specific zone. + * + * @since 2.7.0 + * @param int $zone_id Zone ID + * @param bool $enabled_only True to request enabled methods only. + * @return array Array of objects containing method_id, method_order, instance_id, is_enabled + */ + public function get_methods( $zone_id, $enabled_only ) { + global $wpdb; + $raw_methods_sql = $enabled_only ? "SELECT method_id, method_order, instance_id, is_enabled FROM {$wpdb->prefix}woocommerce_shipping_zone_methods WHERE zone_id = %d AND is_enabled = 1;" : "SELECT method_id, method_order, instance_id, is_enabled FROM {$wpdb->prefix}woocommerce_shipping_zone_methods WHERE zone_id = %d;"; + return $wpdb->get_results( $wpdb->prepare( $raw_methods_sql, $zone_id ) ); + } + + /** + * Get count of methods for a zone. + * + * @since 2.7.0 + * @param int Zone ID + * @return int Method Count + */ + public function get_method_count( $zone_id ) { + global $wpdb; + return $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}woocommerce_shipping_zone_methods WHERE zone_id = %d", $zone_id ) ); + } + + /** + * Add a shipping method to a zone. + * + * @since 2.7.0 + * @param int $zone_id Zone ID + * @param string $type Method Type/ID + * @param int $order Method Order + * @return int Instance ID + */ + public function add_method( $zone_id, $type, $order ) { + global $wpdb; + $wpdb->insert( + $wpdb->prefix . 'woocommerce_shipping_zone_methods', + array( + 'method_id' => $type, + 'zone_id' => $zone_id, + 'method_order' => $order, + ), + array( + '%s', + '%d', + '%d', + ) + ); + return $wpdb->insert_id; + } + + /** + * Delete a method instance. + * + * @since 2.7.0 + * @param int $instance_id + */ + public function delete_method( $instance_id ) { + global $wpdb; + $wpdb->delete( $wpdb->prefix . 'woocommerce_shipping_zone_methods', array( 'instance_id' => $instance_id ) ); + } + + /** + * Get a shipping zone method instance. + * + * @since 2.7.0 + * @param int + * @return object + */ + public function get_method( $instance_id ) { + global $wpdb; + return $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_shipping_zone_methods WHERE instance_id = %d LIMIT 1;", $instance_id ) ); + } + + /** + * Find a matching zone ID for a given package. + * + * @since 2.7.0 + * @param object $package + * @return int + */ + public function get_zone_id_from_package( $package ) { + global $wpdb; + + $country = strtoupper( wc_clean( $package['destination']['country'] ) ); + $state = strtoupper( wc_clean( $package['destination']['state'] ) ); + $continent = strtoupper( wc_clean( WC()->countries->get_continent_code_for_country( $country ) ) ); + $postcode = wc_normalize_postcode( wc_clean( $package['destination']['postcode'] ) ); + + // Work out criteria for our zone search + $criteria = array(); + $criteria[] = $wpdb->prepare( "( ( location_type = 'country' AND location_code = %s )", $country ); + $criteria[] = $wpdb->prepare( "OR ( location_type = 'state' AND location_code = %s )", $country . ':' . $state ); + $criteria[] = $wpdb->prepare( "OR ( location_type = 'continent' AND location_code = %s )", $continent ); + $criteria[] = "OR ( location_type IS NULL ) )"; + + // Postcode range and wildcard matching + $postcode_locations = $wpdb->get_results( "SELECT zone_id, location_code FROM {$wpdb->prefix}woocommerce_shipping_zone_locations WHERE location_type = 'postcode';" ); + + if ( $postcode_locations ) { + $zone_ids_with_postcode_rules = array_map( 'absint', wp_list_pluck( $postcode_locations, 'zone_id' ) ); + $matches = wc_postcode_location_matcher( $postcode, $postcode_locations, 'zone_id', 'location_code', $country ); + $do_not_match = array_unique( array_diff( $zone_ids_with_postcode_rules, array_keys( $matches ) ) ); + + if ( ! empty( $do_not_match ) ) { + $criteria[] = "AND zones.zone_id NOT IN (" . implode( ',', $do_not_match ) . ")"; + } + } + + // Get matching zones + return $wpdb->get_var( " + SELECT zones.zone_id FROM {$wpdb->prefix}woocommerce_shipping_zones as zones + LEFT OUTER JOIN {$wpdb->prefix}woocommerce_shipping_zone_locations as locations ON zones.zone_id = locations.zone_id AND location_type != 'postcode' + WHERE " . implode( ' ', $criteria ) . " + ORDER BY zone_order ASC LIMIT 1 + " ); + } + + /** + * Return an ordered list of zones. + * + * @since 2.7.0 + * @return array An array of objects containing a zone_id, zone_name, and zone_order. + */ + public function get_zones() { + global $wpdb; + return $wpdb->get_results( "SELECT zone_id, zone_name, zone_order FROM {$wpdb->prefix}woocommerce_shipping_zones order by zone_order ASC;" ); + } + + + /** + * Return a zone ID from an instance ID. + * + * @since 2.7.0 + * @param int + * @return int + */ + public function get_zone_id_by_instance_id( $id ) { + global $wpdb; + return $wpdb->get_var( $wpdb->prepare( "SELECT zone_id FROM {$wpdb->prefix}woocommerce_shipping_zone_methods as methods WHERE methods.instance_id = %d LIMIT 1;", $id ) ); + } + + /** + * Read location data from the database. + * + * @param WC_Shipping_Zone + */ + private function read_zone_locations( &$zone ) { + global $wpdb; + if ( $locations = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_shipping_zone_locations WHERE zone_id = %d;", $zone->get_id() ) ) ) { + foreach ( $locations as $location ) { + $zone->add_location( $location->location_code, $location->location_type ); + } + } + } + + /** + * Save locations to the DB. + * This function clears old locations, then re-inserts new if any changes are found. + * + * @since 2.7.0 + * @param WC_Shipping_Zone + */ + private function save_locations( &$zone ) { + $updated_props = array(); + $changed_props = array_keys( $zone->get_changes() ); + if ( ! in_array( 'zone_locations', $changed_props ) ) { + return false; + } + + global $wpdb; + $wpdb->delete( $wpdb->prefix . 'woocommerce_shipping_zone_locations', array( 'zone_id' => $zone->get_id() ) ); + + foreach ( $zone->get_zone_locations( 'edit' ) as $location ) { + $wpdb->insert( $wpdb->prefix . 'woocommerce_shipping_zone_locations', array( + 'zone_id' => $zone->get_id(), + 'location_code' => $location->code, + 'location_type' => $location->type, + ) ); + } + } +} diff --git a/includes/data-stores/interfaces/class-wc-shipping-zone-data-store-interface.php b/includes/data-stores/interfaces/class-wc-shipping-zone-data-store-interface.php new file mode 100644 index 00000000000..2b5a66434b6 --- /dev/null +++ b/includes/data-stores/interfaces/class-wc-shipping-zone-data-store-interface.php @@ -0,0 +1,72 @@ +get_id(); + } + + /** + * Read a shipping zone by ID. + * @deprecated 2.7.0 - Init a shipping zone with an ID. + */ + public function read( $zone_id ) { + //wc_soft_deprecated_function( 'WC_Shipping_Zone::read', '2.7', '2.8', 'Init a shipping zone with an ID.' ); + $this->set_id( $zone_id ); + $data_store = WC_Data_Store::load( 'shipping-zone' ); + $data_store->read( $this ); + } + + /** + * Update a zone. + * @deprecated 2.7.0 - Use ::save instead. + */ + public function update() { + //wc_soft_deprecated_function( 'WC_Shipping_Zone::update', '2.7', '2.8', 'Use ::save instead.' ); + $data_store = WC_Data_Store::load( 'shipping-zone' ); + try { + $data_store->update( $this ); + } catch ( Exception $e ) { + return false; + } + } + + /** + * Create a zone. + * @deprecated 2.7.0 - Use ::save instead. + */ + public function create() { + //wc_soft_deprecated_function( 'WC_Shipping_Zone::create', '2.7', '2.8', 'Use ::save instead.' ); + $data_store = WC_Data_Store::load( 'shipping-zone' ); + try { + $data_store->create( $this ); + } catch ( Exception $e ) { + return false; + } + } + + +} diff --git a/tests/unit-tests/api/shipping-zones.php b/tests/unit-tests/api/shipping-zones.php index 1a424650745..710b8694297 100644 --- a/tests/unit-tests/api/shipping-zones.php +++ b/tests/unit-tests/api/shipping-zones.php @@ -286,7 +286,7 @@ class WC_Tests_API_Shipping_Zones extends WC_REST_Unit_Test_Case { public function test_update_shipping_zone_invalid_id() { wp_set_current_user( $this->user ); - $request = new WP_REST_Request( 'PUT', '/wc/v1/shipping/zones/1' ); + $request = new WP_REST_Request( 'PUT', '/wc/v1/shipping/zones/555555' ); $request->set_body_params( array( 'name' => 'Zone Test', 'order' => 2, @@ -332,7 +332,7 @@ class WC_Tests_API_Shipping_Zones extends WC_REST_Unit_Test_Case { */ public function test_delete_shipping_zone_invalid_id() { wp_set_current_user( $this->user ); - $request = new WP_REST_Request( 'DELETE', '/wc/v1/shipping/zones/0' ); + $request = new WP_REST_Request( 'DELETE', '/wc/v1/shipping/zones/555555' ); $response = $this->server->dispatch( $request ); $this->assertEquals( 404, $response->get_status() ); } diff --git a/tests/unit-tests/shipping-zones/shipping-zone.php b/tests/unit-tests/shipping-zones/shipping-zone.php index d38a92fde21..2448012d311 100644 --- a/tests/unit-tests/shipping-zones/shipping-zone.php +++ b/tests/unit-tests/shipping-zones/shipping-zone.php @@ -25,7 +25,7 @@ class WC_Tests_Shipping_Zone extends WC_Unit_Test_Case { } /** - * Test: WC_Shipping_Zones::get_zone_id + * Test: WC_Shipping_Zones::get_id */ public function test_get_zone_id() { // Setup @@ -35,7 +35,7 @@ class WC_Tests_Shipping_Zone extends WC_Unit_Test_Case { $zone = WC_Shipping_Zones::get_zone( 1 ); // Assert - $this->assertEquals( $zone->get_zone_id(), 1 ); + $this->assertEquals( $zone->get_id(), 1 ); // Clean WC_Helper_Shipping_Zones::remove_mock_zones(); @@ -264,11 +264,11 @@ class WC_Tests_Shipping_Zone extends WC_Unit_Test_Case { // Assert $this->assertEquals( $zone->get_zone_locations(), array( - 2 => (object) array( + 0 => (object) array( 'code' => 'US', 'type' => 'country', ), - 3 => (object) array( + 1 => (object) array( 'code' => '90210', 'type' => 'postcode', ), @@ -320,4 +320,38 @@ class WC_Tests_Shipping_Zone extends WC_Unit_Test_Case { // Clean WC_Helper_Shipping_Zones::remove_mock_zones(); } + + /** + * Test legacy zone functions. + * + * @since 2.7.0 + */ + public function test_wc_shipping_zone_legacy() { + // Create a single zone. + $zone = new WC_Shipping_Zone(); + $zone->set_zone_name( 'Local' ); + $zone->set_zone_order( 1 ); + $zone->add_location( 'GB', 'country' ); + $zone->add_location( 'CB*', 'postcode' ); + $zone->save(); + $zone_id = $zone->get_id(); + + $zone_read = new WC_Shipping_Zone(); + $zone_read->read( $zone_id ); + $this->assertEquals( $zone_id, $zone_read->get_id() ); + + $zone = new WC_Shipping_Zone(); + $zone->set_zone_name( 'Test' ); + $zone->set_zone_order( 2 ); + $zone->create(); + + $this->assertEquals( 'Test', $zone->get_zone_name() ); + $this->assertNotEmpty( $zone->get_id() ); + + $zone->set_zone_name( 'Test 2' ); + $zone->update(); + + $this->assertEquals( 'Test 2', $zone->get_zone_name() ); + } + } diff --git a/woocommerce.php b/woocommerce.php index dd6373fbae4..e897268a9ef 100644 --- a/woocommerce.php +++ b/woocommerce.php @@ -277,6 +277,7 @@ final class WooCommerce { include_once( WC_ABSPATH . 'includes/abstracts/abstract-wc-integration.php' ); // An integration with a service include_once( WC_ABSPATH . 'includes/class-wc-product-factory.php' ); // Product factory include_once( WC_ABSPATH . 'includes/class-wc-payment-tokens.php' ); // Payment tokens controller + include_once( WC_ABSPATH . 'includes/class-wc-shipping-zone.php' ); include_once( WC_ABSPATH . 'includes/gateways/class-wc-payment-gateway-cc.php' ); // CC Payment Gateway include_once( WC_ABSPATH . 'includes/gateways/class-wc-payment-gateway-echeck.php' ); // eCheck Payment Gateway include_once( WC_ABSPATH . 'includes/class-wc-countries.php' ); // Defines countries and states @@ -291,15 +292,17 @@ final class WooCommerce { include_once( WC_ABSPATH . 'includes/data-stores/interfaces/wc-product-variable-data-store-interface.php' ); include_once( WC_ABSPATH . 'includes/data-stores/interfaces/wc-customer-data-store-interface.php' ); include_once( WC_ABSPATH . 'includes/data-stores/interfaces/class-wc-payment-token-data-store-interface.php' ); + include_once( WC_ABSPATH . 'includes/data-stores/interfaces/class-wc-shipping-zone-data-store-interface.php' ); include_once( WC_ABSPATH . 'includes/data-stores/class-wc-data-store-cpt.php' ); include_once( WC_ABSPATH . 'includes/data-stores/class-wc-coupon-data-store-cpt.php' ); - include_once( WC_ABSPATH . 'includes/data-stores/class-wc-payment-token-data-store-table.php' ); include_once( WC_ABSPATH . 'includes/data-stores/class-wc-product-data-store-cpt.php' ); include_once( WC_ABSPATH . 'includes/data-stores/class-wc-product-grouped-data-store-cpt.php' ); include_once( WC_ABSPATH . 'includes/data-stores/class-wc-product-variable-data-store-cpt.php' ); include_once( WC_ABSPATH . 'includes/data-stores/class-wc-product-variation-data-store-cpt.php' ); + include_once( WC_ABSPATH . 'includes/data-stores/class-wc-payment-token-data-store.php' ); include_once( WC_ABSPATH . 'includes/data-stores/class-wc-customer-data-store.php' ); include_once( WC_ABSPATH . 'includes/data-stores/class-wc-customer-data-store-session.php' ); + include_once( WC_ABSPATH . 'includes/data-stores/class-wc-shipping-zone-data-store.php' ); $this->query = new WC_Query(); $this->api = new WC_API();
diff --git a/includes/admin/settings/views/html-admin-page-shipping-zones-instance.php b/includes/admin/settings/views/html-admin-page-shipping-zones-instance.php index fdb8c6ad637..82d273b53e4 100644 --- a/includes/admin/settings/views/html-admin-page-shipping-zones-instance.php +++ b/includes/admin/settings/views/html-admin-page-shipping-zones-instance.php @@ -5,7 +5,7 @@ if ( ! defined( 'ABSPATH' ) ) { ?>

> - get_zone_name() ); ?> > + get_zone_name() ); ?> > get_method_title() ); ?>

diff --git a/includes/admin/settings/views/html-admin-page-shipping-zones.php b/includes/admin/settings/views/html-admin-page-shipping-zones.php index faf96560df3..dfc0ffc733c 100644 --- a/includes/admin/settings/views/html-admin-page-shipping-zones.php +++ b/includes/admin/settings/views/html-admin-page-shipping-zones.php @@ -25,7 +25,7 @@ if ( ! defined( 'ABSPATH' ) ) {
- +
optionally used for regions that are not included in any other shipping zone.', 'woocommerce' ); ?>