/includes/class-wc-shipping-zone.php

  1. <?php 
  2. include_once( 'legacy/class-wc-legacy-shipping-zone.php' ); 
  3.  
  4. if ( ! defined( 'ABSPATH' ) ) { 
  5. exit; 
  6.  
  7. /** 
  8. * Represents a single shipping zone 
  9. * 
  10. * @class WC_Shipping_Zone 
  11. * @since 2.6.0 
  12. * @version 3.0.0 
  13. * @package WooCommerce/Classes 
  14. * @category Class 
  15. * @author WooCommerce 
  16. */ 
  17. class WC_Shipping_Zone extends WC_Legacy_Shipping_Zone { 
  18.  
  19. /** 
  20. * Zone ID 
  21. * @var int|null 
  22. */ 
  23. protected $id = null; 
  24.  
  25. /** 
  26. * This is the name of this object type. 
  27. * @var string 
  28. */ 
  29. protected $object_type = 'shipping_zone'; 
  30.  
  31. /** 
  32. * Zone Data 
  33. * @var array 
  34. */ 
  35. protected $data = array( 
  36. 'zone_name' => '',  
  37. 'zone_order' => 0,  
  38. 'zone_locations' => array(),  
  39. ); 
  40.  
  41. /** 
  42. * Constructor for zones. 
  43. * 
  44. * @param int|object $zone Zone ID to load from the DB or zone object. 
  45. */ 
  46. public function __construct( $zone = null ) { 
  47. if ( is_numeric( $zone ) && ! empty( $zone ) ) { 
  48. $this->set_id( $zone ); 
  49. } elseif ( is_object( $zone ) ) { 
  50. $this->set_id( $zone->zone_id ); 
  51. } elseif ( 0 === $zone || "0" === $zone ) { 
  52. $this->set_id( 0 ); 
  53. } else { 
  54. $this->set_object_read( true ); 
  55.  
  56. $this->data_store = WC_Data_Store::load( 'shipping-zone' ); 
  57. if ( false === $this->get_object_read() ) { 
  58. $this->data_store->read( $this ); 
  59.  
  60. /** 
  61. |-------------------------------------------------------------------------- 
  62. | Getters 
  63. |-------------------------------------------------------------------------- 
  64. */ 
  65.  
  66. /** 
  67. * Get zone name. 
  68. * 
  69. * @param string $context 
  70. * @return string 
  71. */ 
  72. public function get_zone_name( $context = 'view' ) { 
  73. return $this->get_prop( 'zone_name', $context ); 
  74.  
  75. /** 
  76. * Get zone order. 
  77. * 
  78. * @param string $context 
  79. * @return int 
  80. */ 
  81. public function get_zone_order( $context = 'view' ) { 
  82. return $this->get_prop( 'zone_order', $context ); 
  83.  
  84. /** 
  85. * Get zone locations. 
  86. * 
  87. * @param string $context 
  88. * @return array of zone objects 
  89. */ 
  90. public function get_zone_locations( $context = 'view' ) { 
  91. return $this->get_prop( 'zone_locations', $context ); 
  92.  
  93. /** 
  94. * Return a text string representing what this zone is for. 
  95. * 
  96. * @param int $max 
  97. * @param string $context 
  98. * @return string 
  99. */ 
  100. public function get_formatted_location( $max = 10, $context = 'view' ) { 
  101. $location_parts = array(); 
  102. $all_continents = WC()->countries->get_continents(); 
  103. $all_countries = WC()->countries->get_countries(); 
  104. $all_states = WC()->countries->get_states(); 
  105. $locations = $this->get_zone_locations( $context ); 
  106. $continents = array_filter( $locations, array( $this, 'location_is_continent' ) ); 
  107. $countries = array_filter( $locations, array( $this, 'location_is_country' ) ); 
  108. $states = array_filter( $locations, array( $this, 'location_is_state' ) ); 
  109. $postcodes = array_filter( $locations, array( $this, 'location_is_postcode' ) ); 
  110.  
  111. foreach ( $continents as $location ) { 
  112. $location_parts[] = $all_continents[ $location->code ]['name']; 
  113.  
  114. foreach ( $countries as $location ) { 
  115. $location_parts[] = $all_countries[ $location->code ]; 
  116.  
  117. foreach ( $states as $location ) { 
  118. $location_codes = explode( ':', $location->code ); 
  119. $location_parts[] = $all_states[ $location_codes[0] ][ $location_codes[1] ]; 
  120.  
  121. foreach ( $postcodes as $location ) { 
  122. $location_parts[] = $location->code; 
  123.  
  124. // Fix display of encoded characters. 
  125. $location_parts = array_map( 'html_entity_decode', $location_parts ); 
  126.  
  127. if ( sizeof( $location_parts ) > $max ) { 
  128. $remaining = sizeof( $location_parts ) - $max; 
  129. // @codingStandardsIgnoreStart 
  130. return sprintf( _n( '%s and %d other region', '%s and %d other regions', $remaining, 'woocommerce' ), implode( ', ', array_splice( $location_parts, 0, $max ) ), $remaining ); 
  131. // @codingStandardsIgnoreEnd 
  132. } elseif ( ! empty( $location_parts ) ) { 
  133. return implode( ', ', $location_parts ); 
  134. } else { 
  135. return __( 'Everywhere', 'woocommerce' ); 
  136.  
  137. /** 
  138. * Get shipping methods linked to this zone. 
  139. * 
  140. * @param bool Only return enabled methods. 
  141. * @return array of objects 
  142. */ 
  143. public function get_shipping_methods( $enabled_only = false ) { 
  144. if ( null === $this->get_id() ) { 
  145. return array(); 
  146.  
  147. $raw_methods = $this->data_store->get_methods( $this->get_id(), $enabled_only ); 
  148. $wc_shipping = WC_Shipping::instance(); 
  149. $allowed_classes = $wc_shipping->get_shipping_method_class_names(); 
  150. $methods = array(); 
  151.  
  152. foreach ( $raw_methods as $raw_method ) { 
  153. if ( in_array( $raw_method->method_id, array_keys( $allowed_classes ), true ) ) { 
  154. $class_name = $allowed_classes[ $raw_method->method_id ]; 
  155.  
  156. // The returned array may contain instances of shipping methods, as well 
  157. // as classes. If the "class" is an instance, just use it. If not,  
  158. // create an instance. 
  159. if ( is_object( $class_name ) ) { 
  160. $class_name_of_instance = get_class( $class_name ); 
  161. $methods[ $raw_method->instance_id ] = new $class_name_of_instance( $raw_method->instance_id ); 
  162. } else { 
  163. // If the class is not an object, it should be a string. It's better 
  164. // to double check, to be sure (a class must be a string, anything) 
  165. // else would be useless 
  166. if ( is_string( $class_name ) && class_exists( $class_name ) ) { 
  167. $methods[ $raw_method->instance_id ] = new $class_name( $raw_method->instance_id ); 
  168.  
  169. // Let's make sure that we have an instance before setting its attributes 
  170. if ( is_object( $methods[ $raw_method->instance_id ] ) ) { 
  171. $methods[ $raw_method->instance_id ]->method_order = absint( $raw_method->method_order ); 
  172. $methods[ $raw_method->instance_id ]->enabled = $raw_method->is_enabled ? 'yes' : 'no'; 
  173. $methods[ $raw_method->instance_id ]->has_settings = $methods[ $raw_method->instance_id ]->has_settings(); 
  174. $methods[ $raw_method->instance_id ]->settings_html = $methods[ $raw_method->instance_id ]->supports( 'instance-settings-modal' ) ? $methods[ $raw_method->instance_id ]->get_admin_options_html() : false; 
  175. $methods[ $raw_method->instance_id ]->method_description = wp_kses_post( wpautop( $methods[ $raw_method->instance_id ]->method_description ) ); 
  176.  
  177. uasort( $methods, 'wc_shipping_zone_method_order_uasort_comparison' ); 
  178.  
  179. return apply_filters( 'woocommerce_shipping_zone_shipping_methods', $methods, $raw_methods, $allowed_classes, $this ); 
  180.  
  181. /** 
  182. |-------------------------------------------------------------------------- 
  183. | Setters 
  184. |-------------------------------------------------------------------------- 
  185. */ 
  186.  
  187. /** 
  188. * Set zone name. 
  189. * 
  190. * @param string $set 
  191. */ 
  192. public function set_zone_name( $set ) { 
  193. $this->set_prop( 'zone_name', wc_clean( $set ) ); 
  194.  
  195. /** 
  196. * Set zone order. 
  197. * 
  198. * @param int $set 
  199. */ 
  200. public function set_zone_order( $set ) { 
  201. $this->set_prop( 'zone_order', absint( $set ) ); 
  202.  
  203. /** 
  204. * Set zone locations. 
  205. * 
  206. * @since 3.0.0 
  207. * @param array 
  208. */ 
  209. public function set_zone_locations( $locations ) { 
  210. $this->set_prop( 'zone_locations', $locations ); 
  211.  
  212. /** 
  213. |-------------------------------------------------------------------------- 
  214. | Other Methods 
  215. |-------------------------------------------------------------------------- 
  216. */ 
  217.  
  218. /** 
  219. * Save zone data to the database. 
  220. * 
  221. * @return int 
  222. */ 
  223. public function save() { 
  224. if ( ! $this->get_zone_name() ) { 
  225. $this->set_zone_name( $this->generate_zone_name() ); 
  226. if ( $this->data_store ) { 
  227. // Trigger action before saving to the DB. Allows you to adjust object props before save. 
  228. do_action( 'woocommerce_before_' . $this->object_type . '_object_save', $this, $this->data_store ); 
  229.  
  230. if ( null === $this->get_id() ) { 
  231. $this->data_store->create( $this ); 
  232. } else { 
  233. $this->data_store->update( $this ); 
  234. return $this->get_id(); 
  235.  
  236. /** 
  237. * Generate a zone name based on location. 
  238. * 
  239. * @return string 
  240. */ 
  241. protected function generate_zone_name() { 
  242. $zone_name = $this->get_formatted_location(); 
  243.  
  244. if ( empty( $zone_name ) ) { 
  245. $zone_name = __( 'Zone', 'woocommerce' ); 
  246.  
  247. return $zone_name; 
  248.  
  249. /** 
  250. * Location type detection. 
  251. * 
  252. * @param object $location 
  253. * @return boolean 
  254. */ 
  255. private function location_is_continent( $location ) { 
  256. return 'continent' === $location->type; 
  257.  
  258. /** 
  259. * Location type detection. 
  260. * 
  261. * @param object $location 
  262. * @return boolean 
  263. */ 
  264. private function location_is_country( $location ) { 
  265. return 'country' === $location->type; 
  266.  
  267. /** 
  268. * Location type detection. 
  269. * 
  270. * @param object $location 
  271. * @return boolean 
  272. */ 
  273. private function location_is_state( $location ) { 
  274. return 'state' === $location->type; 
  275.  
  276. /** 
  277. * Location type detection. 
  278. * 
  279. * @param object $location 
  280. * @return boolean 
  281. */ 
  282. private function location_is_postcode( $location ) { 
  283. return 'postcode' === $location->type; 
  284.  
  285. /** 
  286. * Is passed location type valid? 
  287. * 
  288. * @param string $type 
  289. * @return boolean 
  290. */ 
  291. public function is_valid_location_type( $type ) { 
  292. return in_array( $type, array( 'postcode', 'state', 'country', 'continent' ) ); 
  293.  
  294. /** 
  295. * Add location (state or postcode) to a zone. 
  296. * 
  297. * @param string $code 
  298. * @param string $type state or postcode 
  299. */ 
  300. public function add_location( $code, $type ) { 
  301. if ( $this->is_valid_location_type( $type ) ) { 
  302. if ( 'postcode' === $type ) { 
  303. $code = trim( strtoupper( str_replace( chr( 226 ) . chr( 128 ) . chr( 166 ), '...', $code ) ) ); // No normalization - postcodes are matched against both normal and formatted versions to support wildcards. 
  304. $location = array( 
  305. 'code' => wc_clean( $code ),  
  306. 'type' => wc_clean( $type ),  
  307. ); 
  308. $zone_locations = $this->get_prop( 'zone_locations', 'edit' ); 
  309. $zone_locations[] = (object) $location; 
  310. $this->set_prop( 'zone_locations', $zone_locations ); 
  311.  
  312.  
  313. /** 
  314. * Clear all locations for this zone. 
  315. * 
  316. * @param array|string $types of location to clear 
  317. */ 
  318. public function clear_locations( $types = array( 'postcode', 'state', 'country', 'continent' ) ) { 
  319. if ( ! is_array( $types ) ) { 
  320. $types = array( $types ); 
  321. $zone_locations = $this->get_prop( 'zone_locations', 'edit' ); 
  322. foreach ( $zone_locations as $key => $values ) { 
  323. if ( in_array( $values->type, $types ) ) { 
  324. unset( $zone_locations[ $key ] ); 
  325. $zone_locations = array_values( $zone_locations ); // reindex. 
  326. $this->set_prop( 'zone_locations', $zone_locations ); 
  327.  
  328. /** 
  329. * Set locations. 
  330. * 
  331. * @param array $locations Array of locations 
  332. */ 
  333. public function set_locations( $locations = array() ) { 
  334. $this->clear_locations(); 
  335. foreach ( $locations as $location ) { 
  336. $this->add_location( $location['code'], $location['type'] ); 
  337.  
  338. /** 
  339. * Add a shipping method to this zone. 
  340. * 
  341. * @param string $type shipping method type 
  342. * @return int new instance_id, 0 on failure 
  343. */ 
  344. public function add_shipping_method( $type ) { 
  345. if ( null === $this->get_id() ) { 
  346. $this->save(); 
  347.  
  348. $instance_id = 0; 
  349. $wc_shipping = WC_Shipping::instance(); 
  350. $allowed_classes = $wc_shipping->get_shipping_method_class_names(); 
  351. $count = $this->data_store->get_method_count( $this->get_id() ); 
  352.  
  353. if ( in_array( $type, array_keys( $allowed_classes ) ) ) { 
  354. $instance_id = $this->data_store->add_method( $this->get_id(), $type, $count + 1 ); 
  355.  
  356. if ( $instance_id ) { 
  357. do_action( 'woocommerce_shipping_zone_method_added', $instance_id, $type, $this->get_id() ); 
  358.  
  359. WC_Cache_Helper::get_transient_version( 'shipping', true ); 
  360.  
  361. return $instance_id; 
  362.  
  363. /** 
  364. * Delete a shipping method from a zone. 
  365. * 
  366. * @param int $instance_id 
  367. * @return True on success, false on failure 
  368. */ 
  369. public function delete_shipping_method( $instance_id ) { 
  370. if ( null === $this->get_id() ) { 
  371. return false; 
  372.  
  373. $this->data_store->delete_method( $instance_id ); 
  374. do_action( 'woocommerce_shipping_zone_method_deleted', $instance_id, $this->get_id() ); 
  375.  
  376. WC_Cache_Helper::get_transient_version( 'shipping', true ); 
  377.  
  378. return true; 
.