Completed
Push — master ( 3893d0...b059c6 )
by Mike
07:56
created

WC_Shipping_Zone::get_formatted_location()   B

Complexity

Conditions 6
Paths 32

Size

Total Lines 38
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 38
rs 8.439
nc 32
cc 6
eloc 25
nop 1
1
<?php
1 ignored issue
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 16 and the first side effect is on line 4.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

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