Completed
Pull Request — master (#9826)
by Mike
10:44
created

WC_Countries::get_continents()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 6
rs 9.4286
cc 2
eloc 4
nc 2
nop 0
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 18 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; // Exit if accessed directly
5
}
6
7
/**
8
 * WooCommerce countries
9
 *
10
 * The WooCommerce countries class stores country/state data.
11
 *
12
 * @class       WC_Countries
13
 * @version     2.3.0
14
 * @package     WooCommerce/Classes
15
 * @category    Class
16
 * @author      WooThemes
17
 */
18
class WC_Countries {
19
20
	/** @var array Array of locales */
21
	public $locale;
22
23
	/** @var array Array of address formats for locales */
24
	public $address_formats;
25
26
	/**
27
	 * Auto-load in-accessible properties on demand.
28
	 * @param  mixed $key
29
	 * @return mixed
30
	 */
31
	public function __get( $key ) {
32
		if ( 'countries' == $key ) {
33
			return $this->get_countries();
34
		} elseif ( 'states' == $key ) {
35
			return $this->get_states();
36
		}
37
	}
38
39
	/**
40
	 * Get all countries.
41
	 * @return array
42
	 */
43
	public function get_countries() {
44
		if ( empty( $this->countries ) ) {
45
			$this->countries = apply_filters( 'woocommerce_countries', include( WC()->plugin_path() . '/i18n/countries.php' ) );
46
			if ( apply_filters( 'woocommerce_sort_countries', true ) ) {
47
				asort( $this->countries );
48
			}
49
		}
50
		return $this->countries;
51
	}
52
53
	/**
54
	 * Get all continents.
55
	 * @return array
56
	 */
57
	public function get_continents() {
58
		if ( empty( $this->continents ) ) {
59
			$this->continents = apply_filters( 'woocommerce_continents', include( WC()->plugin_path() . '/i18n/continents.php' ) );
60
		}
61
		return $this->continents;
62
	}
63
64
	/**
65
	 * Get continent code for a country code.
66
	 * @since 2.6.0
67
	 * @param $cc string
68
	 * @return string
69
	 */
70
	public function get_continent_code_for_country( $cc ) {
71
		$cc                 = trim( strtoupper( $cc ) );
72
		$continents         = $this->get_continents();
73
		$continents_and_ccs = wp_list_pluck( $continents, 'countries' );
74
		foreach ( $continents_and_ccs as $continent_code => $countries ) {
75
			if ( false !== array_search( $cc, $countries ) ) {
76
				return $continent_code;
77
			}
78
		}
79
		return '';
80
	}
81
82
	/**
83
	 * Load the states.
84
	 */
85
	public function load_country_states() {
86
		global $states;
87
88
		// States set to array() are blank i.e. the country has no use for the state field.
89
		$states = array(
90
			'AF' => array(),
91
			'AT' => array(),
92
			'AX' => array(),
93
			'BE' => array(),
94
			'BI' => array(),
95
			'CZ' => array(),
96
			'DE' => array(),
97
			'DK' => array(),
98
			'EE' => array(),
99
			'FI' => array(),
100
			'FR' => array(),
101
			'IS' => array(),
102
			'IL' => array(),
103
			'KR' => array(),
104
			'NL' => array(),
105
			'NO' => array(),
106
			'PL' => array(),
107
			'PT' => array(),
108
			'SG' => array(),
109
			'SK' => array(),
110
			'SI' => array(),
111
			'LK' => array(),
112
			'SE' => array(),
113
			'VN' => array(),
114
		);
115
116
		// Load only the state files the shop owner wants/needs.
117
		$allowed = array_merge( $this->get_allowed_countries(), $this->get_shipping_countries() );
118
119
		if ( $allowed ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $allowed of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
120
			foreach ( $allowed as $code => $country ) {
121
				if ( ! isset( $states[ $code ] ) && file_exists( WC()->plugin_path() . '/i18n/states/' . $code . '.php' ) ) {
122
					include( WC()->plugin_path() . '/i18n/states/' . $code . '.php' );
123
				}
124
			}
125
		}
126
127
		$this->states = apply_filters( 'woocommerce_states', $states );
128
	}
129
130
	/**
131
	 * Get the states for a country.
132
	 * @param  string $cc country code
133
	 * @return array of states
134
	 */
135
	public function get_states( $cc = null ) {
136
		if ( empty( $this->states ) ) {
137
			$this->load_country_states();
138
		}
139
140
		if ( ! is_null( $cc ) ) {
141
			return isset( $this->states[ $cc ] ) ? $this->states[ $cc ] : false;
142
		} else {
143
			return $this->states;
144
		}
145
	}
146
147
	/**
148
	 * Get the base country for the store.
149
	 * @return string
150
	 */
151
	public function get_base_country() {
152
		$default = wc_get_base_location();
153
		return apply_filters( 'woocommerce_countries_base_country', $default['country'] );
154
	}
155
156
	/**
157
	 * Get the base state for the store.
158
	 * @return string
159
	 */
160
	public function get_base_state() {
161
		$default = wc_get_base_location();
162
		return apply_filters( 'woocommerce_countries_base_state', $default['state'] );
163
	}
164
165
	/**
166
	 * Get the base city for the store.
167
	 * @return string
168
	 */
169
	public function get_base_city() {
170
		return apply_filters( 'woocommerce_countries_base_city', '' );
171
	}
172
173
	/**
174
	 * Get the base postcode for the store.
175
	 * @return string
176
	 */
177
	public function get_base_postcode() {
178
		return apply_filters( 'woocommerce_countries_base_postcode', '' );
179
	}
180
181
	/**
182
	 * Get the allowed countries for the store.
183
	 * @return array
184
	 */
185 View Code Duplication
	public function get_allowed_countries() {
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...
186
		if ( get_option( 'woocommerce_allowed_countries' ) !== 'specific' ) {
187
			return $this->countries;
188
		}
189
190
		$countries = array();
191
192
		$raw_countries = get_option( 'woocommerce_specific_allowed_countries' );
193
194
		foreach ( $raw_countries as $country ) {
195
			$countries[ $country ] = $this->countries[ $country ];
196
		}
197
198
		return apply_filters( 'woocommerce_countries_allowed_countries', $countries );
199
	}
200
201
	/**
202
	 * Get the countries you ship to.
203
	 * @return array
204
	 */
205
	public function get_shipping_countries() {
206
		if ( get_option( 'woocommerce_ship_to_countries' ) == '' ) {
207
			return $this->get_allowed_countries();
208
		}
209
210
		if ( get_option( 'woocommerce_ship_to_countries' ) !== 'specific' ) {
211
			return $this->countries;
212
		}
213
214
		$countries = array();
215
216
		$raw_countries = get_option( 'woocommerce_specific_ship_to_countries' );
217
218
		foreach ( $raw_countries as $country ) {
219
			$countries[ $country ] = $this->countries[ $country ];
220
		}
221
222
		return apply_filters( 'woocommerce_countries_shipping_countries', $countries );
223
	}
224
225
	/**
226
	 * get_allowed_country_states function.
227
	 * @return array
228
	 */
229 View Code Duplication
	public function get_allowed_country_states() {
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...
230
		if ( get_option( 'woocommerce_allowed_countries' ) !== 'specific' ) {
231
			return $this->states;
232
		}
233
234
		$states = array();
235
236
		$raw_countries = get_option( 'woocommerce_specific_allowed_countries' );
237
238
		foreach ( $raw_countries as $country ) {
239
			if ( isset( $this->states[ $country ] ) ) {
240
				$states[ $country ] = $this->states[ $country ];
241
			}
242
		}
243
244
		return apply_filters( 'woocommerce_countries_allowed_country_states', $states );
245
	}
246
247
	/**
248
	 * get_shipping_country_states function.
249
	 * @return array
250
	 */
251
	public function get_shipping_country_states() {
252
		if ( get_option( 'woocommerce_ship_to_countries' ) == '' ) {
253
			return $this->get_allowed_country_states();
254
		}
255
256
		if ( get_option( 'woocommerce_ship_to_countries' ) !== 'specific' ) {
257
			return $this->states;
258
		}
259
260
		$states = array();
261
262
		$raw_countries = get_option( 'woocommerce_specific_ship_to_countries' );
263
264
		foreach ( $raw_countries as $country ) {
265
			if ( ! empty( $this->states[ $country ] ) ) {
266
				$states[ $country ] = $this->states[ $country ];
267
			}
268
		}
269
270
		return apply_filters( 'woocommerce_countries_shipping_country_states', $states );
271
	}
272
273
	/**
274
	 * Gets an array of countries in the EU.
275
	 *
276
	 * MC (monaco) and IM (isle of man, part of UK) also use VAT.
277
	 *
278
	 * @param  $type Type of countries to retrieve. Blank for EU member countries. eu_vat for EU VAT countries.
279
	 * @return string[]
280
	 */
281
	public function get_european_union_countries( $type = '' ) {
282
		$countries = array( 'AT', 'BE', 'BG', 'CY', 'CZ', 'DE', 'DK', 'EE', 'ES', 'FI', 'FR', 'GB', 'GR', 'HU', 'HR', 'IE', 'IT', 'LT', 'LU', 'LV', 'MT', 'NL', 'PL', 'PT', 'RO', 'SE', 'SI', 'SK' );
283
284
		if ( 'eu_vat' === $type ) {
285
			$countries[] = 'MC';
286
			$countries[] = 'IM';
287
		}
288
289
		return $countries;
290
	}
291
292
	/**
293
	 * Gets the correct string for shipping - either 'to the' or 'to'
294
	 * @return string
295
	 */
296
	public function shipping_to_prefix() {
297
		$countries  = array( 'GB', 'US', 'AE', 'CZ', 'DO', 'NL', 'PH', 'USAF' );
298
		$return     = in_array( WC()->customer->get_shipping_country(), $countries ) ? __( 'to the', 'woocommerce' ) : __( 'to', 'woocommerce' );
299
300
		return apply_filters( 'woocommerce_countries_shipping_to_prefix', $return, WC()->customer->get_shipping_country() );
301
	}
302
303
	/**
304
	 * Prefix certain countries with 'the'
305
	 * @return string
306
	 */
307
	public function estimated_for_prefix() {
308
		$countries  = array( 'GB', 'US', 'AE', 'CZ', 'DO', 'NL', 'PH', 'USAF' );
309
		$return     = in_array( $this->get_base_country(), $countries ) ? __( 'the', 'woocommerce' ) . ' ' : '';
310
311
		return apply_filters( 'woocommerce_countries_estimated_for_prefix', $return, $this->get_base_country() );
312
	}
313
314
	/**
315
	 * Correctly name tax in some countries VAT on the frontend.
316
	 * @return string
317
	 */
318
	public function tax_or_vat() {
319
		$return = in_array( $this->get_base_country(), $this->get_european_union_countries( 'eu_vat' ) ) ? __( 'VAT', 'woocommerce' ) : __( 'Tax', 'woocommerce' );
320
321
		return apply_filters( 'woocommerce_countries_tax_or_vat', $return );
322
	}
323
324
	/**
325
	 * Include the Inc Tax label.
326
	 * @return string
327
	 */
328
	public function inc_tax_or_vat() {
329
		$return = in_array( $this->get_base_country(), $this->get_european_union_countries( 'eu_vat' ) ) ? __( '(incl. VAT)', 'woocommerce' ) : __( '(incl. tax)', 'woocommerce' );
330
331
		return apply_filters( 'woocommerce_countries_inc_tax_or_vat', $return );
332
	}
333
334
	/**
335
	 * Include the Ex Tax label.
336
	 * @return string
337
	 */
338
	public function ex_tax_or_vat() {
339
		$return = in_array( $this->get_base_country(), $this->get_european_union_countries( 'eu_vat' ) ) ? __( '(ex. VAT)', 'woocommerce' ) : __( '(ex. tax)', 'woocommerce' );
340
341
		return apply_filters( 'woocommerce_countries_ex_tax_or_vat', $return );
342
	}
343
344
	/**
345
	 * Outputs the list of countries and states for use in dropdown boxes.
346
	 * @param string $selected_country (default: '')
347
	 * @param string $selected_state (default: '')
348
	 * @param bool   $escape (default: false)
349
	 */
350
	public function country_dropdown_options( $selected_country = '', $selected_state = '', $escape = false ) {
351
		if ( $this->countries ) foreach ( $this->countries as $key => $value ) :
352
			if ( $states = $this->get_states( $key ) ) :
353
				echo '<optgroup label="' . esc_attr( $value ) . '">';
354
					foreach ( $states as $state_key => $state_value ) :
355
						echo '<option value="' . esc_attr( $key ) . ':' . $state_key . '"';
356
357
						if ( $selected_country == $key && $selected_state == $state_key ) {
358
							echo ' selected="selected"';
359
						}
360
361
						echo '>' . $value . ' &mdash; ' . ( $escape ? esc_js( $state_value ) : $state_value ) . '</option>';
362
					endforeach;
363
				echo '</optgroup>';
364
			else :
365
				echo '<option';
366
				if ( $selected_country == $key && $selected_state == '*' ) {
367
					echo ' selected="selected"';
368
				}
369
				echo ' value="' . esc_attr( $key ) . '">' . ( $escape ? esc_js( $value ) : $value ) . '</option>';
370
			endif;
371
		endforeach;
372
	}
373
374
	/**
375
	 * Get country address formats.
376
	 * @return array
377
	 */
378
	public function get_address_formats() {
379
		if ( ! $this->address_formats ) :
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->address_formats of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
380
381
			// Common formats
382
			$postcode_before_city = "{company}\n{name}\n{address_1}\n{address_2}\n{postcode} {city}\n{country}";
383
384
			// Define address formats
385
			$this->address_formats = apply_filters( 'woocommerce_localisation_address_formats', array(
386
				'default' => "{name}\n{company}\n{address_1}\n{address_2}\n{city}\n{state}\n{postcode}\n{country}",
387
				'AU' => "{name}\n{company}\n{address_1}\n{address_2}\n{city} {state} {postcode}\n{country}",
388
				'AT' => $postcode_before_city,
389
				'BE' => $postcode_before_city,
390
				'CA' => "{company}\n{name}\n{address_1}\n{address_2}\n{city} {state} {postcode}\n{country}",
391
				'CH' => $postcode_before_city,
392
				'CL' => "{company}\n{name}\n{address_1}\n{address_2}\n{state}\n{postcode} {city}\n{country}",
393
				'CN' => "{country} {postcode}\n{state}, {city}, {address_2}, {address_1}\n{company}\n{name}",
394
				'CZ' => $postcode_before_city,
395
				'DE' => $postcode_before_city,
396
				'EE' => $postcode_before_city,
397
				'FI' => $postcode_before_city,
398
				'DK' => $postcode_before_city,
399
				'FR' => "{company}\n{name}\n{address_1}\n{address_2}\n{postcode} {city_upper}\n{country}",
400
				'HK' => "{company}\n{first_name} {last_name_upper}\n{address_1}\n{address_2}\n{city_upper}\n{state_upper}\n{country}",
401
				'HU' => "{name}\n{company}\n{city}\n{address_1}\n{address_2}\n{postcode}\n{country}",
402
				'IN' => "{company}\n{name}\n{address_1}\n{address_2}\n{city} - {postcode}\n{state}, {country}",
403
				'IS' => $postcode_before_city,
404
				'IT' => "{company}\n{name}\n{address_1}\n{address_2}\n{postcode}\n{city}\n{state_upper}\n{country}",
405
				'JP' => "{postcode}\n{state}{city}{address_1}\n{address_2}\n{company}\n{last_name} {first_name}\n{country}",
406
				'TW' => "{company}\n{last_name} {first_name}\n{address_1}\n{address_2}\n{state}, {city} {postcode}\n{country}",
407
				'LI' => $postcode_before_city,
408
				'NL' => $postcode_before_city,
409
				'NZ' => "{name}\n{company}\n{address_1}\n{address_2}\n{city} {postcode}\n{country}",
410
				'NO' => $postcode_before_city,
411
				'PL' => $postcode_before_city,
412
				'SK' => $postcode_before_city,
413
				'SI' => $postcode_before_city,
414
				'ES' => "{name}\n{company}\n{address_1}\n{address_2}\n{postcode} {city}\n{state}\n{country}",
415
				'SE' => $postcode_before_city,
416
				'TR' => "{name}\n{company}\n{address_1}\n{address_2}\n{postcode} {city} {state}\n{country}",
417
				'US' => "{name}\n{company}\n{address_1}\n{address_2}\n{city}, {state_code} {postcode}\n{country}",
418
				'VN' => "{name}\n{company}\n{address_1}\n{city}\n{country}",
419
			));
420
		endif;
421
422
		return $this->address_formats;
423
	}
424
425
	/**
426
	 * Get country address format.
427
	 * @param  array  $args (default: array())
428
	 * @return string address
429
	 */
430
	public function get_formatted_address( $args = array() ) {
431
		$default_args = array(
432
			'first_name' => '',
433
			'last_name'  => '',
434
			'company'    => '',
435
			'address_1'  => '',
436
			'address_2'  => '',
437
			'city'       => '',
438
			'state'      => '',
439
			'postcode'   => '',
440
			'country'    => ''
441
		);
442
443
		$args = array_map( 'trim', wp_parse_args( $args, $default_args ) );
444
445
		extract( $args );
446
447
		// Get all formats
448
		$formats = $this->get_address_formats();
449
450
		// Get format for the address' country
451
		$format = ( $country && isset( $formats[ $country ] ) ) ? $formats[ $country ] : $formats['default'];
452
453
		// Handle full country name
454
		$full_country = ( isset( $this->countries[ $country ] ) ) ? $this->countries[ $country ] : $country;
455
456
		// Country is not needed if the same as base
457
		if ( $country == $this->get_base_country() && ! apply_filters( 'woocommerce_formatted_address_force_country_display', false ) ) {
458
			$format = str_replace( '{country}', '', $format );
459
		}
460
461
		// Handle full state name
462
		$full_state = ( $country && $state && isset( $this->states[ $country ][ $state ] ) ) ? $this->states[ $country ][ $state ] : $state;
463
464
		// Substitute address parts into the string
465
		$replace = array_map( 'esc_html', apply_filters( 'woocommerce_formatted_address_replacements', array(
466
			'{first_name}'       => $first_name,
467
			'{last_name}'        => $last_name,
468
			'{name}'             => $first_name . ' ' . $last_name,
469
			'{company}'          => $company,
470
			'{address_1}'        => $address_1,
471
			'{address_2}'        => $address_2,
472
			'{city}'             => $city,
473
			'{state}'            => $full_state,
474
			'{postcode}'         => $postcode,
475
			'{country}'          => $full_country,
476
			'{first_name_upper}' => strtoupper( $first_name ),
477
			'{last_name_upper}'  => strtoupper( $last_name ),
478
			'{name_upper}'       => strtoupper( $first_name . ' ' . $last_name ),
479
			'{company_upper}'    => strtoupper( $company ),
480
			'{address_1_upper}'  => strtoupper( $address_1 ),
481
			'{address_2_upper}'  => strtoupper( $address_2 ),
482
			'{city_upper}'       => strtoupper( $city ),
483
			'{state_upper}'      => strtoupper( $full_state ),
484
			'{state_code}'       => strtoupper( $state ),
485
			'{postcode_upper}'   => strtoupper( $postcode ),
486
			'{country_upper}'    => strtoupper( $full_country ),
487
		), $args ) );
488
489
		$formatted_address = str_replace( array_keys( $replace ), $replace, $format );
490
491
		// Clean up white space
492
		$formatted_address = preg_replace( '/  +/', ' ', trim( $formatted_address ) );
493
		$formatted_address = preg_replace( '/\n\n+/', "\n", $formatted_address );
494
495
		// Break newlines apart and remove empty lines/trim commas and white space
496
		$formatted_address = array_filter( array_map( array( $this, 'trim_formatted_address_line' ), explode( "\n", $formatted_address ) ) );
497
498
		// Add html breaks
499
		$formatted_address = implode( '<br/>', $formatted_address );
500
501
		// We're done!
502
		return $formatted_address;
503
	}
504
505
	/**
506
	 * Trim white space and commas off a line.
507
	 * @param  string
508
	 * @return string
509
	 */
510
	private function trim_formatted_address_line( $line ) {
511
		return trim( $line, ", " );
512
	}
513
514
	/**
515
	 * Returns the fields we show by default. This can be filtered later on.
516
	 * @return array
517
	 */
518
	public function get_default_address_fields() {
519
		$fields = array(
520
			'first_name' => array(
521
				'label'    => __( 'First Name', 'woocommerce' ),
522
				'required' => true,
523
				'class'    => array( 'form-row-first' ),
524
			),
525
			'last_name' => array(
526
				'label'    => __( 'Last Name', 'woocommerce' ),
527
				'required' => true,
528
				'class'    => array( 'form-row-last' ),
529
				'clear'    => true
530
			),
531
			'company' => array(
532
				'label' => __( 'Company Name', 'woocommerce' ),
533
				'class' => array( 'form-row-wide' ),
534
			),
535
			'country' => array(
536
				'type'     => 'country',
537
				'label'    => __( 'Country', 'woocommerce' ),
538
				'required' => true,
539
				'class'    => array( 'form-row-wide', 'address-field', 'update_totals_on_change' ),
540
			),
541
			'address_1' => array(
542
				'label'       => __( 'Address', 'woocommerce' ),
543
				'placeholder' => _x( 'Street address', 'placeholder', 'woocommerce' ),
544
				'required'    => true,
545
				'class'       => array( 'form-row-wide', 'address-field' )
546
			),
547
			'address_2' => array(
548
				'placeholder' => _x( 'Apartment, suite, unit etc. (optional)', 'placeholder', 'woocommerce' ),
549
				'class'       => array( 'form-row-wide', 'address-field' ),
550
				'required'    => false
551
			),
552
			'city' => array(
553
				'label'       => __( 'Town / City', 'woocommerce' ),
554
				'placeholder' => __( 'Town / City', 'woocommerce' ),
555
				'required'    => true,
556
				'class'       => array( 'form-row-wide', 'address-field' )
557
			),
558
			'state' => array(
559
				'type'        => 'state',
560
				'label'       => __( 'State / County', 'woocommerce' ),
561
				'required'    => true,
562
				'class'       => array( 'form-row-first', 'address-field' ),
563
				'validate'    => array( 'state' )
564
			),
565
			'postcode' => array(
566
				'label'       => __( 'Postcode / ZIP', 'woocommerce' ),
567
				'placeholder' => __( 'Postcode / ZIP', 'woocommerce' ),
568
				'required'    => true,
569
				'class'       => array( 'form-row-last', 'address-field' ),
570
				'clear'       => true,
571
				'validate'    => array( 'postcode' )
572
			),
573
		);
574
575
		return apply_filters( 'woocommerce_default_address_fields', $fields );
576
	}
577
578
	/**
579
	 * Get JS selectors for fields which are shown/hidden depending on the locale.
580
	 * @return array
581
	 */
582
	public function get_country_locale_field_selectors() {
583
		$locale_fields = array (
584
			'address_1' => '#billing_address_1_field, #shipping_address_1_field',
585
			'address_2' => '#billing_address_2_field, #shipping_address_2_field',
586
			'state'     => '#billing_state_field, #shipping_state_field, #calc_shipping_state_field',
587
			'postcode'  => '#billing_postcode_field, #shipping_postcode_field, #calc_shipping_postcode_field',
588
			'city'      => '#billing_city_field, #shipping_city_field, #calc_shipping_city_field',
589
		);
590
		return apply_filters( 'woocommerce_country_locale_field_selectors', $locale_fields );
591
	}
592
593
	/**
594
	 * Get country locale settings.
595
	 * @return array
596
	 * @todo  [2.4] Check select2 4.0.0 compatibility with `placeholder` attribute and uncomment relevant lines. https://github.com/woothemes/woocommerce/issues/7729
597
	 */
598
	public function get_country_locale() {
599
		if ( ! $this->locale ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->locale of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
600
601
			// Locale information used by the checkout
602
			$this->locale = apply_filters( 'woocommerce_get_country_locale', array(
603
				'AE' => array(
604
					'postcode' => array(
605
						'required' => false,
606
						'hidden'   => true
607
					),
608
				),
609
				'AF' => array(
610
					'state' => array(
611
						'required' => false,
612
					),
613
				),
614
				'AT' => array(
615
					'postcode_before_city' => true,
616
					'state' => array(
617
						'required' => false
618
					)
619
				),
620
				'AU' => array(
621
					'city'      => array(
622
						'label'       => __( 'Suburb', 'woocommerce' ),
623
						'placeholder' => __( 'Suburb', 'woocommerce' ),
624
					),
625
					'postcode'  => array(
626
						'label'       => __( 'Postcode', 'woocommerce' ),
627
						'placeholder' => __( 'Postcode', 'woocommerce' ),
628
					),
629
					'state'     => array(
630
						'label'       => __( 'State', 'woocommerce' ),
631
						//'placeholder' => __( 'State', 'woocommerce' ),
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
632
					)
633
				),
634
				'AX' => array(
635
					'postcode_before_city' => true,
636
					'state' => array(
637
						'required' => false,
638
					),
639
				),
640
				'BD' => array(
641
					'postcode' => array(
642
						'required' => false
643
					),
644
					'state' => array(
645
						'label'       => __( 'District', 'woocommerce' ),
646
						//'placeholder' => __( 'District', 'woocommerce' ),
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
647
					)
648
				),
649
				'BE' => array(
650
					'postcode_before_city' => true,
651
					'state' => array(
652
						'required'    => false,
653
						'label'       => __( 'Province', 'woocommerce' ),
654
						//'placeholder' => __( 'Province', 'woocommerce' ),
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
655
					),
656
				),
657
				'BI' => array(
658
					'state' => array(
659
						'required' => false,
660
					),
661
				),
662
				'BO' => array(
663
					'postcode' => array(
664
						'required' => false,
665
						'hidden'   => true
666
					),
667
				),
668
				'BS' => array(
669
					'postcode' => array(
670
						'required' => false,
671
						'hidden'   => true
672
					),
673
				),
674
				'CA' => array(
675
					'state' => array(
676
						'label'       => __( 'Province', 'woocommerce' ),
677
						//'placeholder' => __( 'Province', 'woocommerce' ),
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
678
					)
679
				),
680
				'CH' => array(
681
					'postcode_before_city' => true,
682
					'state' => array(
683
						'label'       => __( 'Canton', 'woocommerce' ),
684
						//'placeholder' => __( 'Canton', 'woocommerce' ),
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
685
						'required'    => false
686
					)
687
				),
688
				'CL' => array(
689
					'city'      => array(
690
						'required' 	=> true,
691
					),
692
					'postcode'  => array(
693
						'required' => false
694
					),
695
					'state'     => array(
696
						'label'       => __( 'Region', 'woocommerce' ),
697
						//'placeholder' => __( 'Region', 'woocommerce' )
0 ignored issues
show
Unused Code Comprehensibility introduced by
54% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
698
					)
699
				),
700
				'CN' => array(
701
					'state' => array(
702
						'label'       => __( 'Province', 'woocommerce' ),
703
						//'placeholder' => __( 'Province', 'woocommerce' ),
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
704
					)
705
				),
706
				'CO' => array(
707
					'postcode' => array(
708
						'required' => false
709
					)
710
				),
711
				'CZ' => array(
712
					'state' => array(
713
						'required' => false
714
					)
715
				),
716
				'DE' => array(
717
					'postcode_before_city' => true,
718
					'state' => array(
719
						'required' => false
720
					)
721
				),
722
				'DK' => array(
723
					'postcode_before_city' => true,
724
					'state' => array(
725
						'required' => false
726
					)
727
				),
728
				'EE' => array(
729
					'postcode_before_city' => true,
730
					'state' => array(
731
						'required' => false
732
					)
733
				),
734
				'FI' => array(
735
					'postcode_before_city' => true,
736
					'state' => array(
737
						'required' => false
738
					)
739
				),
740
				'FR' => array(
741
					'postcode_before_city' => true,
742
					'state' => array(
743
						'required' => false
744
					)
745
				),
746
				'HK' => array(
747
					'postcode' => array(
748
						'required' => false
749
					),
750
					'city'  => array(
751
						'label'       => __( 'Town / District', 'woocommerce' ),
752
						'placeholder' => __( 'Town / District', 'woocommerce' )
753
					),
754
					'state' => array(
755
						'label'       => __( 'Region', 'woocommerce' ),
756
						//'placeholder' => __( 'Region', 'woocommerce' ),
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
757
					)
758
				),
759
				'HU' => array(
760
					'state' => array(
761
						'label'       => __( 'County', 'woocommerce' ),
762
						//'placeholder' => __( 'County', 'woocommerce' ),
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
763
					)
764
				),
765
				'ID' => array(
766
					'state' => array(
767
						'label'       => __( 'Province', 'woocommerce' ),
768
						//'placeholder' => __( 'Province', 'woocommerce' ),
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
769
					)
770
				),
771
				'IS' => array(
772
					'postcode_before_city' => true,
773
					'state' => array(
774
						'required' => false
775
					)
776
				),
777
				'IL' => array(
778
					'postcode_before_city' => true,
779
					'state' => array(
780
						'required' => false
781
					)
782
				),
783
				'IT' => array(
784
					'postcode_before_city' => true,
785
					'state' => array(
786
						'required'    => true,
787
						'label'       => __( 'Province', 'woocommerce' ),
788
						//'placeholder' => __( 'Province', 'woocommerce' ),
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
789
					)
790
				),
791
				'JP' => array(
792
					'state' => array(
793
						'label' => __( 'Prefecture', 'woocommerce' )
794
					)
795
				),
796
				'KR' => array(
797
					'state' => array(
798
						'required' => false
799
					)
800
				),
801
				'NL' => array(
802
					'postcode_before_city' => true,
803
					'state' => array(
804
						'required'    => false,
805
						'label'       => __( 'Province', 'woocommerce' ),
806
						//'placeholder' => __( 'Province', 'woocommerce' ),
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
807
					)
808
				),
809
				'NZ' => array(
810
					'state' => array(
811
						'required' => false
812
					)
813
				),
814
				'NO' => array(
815
					'postcode_before_city' => true,
816
					'state' => array(
817
						'required' => false
818
					)
819
				),
820
				'NP' => array(
821
					'state' => array(
822
						'label'       => __( 'State / Zone', 'woocommerce' ),
823
						//'placeholder' => __( 'State / Zone', 'woocommerce' ),
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
824
					),
825
					'postcode' => array(
826
						'required' => false
827
					)
828
				),
829
				'PL' => array(
830
					'postcode_before_city' => true,
831
					'state' => array(
832
						'required' => false
833
					)
834
				),
835
				'PT' => array(
836
					'state' => array(
837
						'required' => false
838
					)
839
				),
840
				'RO' => array(
841
					'state' => array(
842
						'required' => false
843
					)
844
				),
845
				'SG' => array(
846
					'state' => array(
847
						'required' => false
848
					)
849
				),
850
				'SK' => array(
851
					'postcode_before_city' => true,
852
					'state' => array(
853
						'required' => false
854
					)
855
				),
856
				'SI' => array(
857
					'postcode_before_city' => true,
858
					'state' => array(
859
						'required' => false
860
					)
861
				),
862
				'ES' => array(
863
					'postcode_before_city' => true,
864
					'state' => array(
865
						'label'       => __( 'Province', 'woocommerce' ),
866
						//'placeholder' => __( 'Province', 'woocommerce' ),
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
867
					)
868
				),
869
				'LI' => array(
870
					'postcode_before_city' => true,
871
					'state' => array(
872
						'label'       => __( 'Municipality', 'woocommerce' ),
873
						//'placeholder' => __( 'Municipality', 'woocommerce' ),
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
874
						'required'    => false
875
					)
876
				),
877
				'LK' => array(
878
					'state' => array(
879
						'required' => false
880
					)
881
				),
882
				'SE' => array(
883
					'postcode_before_city' => true,
884
					'state' => array(
885
						'required' => false
886
					)
887
				),
888
				'TR' => array(
889
					'postcode_before_city' => true,
890
					'state' => array(
891
						'label'       => __( 'Province', 'woocommerce' ),
892
						//'placeholder' => __( 'Province', 'woocommerce' ),
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
893
					)
894
				),
895
				'US' => array(
896
					'postcode'  => array(
897
						'label'       => __( 'ZIP', 'woocommerce' ),
898
						'placeholder' => __( 'ZIP', 'woocommerce' ),
899
					),
900
					'state'     => array(
901
						'label'       => __( 'State', 'woocommerce' ),
902
						//'placeholder' => __( 'State', 'woocommerce' ),
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
903
					)
904
				),
905
				'GB' => array(
906
					'postcode'  => array(
907
						'label'       => __( 'Postcode', 'woocommerce' ),
908
						'placeholder' => __( 'Postcode', 'woocommerce' ),
909
					),
910
					'state'     => array(
911
						'label'       => __( 'County', 'woocommerce' ),
912
						//'placeholder' => __( 'County', 'woocommerce' ),
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
913
						'required'    => false
914
					)
915
				),
916
				'VN' => array(
917
					'postcode_before_city' => true,
918
					'state' => array(
919
						'required' => false
920
					),
921
					'postcode' => array(
922
						'required' => false,
923
						'hidden'   => false
924
					),
925
					'address_2' => array(
926
						'required' => false,
927
						'hidden'   => true
928
					)
929
				),
930
				'WS' => array(
931
					'postcode' => array(
932
						'required' => false,
933
						'hidden'   => true
934
					),
935
				),
936
				'ZA' => array(
937
					'state' => array(
938
						'label'       => __( 'Province', 'woocommerce' ),
939
						//'placeholder' => __( 'Province', 'woocommerce' ),
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
940
					)
941
				),
942
				'ZW' => array(
943
					'postcode' => array(
944
						'required' => false,
945
						'hidden'   => true
946
					),
947
				),
948
			));
949
950
			$this->locale = array_intersect_key( $this->locale, array_merge( $this->get_allowed_countries(), $this->get_shipping_countries() ) );
951
952
			// Default Locale Can be filtered to override fields in get_address_fields().
953
			// Countries with no specific locale will use default.
954
			$this->locale['default'] = apply_filters('woocommerce_get_country_locale_default', $this->get_default_address_fields() );
955
956
			// Filter default AND shop base locales to allow overides via a single function. These will be used when changing countries on the checkout
957
			if ( ! isset( $this->locale[ $this->get_base_country() ] ) ) {
958
				$this->locale[ $this->get_base_country() ] = $this->locale['default'];
959
			}
960
961
			$this->locale['default']                   = apply_filters( 'woocommerce_get_country_locale_base', $this->locale['default'] );
962
			$this->locale[ $this->get_base_country() ] = apply_filters( 'woocommerce_get_country_locale_base', $this->locale[ $this->get_base_country() ] );
963
		}
964
965
		return $this->locale;
966
	}
967
968
	/**
969
	 * Apply locale and get address fields.
970
	 * @param  mixed  $country
971
	 * @param  string $type (default: 'billing_')
972
	 * @return array
973
	 */
974
	public function get_address_fields( $country = '', $type = 'billing_' ) {
975
		if ( ! $country ) {
976
			$country = $this->get_base_country();
977
		}
978
979
		$fields = $this->get_default_address_fields();
980
		$locale = $this->get_country_locale();
981
982
		if ( isset( $locale[ $country ] ) ) {
983
			$fields = wc_array_overlay( $fields, $locale[ $country ] );
984
		}
985
986
		// Prepend field keys
987
		$address_fields = array();
988
989
		foreach ( $fields as $key => $value ) {
990
			$keys = array_keys( $fields );
991
			$address_fields[ $type . $key ] = $value;
992
993
			// Add email and phone after company or last
994
			if ( $type == 'billing_' && ( 'company' === $key || ( ! array_key_exists( 'company', $fields ) && $key === end( $keys ) ) ) ) {
995
				$address_fields['billing_email'] = array(
996
					'label'		=> __( 'Email Address', 'woocommerce' ),
997
					'required'	=> true,
998
					'type'		=> 'email',
999
					'class'		=> array( 'form-row-first' ),
1000
					'validate'	=> array( 'email' ),
1001
				);
1002
				$address_fields['billing_phone'] = array(
1003
					'label'    	=> __( 'Phone', 'woocommerce' ),
1004
					'required' 	=> true,
1005
					'type'		=> 'tel',
1006
					'class'    	=> array( 'form-row-last' ),
1007
					'clear'    	=> true,
1008
					'validate' 	=> array( 'phone' ),
1009
				);
1010
			}
1011
		}
1012
1013
		$address_fields = apply_filters( 'woocommerce_' . $type . 'fields', $address_fields, $country );
1014
1015
		return $address_fields;
1016
	}
1017
}
1018