Completed
Push — master ( c63cd8...c4c8fb )
by Claudio
10:23
created

WC_Shipping_Zone::get_formatted_location()   B

Complexity

Conditions 7
Paths 48

Size

Total Lines 42

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 23
CRAP Score 7.0753

Importance

Changes 0
Metric Value
cc 7
nc 48
nop 2
dl 0
loc 42
ccs 23
cts 26
cp 0.8846
crap 7.0753
rs 8.3146
c 0
b 0
f 0
1
<?php
2
/**
3
 * Represents a single shipping zone
4
 *
5
 * @since   2.6.0
6
 * @version 3.0.0
7
 * @package WooCommerce/Classes
8
 */
9
10
defined( 'ABSPATH' ) || exit;
11
12
require_once 'legacy/class-wc-legacy-shipping-zone.php';
13
14
/**
15
 * WC_Shipping_Zone class.
16
 */
17
class WC_Shipping_Zone extends WC_Legacy_Shipping_Zone {
18
19
	/**
20
	 * Zone ID
21
	 *
22
	 * @var int|null
23
	 */
24
	protected $id = null;
25
26
	/**
27
	 * This is the name of this object type.
28
	 *
29
	 * @var string
30
	 */
31
	protected $object_type = 'shipping_zone';
32
33
	/**
34
	 * Zone Data.
35
	 *
36
	 * @var array
37
	 */
38
	protected $data = array(
39
		'zone_name'      => '',
40
		'zone_order'     => 0,
41
		'zone_locations' => array(),
42
	);
43
44
	/**
45
	 * Constructor for zones.
46
	 *
47
	 * @param int|object $zone Zone ID to load from the DB or zone object.
48
	 */
49 40
	public function __construct( $zone = null ) {
50 40
		if ( is_numeric( $zone ) && ! empty( $zone ) ) {
51 20
			$this->set_id( $zone );
52 40
		} elseif ( is_object( $zone ) ) {
53 2
			$this->set_id( $zone->zone_id );
54 40
		} elseif ( 0 === $zone || '0' === $zone ) {
55 18
			$this->set_id( 0 );
56
		} else {
57 22
			$this->set_object_read( true );
58
		}
59
60 40
		$this->data_store = WC_Data_Store::load( 'shipping-zone' );
61 40
		if ( false === $this->get_object_read() ) {
62 39
			$this->data_store->read( $this );
63
		}
64
	}
65
66
	/**
67
	 * --------------------------------------------------------------------------
68
	 * Getters
69
	 * --------------------------------------------------------------------------
70
	 */
71
72
	/**
73
	 * Get zone name.
74
	 *
75
	 * @param  string $context View or edit context.
76
	 * @return string
77
	 */
78 22
	public function get_zone_name( $context = 'view' ) {
79 22
		return $this->get_prop( 'zone_name', $context );
80
	}
81
82
	/**
83
	 * Get zone order.
84
	 *
85
	 * @param  string $context View or edit context.
86
	 * @return int
87
	 */
88 22
	public function get_zone_order( $context = 'view' ) {
89 22
		return $this->get_prop( 'zone_order', $context );
90
	}
91
92
	/**
93
	 * Get zone locations.
94
	 *
95
	 * @param  string $context View or edit context.
96
	 * @return array of zone objects
97
	 */
98 22
	public function get_zone_locations( $context = 'view' ) {
99 22
		return $this->get_prop( 'zone_locations', $context );
100
	}
101
102
	/**
103
	 * Return a text string representing what this zone is for.
104
	 *
105
	 * @param  int    $max Max locations to return.
106
	 * @param  string $context View or edit context.
107
	 * @return string
108
	 */
109 3
	public function get_formatted_location( $max = 10, $context = 'view' ) {
110 3
		$location_parts = array();
111 3
		$all_continents = WC()->countries->get_continents();
112 3
		$all_countries  = WC()->countries->get_countries();
113 3
		$all_states     = WC()->countries->get_states();
114 3
		$locations      = $this->get_zone_locations( $context );
115 3
		$continents     = array_filter( $locations, array( $this, 'location_is_continent' ) );
116 3
		$countries      = array_filter( $locations, array( $this, 'location_is_country' ) );
117 3
		$states         = array_filter( $locations, array( $this, 'location_is_state' ) );
118 3
		$postcodes      = array_filter( $locations, array( $this, 'location_is_postcode' ) );
119
120 3
		foreach ( $continents as $location ) {
121 3
			$location_parts[] = $all_continents[ $location->code ]['name'];
122
		}
123
124 3
		foreach ( $countries as $location ) {
125 3
			$location_parts[] = $all_countries[ $location->code ];
126
		}
127
128 3
		foreach ( $states as $location ) {
129 3
			$location_codes   = explode( ':', $location->code );
130 3
			$location_parts[] = $all_states[ $location_codes[0] ][ $location_codes[1] ];
131
		}
132
133 3
		foreach ( $postcodes as $location ) {
134 2
			$location_parts[] = $location->code;
135
		}
136
137
		// Fix display of encoded characters.
138 3
		$location_parts = array_map( 'html_entity_decode', $location_parts );
139
140 3
		if ( count( $location_parts ) > $max ) {
141
			$remaining = count( $location_parts ) - $max;
142
			// @codingStandardsIgnoreStart
143
			return sprintf( _n( '%s and %d other region', '%s and %d other regions', $remaining, 'woocommerce' ), implode( ', ', array_splice( $location_parts, 0, $max ) ), $remaining );
144
			// @codingStandardsIgnoreEnd
145 3
		} elseif ( ! empty( $location_parts ) ) {
146 3
			return implode( ', ', $location_parts );
147
		} else {
148
			return __( 'Everywhere', 'woocommerce' );
149
		}
150
	}
151
152
	/**
153
	 * Get shipping methods linked to this zone.
154
	 *
155
	 * @param bool   $enabled_only Only return enabled methods.
156
	 * @param string $context Getting shipping methods for what context. Valid values, admin, json.
157
	 * @return array of objects
158
	 */
159 22
	public function get_shipping_methods( $enabled_only = false, $context = 'admin' ) {
160 22
		if ( null === $this->get_id() ) {
161
			return array();
162
		}
163
164 22
		$raw_methods     = $this->data_store->get_methods( $this->get_id(), $enabled_only );
165 22
		$wc_shipping     = WC_Shipping::instance();
166 22
		$allowed_classes = $wc_shipping->get_shipping_method_class_names();
167 22
		$methods         = array();
168
169 22
		foreach ( $raw_methods as $raw_method ) {
170 2
			if ( in_array( $raw_method->method_id, array_keys( $allowed_classes ), true ) ) {
171 2
				$class_name  = $allowed_classes[ $raw_method->method_id ];
172 2
				$instance_id = $raw_method->instance_id;
173
174
				// The returned array may contain instances of shipping methods, as well
175
				// as classes. If the "class" is an instance, just use it. If not,
176
				// create an instance.
177 2
				if ( is_object( $class_name ) ) {
178
					$class_name_of_instance  = get_class( $class_name );
179
					$methods[ $instance_id ] = new $class_name_of_instance( $instance_id );
180
				} else {
181
					// If the class is not an object, it should be a string. It's better
182
					// to double check, to be sure (a class must be a string, anything)
183
					// else would be useless.
184 2
					if ( is_string( $class_name ) && class_exists( $class_name ) ) {
185 2
						$methods[ $instance_id ] = new $class_name( $instance_id );
186
					}
187
				}
188
189
				// Let's make sure that we have an instance before setting its attributes.
190 2
				if ( is_object( $methods[ $instance_id ] ) ) {
191 2
					$methods[ $instance_id ]->method_order       = absint( $raw_method->method_order );
192 2
					$methods[ $instance_id ]->enabled            = $raw_method->is_enabled ? 'yes' : 'no';
193 2
					$methods[ $instance_id ]->has_settings       = $methods[ $instance_id ]->has_settings();
194 2
					$methods[ $instance_id ]->settings_html      = $methods[ $instance_id ]->supports( 'instance-settings-modal' ) ? $methods[ $instance_id ]->get_admin_options_html() : false;
195 2
					$methods[ $instance_id ]->method_description = wp_kses_post( wpautop( $methods[ $instance_id ]->method_description ) );
196
				}
197
198 2
				if ( 'json' === $context ) {
199
					// We don't want the entire object in this context, just the public props.
200
					$methods[ $instance_id ] = (object) get_object_vars( $methods[ $instance_id ] );
201
					unset( $methods[ $instance_id ]->instance_form_fields, $methods[ $instance_id ]->form_fields );
202
				}
203
			}
204
		}
205
206 22
		uasort( $methods, 'wc_shipping_zone_method_order_uasort_comparison' );
207
208 22
		return apply_filters( 'woocommerce_shipping_zone_shipping_methods', $methods, $raw_methods, $allowed_classes, $this );
209
	}
210
211
	/**
212
	 * --------------------------------------------------------------------------
213
	 * Setters
214
	 * --------------------------------------------------------------------------
215
	 */
216
217
	/**
218
	 * Set zone name.
219
	 *
220
	 * @param string $set Value to set.
221
	 */
222 40
	public function set_zone_name( $set ) {
223 40
		$this->set_prop( 'zone_name', wc_clean( $set ) );
224
	}
225
226
	/**
227
	 * Set zone order. Value to set.
228
	 *
229
	 * @param int $set Value to set.
230
	 */
231 22
	public function set_zone_order( $set ) {
232 22
		$this->set_prop( 'zone_order', absint( $set ) );
233
	}
234
235
	/**
236
	 * Set zone locations.
237
	 *
238
	 * @since 3.0.0
239
	 * @param array $locations Value to set.
240
	 */
241
	public function set_zone_locations( $locations ) {
242
		if ( 0 !== $this->get_id() ) {
243
			$this->set_prop( 'zone_locations', $locations );
244
		}
245
	}
246
247
	/**
248
	 * --------------------------------------------------------------------------
249
	 * Other
250
	 * --------------------------------------------------------------------------
251
	 */
252
253
	/**
254
	 * Save zone data to the database.
255
	 *
256
	 * @return int
257
	 */
258 22 View Code Duplication
	public function save() {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
259 22
		if ( ! $this->get_zone_name() ) {
260
			$this->set_zone_name( $this->generate_zone_name() );
261
		}
262
263 22
		if ( ! $this->data_store ) {
264
			return $this->get_id();
265
		}
266
267
		/**
268
		 * Trigger action before saving to the DB. Allows you to adjust object props before save.
269
		 *
270
		 * @param WC_Data          $this The object being saved.
271
		 * @param WC_Data_Store_WP $data_store THe data store persisting the data.
272
		 */
273 22
		do_action( 'woocommerce_before_' . $this->object_type . '_object_save', $this, $this->data_store );
274
275 22
		if ( null !== $this->get_id() ) {
276 1
			$this->data_store->update( $this );
277
		} else {
278 22
			$this->data_store->create( $this );
279
		}
280
281
		/**
282
		 * Trigger action after saving to the DB.
283
		 *
284
		 * @param WC_Data          $this The object being saved.
285
		 * @param WC_Data_Store_WP $data_store THe data store persisting the data.
286
		 */
287 22
		do_action( 'woocommerce_after_' . $this->object_type . '_object_save', $this, $this->data_store );
288
289 22
		return $this->get_id();
290
	}
291
292
	/**
293
	 * Generate a zone name based on location.
294
	 *
295
	 * @return string
296
	 */
297
	protected function generate_zone_name() {
298
		$zone_name = $this->get_formatted_location();
299
300
		if ( empty( $zone_name ) ) {
301
			$zone_name = __( 'Zone', 'woocommerce' );
302
		}
303
304
		return $zone_name;
305
	}
306
307
	/**
308
	 * Location type detection.
309
	 *
310
	 * @param  object $location Location to check.
311
	 * @return boolean
312
	 */
313 3
	private function location_is_continent( $location ) {
314 3
		return 'continent' === $location->type;
315
	}
316
317
	/**
318
	 * Location type detection.
319
	 *
320
	 * @param  object $location Location to check.
321
	 * @return boolean
322
	 */
323 3
	private function location_is_country( $location ) {
324 3
		return 'country' === $location->type;
325
	}
326
327
	/**
328
	 * Location type detection.
329
	 *
330
	 * @param  object $location Location to check.
331
	 * @return boolean
332
	 */
333 3
	private function location_is_state( $location ) {
334 3
		return 'state' === $location->type;
335
	}
336
337
	/**
338
	 * Location type detection.
339
	 *
340
	 * @param  object $location Location to check.
341
	 * @return boolean
342
	 */
343 3
	private function location_is_postcode( $location ) {
344 3
		return 'postcode' === $location->type;
345
	}
346
347
	/**
348
	 * Is passed location type valid?
349
	 *
350
	 * @param  string $type Type to check.
351
	 * @return boolean
352
	 */
353 22
	public function is_valid_location_type( $type ) {
354 22
		return in_array( $type, apply_filters( 'woocommerce_valid_location_types', array( 'postcode', 'state', 'country', 'continent' ) ), true );
355
	}
356
357
	/**
358
	 * Add location (state or postcode) to a zone.
359
	 *
360
	 * @param string $code Location code.
361
	 * @param string $type state or postcode.
362
	 */
363 22
	public function add_location( $code, $type ) {
364 22
		if ( 0 !== $this->get_id() && $this->is_valid_location_type( $type ) ) {
365 22
			if ( 'postcode' === $type ) {
366 22
				$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.
367
			}
368
			$location         = array(
369 22
				'code' => wc_clean( $code ),
370 22
				'type' => wc_clean( $type ),
371
			);
372 22
			$zone_locations   = $this->get_prop( 'zone_locations', 'edit' );
373 22
			$zone_locations[] = (object) $location;
374 22
			$this->set_prop( 'zone_locations', $zone_locations );
375
		}
376
	}
377
378
379
	/**
380
	 * Clear all locations for this zone.
381
	 *
382
	 * @param array|string $types of location to clear.
383
	 */
384 2
	public function clear_locations( $types = array( 'postcode', 'state', 'country', 'continent' ) ) {
385 2
		if ( ! is_array( $types ) ) {
386
			$types = array( $types );
387
		}
388 2
		$zone_locations = $this->get_prop( 'zone_locations', 'edit' );
389 2
		foreach ( $zone_locations as $key => $values ) {
390 2
			if ( in_array( $values->type, $types, true ) ) {
391 2
				unset( $zone_locations[ $key ] );
392
			}
393
		}
394 2
		$zone_locations = array_values( $zone_locations ); // reindex.
395 2
		$this->set_prop( 'zone_locations', $zone_locations );
396
	}
397
398
	/**
399
	 * Set locations.
400
	 *
401
	 * @param array $locations Array of locations.
402
	 */
403 1
	public function set_locations( $locations = array() ) {
404 1
		$this->clear_locations();
405 1
		foreach ( $locations as $location ) {
406 1
			$this->add_location( $location['code'], $location['type'] );
407
		}
408
	}
409
410
	/**
411
	 * Add a shipping method to this zone.
412
	 *
413
	 * @param string $type shipping method type.
414
	 * @return int new instance_id, 0 on failure
415
	 */
416 4
	public function add_shipping_method( $type ) {
417 4
		if ( null === $this->get_id() ) {
418
			$this->save();
419
		}
420
421 4
		$instance_id     = 0;
422 4
		$wc_shipping     = WC_Shipping::instance();
423 4
		$allowed_classes = $wc_shipping->get_shipping_method_class_names();
424 4
		$count           = $this->data_store->get_method_count( $this->get_id() );
425
426 4
		if ( in_array( $type, array_keys( $allowed_classes ), true ) ) {
427 4
			$instance_id = $this->data_store->add_method( $this->get_id(), $type, $count + 1 );
428
		}
429
430 4
		if ( $instance_id ) {
431 4
			do_action( 'woocommerce_shipping_zone_method_added', $instance_id, $type, $this->get_id() );
432
		}
433
434 4
		WC_Cache_Helper::get_transient_version( 'shipping', true );
435
436 4
		return $instance_id;
437
	}
438
439
	/**
440
	 * Delete a shipping method from a zone.
441
	 *
442
	 * @param int $instance_id Shipping method instance ID.
443
	 * @return True on success, false on failure
444
	 */
445
	public function delete_shipping_method( $instance_id ) {
446
		if ( null === $this->get_id() ) {
447
			return false;
448
		}
449
450
		// Get method details.
451
		$method = $this->data_store->get_method( $instance_id );
452
453
		if ( $method ) {
454
			$this->data_store->delete_method( $instance_id );
455
			do_action( 'woocommerce_shipping_zone_method_deleted', $instance_id, $method->method_id, $this->get_id() );
456
		}
457
458
		WC_Cache_Helper::get_transient_version( 'shipping', true );
459
460
		return true;
461
	}
462
}
463