Completed
Push — master ( 59dcb3...b5cb25 )
by Mike
11:10
created

WC_Shipping_Zone::get_data()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

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