Completed
Push — master ( fb1e2f...9a3784 )
by Justin
25:57
created

WC_Shipping_Zone::set_zone_id()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 2
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 3
rs 10
1
<?php
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
	protected $id = null;
19
20
	/**
21
	 * Zone Data
22
	 * @var array
23
	 */
24
	protected $data = array(
25
		'zone_name'      => '',
26
		'zone_order'     => 0,
27
		'zone_locations' => array(),
28
	);
29
30
	/**
31
	 * True when location data needs to be re-saved
32
	 * @var bool
33
	 */
34
	private $locations_changed = false;
35
36
	/**
37
	 * Constructor for zones
38
	 * @param int|object $zone Zone ID to load from the DB (optional) or already queried data.
39
	 */
40
	public function __construct( $zone = null ) {
41
		if ( is_numeric( $zone ) && ! empty( $zone ) ) {
42
			$this->read( $zone );
43
		} elseif ( is_object( $zone ) ) {
44
			$this->set_id( $zone->zone_id );
45
			$this->set_zone_name( $zone->zone_name );
46
			$this->set_zone_order( $zone->zone_order );
47
			$this->read_zone_locations( $zone->zone_id );
48
		} elseif ( 0 === $zone ) {
49
			$this->set_id( 0 );
50
			$this->set_zone_name( __( 'Rest of the World', 'woocommerce' ) );
51
			$this->read_zone_locations( 0 );
52
		} else {
53
			$this->set_zone_name( __( 'Zone', 'woocommerce' ) );
54
		}
55
	}
56
57
	/**
58
	 * Insert zone into the database
59
	 */
60
	public function create() {
61
		global $wpdb;
62
		$wpdb->insert( $wpdb->prefix . 'woocommerce_shipping_zones', array(
63
			'zone_name'  => $this->get_zone_name(),
64
			'zone_order' => $this->get_zone_order(),
65
		) );
66
		$this->set_id( $wpdb->insert_id );
67
	}
68
69
	/**
70
	 * Read zone.
71
	 * @param int ID to read from DB
72
	 */
73
	public function read( $id ) {
74
		global $wpdb;
75
76
		if ( $zone_data = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM {$wpdb->prefix}woocommerce_shipping_zones WHERE zone_id = %d LIMIT 1;", $id ) ) ) {
77
			$this->set_id( $zone_data->zone_id );
78
			$this->set_zone_name( $zone_data->zone_name );
79
			$this->set_zone_order( $zone_data->zone_order );
80
			$this->read_zone_locations( $zone_data->zone_id );
81
		}
82
	}
83
84
	/**
85
	 * Update zone in the database
86
	 */
87
	public function update() {
88
		global $wpdb;
89
90
		if ( $this->get_id() ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->get_id() of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
91
			$wpdb->update( $wpdb->prefix . 'woocommerce_shipping_zones', array(
92
				'zone_name'  => $this->get_zone_name(),
93
				'zone_order' => $this->get_zone_order(),
94
			), array( 'zone_id' => $this->get_id() ) );
95
		}
96
	}
97
98
	/**
99
	 * Delete a zone.
100
	 * @since 2.6.0
101
	 */
102
	public function delete() {
103
		if ( $this->get_id() ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->get_id() of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

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