PageRenderTime 54ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 1ms

/wp-content/plugins/woocommerce/includes/class-wc-shipping-zone.php

https://gitlab.com/hunt9310/ras
PHP | 469 lines | 265 code | 59 blank | 145 comment | 27 complexity | b46563a603ceab68fc86755993a1b808 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. $name = $this->get_zone_name();
  109. if ( empty( $name ) ) {
  110. $this->set_zone_name( $this->generate_zone_name() );
  111. }
  112. if ( ! $this->get_zone_id() ) {
  113. $this->create();
  114. } else {
  115. $this->update();
  116. }
  117. $this->save_locations();
  118. WC_Cache_Helper::incr_cache_prefix( 'shipping_zones' );
  119. // Increments the transient version to invalidate cache.
  120. WC_Cache_Helper::get_transient_version( 'shipping', true );
  121. }
  122. /**
  123. * Get zone ID
  124. * @return int
  125. */
  126. public function get_zone_id() {
  127. return absint( $this->_data['zone_id'] );
  128. }
  129. /**
  130. * Get zone name
  131. * @return string
  132. */
  133. public function get_zone_name() {
  134. return $this->_data['zone_name'];
  135. }
  136. /**
  137. * Get zone order
  138. * @return int
  139. */
  140. public function get_zone_order() {
  141. return absint( $this->_data['zone_order'] );
  142. }
  143. /**
  144. * Get zone locations
  145. * @return array of zone objects
  146. */
  147. public function get_zone_locations() {
  148. return $this->_data['zone_locations'];
  149. }
  150. /**
  151. * Generate a zone name based on location.
  152. * @return string
  153. */
  154. protected function generate_zone_name() {
  155. $zone_name = $this->get_formatted_location();
  156. if ( empty( $zone_name ) ) {
  157. $zone_name = __( 'Zone', 'woocommerce' );
  158. }
  159. return $zone_name;
  160. }
  161. /**
  162. * Return a text string representing what this zone is for.
  163. * @return string
  164. */
  165. public function get_formatted_location( $max = 10 ) {
  166. $location_parts = array();
  167. $all_continents = WC()->countries->get_continents();
  168. $all_countries = WC()->countries->get_countries();
  169. $all_states = WC()->countries->get_states();
  170. $locations = $this->get_zone_locations();
  171. $continents = array_filter( $locations, array( $this, 'location_is_continent' ) );
  172. $countries = array_filter( $locations, array( $this, 'location_is_country' ) );
  173. $states = array_filter( $locations, array( $this, 'location_is_state' ) );
  174. $postcodes = array_filter( $locations, array( $this, 'location_is_postcode' ) );
  175. foreach ( $continents as $location ) {
  176. $location_parts[] = $all_continents[ $location->code ]['name'];
  177. }
  178. foreach ( $countries as $location ) {
  179. $location_parts[] = $all_countries[ $location->code ];
  180. }
  181. foreach ( $states as $location ) {
  182. $location_codes = explode( ':', $location->code );
  183. $location_parts[] = $all_states[ $location_codes[ 0 ] ][ $location_codes[ 1 ] ];
  184. }
  185. foreach ( $postcodes as $location ) {
  186. $location_parts[] = $location->code;
  187. }
  188. // Fix display of encoded characters.
  189. $location_parts = array_map( 'html_entity_decode', $location_parts );
  190. if ( sizeof( $location_parts ) > $max ) {
  191. $remaining = sizeof( $location_parts ) - $max;
  192. return sprintf( _n( '%s and %d other region', '%s and %d other regions', $remaining, 'woocommerce' ), implode( ', ', array_splice( $location_parts, 0, $max ) ), $remaining );
  193. } elseif ( ! empty( $location_parts ) ) {
  194. return implode( ', ', $location_parts );
  195. } else {
  196. return __( 'Everywhere', 'woocommerce' );
  197. }
  198. }
  199. /**
  200. * Get shipping methods linked to this zone
  201. * @param bool Only return enabled methods.
  202. * @return array of objects
  203. */
  204. public function get_shipping_methods( $enabled_only = false ) {
  205. global $wpdb;
  206. $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;";
  207. $raw_methods = $wpdb->get_results( $wpdb->prepare( $raw_methods_sql, $this->get_zone_id() ) );
  208. $wc_shipping = WC_Shipping::instance();
  209. $allowed_classes = $wc_shipping->get_shipping_method_class_names();
  210. $methods = array();
  211. foreach ( $raw_methods as $raw_method ) {
  212. if ( in_array( $raw_method->method_id, array_keys( $allowed_classes ), true ) ) {
  213. $class_name = $allowed_classes[ $raw_method->method_id ];
  214. // The returned array may contain instances of shipping methods, as well
  215. // as classes. If the "class" is an instance, just use it. If not,
  216. // create an instance.
  217. if ( is_object( $class_name ) ) {
  218. $class_name_of_instance = get_class( $class_name );
  219. $methods[ $raw_method->instance_id ] = new $class_name_of_instance( $raw_method->instance_id );
  220. } else {
  221. // If the class is not an object, it should be a string. It's better
  222. // to double check, to be sure (a class must be a string, anything)
  223. // else would be useless
  224. if ( is_string( $class_name ) && class_exists( $class_name ) ) {
  225. $methods[ $raw_method->instance_id ] = new $class_name( $raw_method->instance_id );
  226. }
  227. }
  228. // Let's make sure that we have an instance before setting its attributes
  229. if ( is_object( $methods[ $raw_method->instance_id ] ) ) {
  230. $methods[ $raw_method->instance_id ]->method_order = absint( $raw_method->method_order );
  231. $methods[ $raw_method->instance_id ]->enabled = $raw_method->is_enabled ? 'yes' : 'no';
  232. $methods[ $raw_method->instance_id ]->has_settings = $methods[ $raw_method->instance_id ]->has_settings();
  233. $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;
  234. }
  235. }
  236. }
  237. return apply_filters( 'woocommerce_shipping_zone_shipping_methods', $methods, $raw_methods, $allowed_classes, $this );
  238. }
  239. /**
  240. * Location type detection
  241. * @param object $location
  242. * @return boolean
  243. */
  244. private function location_is_continent( $location ) {
  245. return 'continent' === $location->type;
  246. }
  247. /**
  248. * Location type detection
  249. * @param object $location
  250. * @return boolean
  251. */
  252. private function location_is_country( $location ) {
  253. return 'country' === $location->type;
  254. }
  255. /**
  256. * Location type detection
  257. * @param object $location
  258. * @return boolean
  259. */
  260. private function location_is_state( $location ) {
  261. return 'state' === $location->type;
  262. }
  263. /**
  264. * Location type detection
  265. * @param object $location
  266. * @return boolean
  267. */
  268. private function location_is_postcode( $location ) {
  269. return 'postcode' === $location->type;
  270. }
  271. /**
  272. * Set zone ID
  273. * @access private
  274. * @param int $set
  275. */
  276. private function set_zone_id( $set ) {
  277. $this->_data['zone_id'] = absint( $set );
  278. }
  279. /**
  280. * Set zone name
  281. * @param string $set
  282. */
  283. public function set_zone_name( $set ) {
  284. $this->_data['zone_name'] = wc_clean( $set );
  285. }
  286. /**
  287. * Set zone order
  288. * @param int $set
  289. */
  290. public function set_zone_order( $set ) {
  291. $this->_data['zone_order'] = absint( $set );
  292. }
  293. /**
  294. * Is passed location type valid?
  295. * @param string $type
  296. * @return boolean
  297. */
  298. public function is_valid_location_type( $type ) {
  299. return in_array( $type, array( 'postcode', 'state', 'country', 'continent' ) );
  300. }
  301. /**
  302. * Add location (state or postcode) to a zone.
  303. * @param string $code
  304. * @param string $type state or postcode
  305. */
  306. public function add_location( $code, $type ) {
  307. if ( $this->is_valid_location_type( $type ) ) {
  308. if ( 'postcode' === $type ) {
  309. $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.
  310. }
  311. $location = array(
  312. 'code' => wc_clean( $code ),
  313. 'type' => wc_clean( $type )
  314. );
  315. $this->_data['zone_locations'][] = (object) $location;
  316. $this->_locations_changed = true;
  317. }
  318. }
  319. /**
  320. * Clear all locations for this zone.
  321. * @param array|string $types of location to clear
  322. */
  323. public function clear_locations( $types = array( 'postcode', 'state', 'country', 'continent' ) ) {
  324. if ( ! is_array( $types ) ) {
  325. $types = array( $types );
  326. }
  327. foreach ( $this->_data['zone_locations'] as $key => $values ) {
  328. if ( in_array( $values->type, $types ) ) {
  329. unset( $this->_data['zone_locations'][ $key ] );
  330. $this->_locations_changed = true;
  331. }
  332. }
  333. }
  334. /**
  335. * Set locations
  336. * @param array $locations Array of locations
  337. */
  338. public function set_locations( $locations = array() ) {
  339. $this->clear_locations();
  340. foreach ( $locations as $location ) {
  341. $this->add_location( $location['code'], $location['type'] );
  342. }
  343. $this->_locations_changed = true;
  344. }
  345. /**
  346. * Read location data from the database
  347. * @param int $zone_id
  348. */
  349. private function read_zone_locations( $zone_id ) {
  350. global $wpdb;
  351. if ( $locations = $wpdb->get_results( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_shipping_zone_locations WHERE zone_id = %d;", $zone_id ) ) ) {
  352. foreach ( $locations as $location ) {
  353. $this->add_location( $location->location_code, $location->location_type );
  354. }
  355. }
  356. $this->_locations_changed = false;
  357. }
  358. /**
  359. * Save locations to the DB.
  360. *
  361. * This function clears old locations, then re-inserts new if any changes are found.
  362. */
  363. private function save_locations() {
  364. if ( ! $this->get_zone_id() || ! $this->_locations_changed ) {
  365. return false;
  366. }
  367. global $wpdb;
  368. $wpdb->delete( $wpdb->prefix . 'woocommerce_shipping_zone_locations', array( 'zone_id' => $this->get_zone_id() ) );
  369. foreach ( $this->get_zone_locations() as $location ) {
  370. $wpdb->insert( $wpdb->prefix . 'woocommerce_shipping_zone_locations', array(
  371. 'zone_id' => $this->get_zone_id(),
  372. 'location_code' => $location->code,
  373. 'location_type' => $location->type
  374. ) );
  375. }
  376. }
  377. /**
  378. * Add a shipping method to this zone.
  379. * @param string $type shipping method type
  380. * @return int new instance_id, 0 on failure
  381. */
  382. public function add_shipping_method( $type ) {
  383. global $wpdb;
  384. $instance_id = 0;
  385. $wc_shipping = WC_Shipping::instance();
  386. $allowed_classes = $wc_shipping->get_shipping_method_class_names();
  387. $count = $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM {$wpdb->prefix}woocommerce_shipping_zone_methods WHERE zone_id = %d", $this->get_zone_id() ) );
  388. if ( in_array( $type, array_keys( $allowed_classes ) ) ) {
  389. $wpdb->insert(
  390. $wpdb->prefix . 'woocommerce_shipping_zone_methods',
  391. array(
  392. 'method_id' => $type,
  393. 'zone_id' => $this->get_zone_id(),
  394. 'method_order' => ( $count + 1 )
  395. ),
  396. array(
  397. '%s',
  398. '%d',
  399. '%d'
  400. )
  401. );
  402. $instance_id = $wpdb->insert_id;
  403. }
  404. if ( $instance_id ) {
  405. do_action( 'woocommerce_shipping_zone_method_added', $instance_id, $type, $this->get_zone_id() );
  406. }
  407. WC_Cache_Helper::get_transient_version( 'shipping', true );
  408. return $instance_id;
  409. }
  410. }