class-wc-shipping-zone-data-store.php 9.71 KB
Newer Older
imac's avatar
imac committed
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292
<?php
if ( ! defined( 'ABSPATH' ) ) {
	exit;
}

/**
 * WC Shipping Zone Data Store.
 *
 * @version  3.0.0
 * @category Class
 * @author   WooCommerce
 */
class WC_Shipping_Zone_Data_Store extends WC_Data_Store_WP implements WC_Shipping_Zone_Data_Store_Interface, WC_Object_Data_Store_Interface {

	/**
	 * Method to create a new shipping zone.
	 *
	 * @since 3.0.0
	 * @param WC_Shipping_Zone $zone
	 */
	public function create( &$zone ) {
		global $wpdb;
		$wpdb->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 3.0.0
	 * @param WC_Shipping_Zone $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 3.0.0
	 * @param WC_Shipping_Zone $zone
	 * @throws Exception
	 */
	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 zone_name, zone_order 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  3.0.0
	 * @param  WC_Shipping_Zone $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' );
			$id = $zone->get_id();
			$zone->set_id( null );
			WC_Cache_Helper::incr_cache_prefix( 'shipping_zones' );
			WC_Cache_Helper::get_transient_version( 'shipping', true );
			do_action( 'woocommerce_delete_shipping_zone', $id );
		}
	}

	/**
	 * Get a list of shipping methods for a specific zone.
	 *
	 * @since  3.0.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  3.0.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  3.0.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 3.0.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 ) );
		do_action( 'woocommerce_delete_shipping_zone_method', $instance_id );
	}

	/**
	 * Get a shipping zone method instance.
	 *
	 * @since  3.0.0
	 * @param  int
	 * @return object
	 */
	public function get_method( $instance_id ) {
		global $wpdb;
		return $wpdb->get_row( $wpdb->prepare( "SELECT zone_id, method_id, instance_id, method_order, is_enabled 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  3.0.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 3.0.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  3.0.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 location_code, location_type 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 3.0.0
	 *
	 * @param WC_Shipping_Zone
	 *
	 * @return bool|void
	 */
	private function save_locations( &$zone ) {
		$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,
			) );
		}
	}
}