PageRenderTime 49ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

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

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