Completed
Push — master ( fb5f9c...377d79 )
by Mike
64:31 queued 55:52
created

wc-core-functions.php ➔ wc_get_template()   B

Complexity

Conditions 6
Paths 10

Size

Total Lines 38

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 6

Importance

Changes 0
Metric Value
cc 6
nc 10
nop 4
dl 0
loc 38
ccs 5
cts 5
cp 1
crap 6
rs 8.6897
c 0
b 0
f 0
1
<?php
2
/**
3
 * WooCommerce Core Functions
4
 *
5
 * General core functions available on both the front-end and admin.
6
 *
7
 * @package WooCommerce\Functions
8
 * @version 3.3.0
9
 */
10
11
if ( ! defined( 'ABSPATH' ) ) {
12
	exit;
13
}
14
15
// Include core functions (available in both admin and frontend).
16
require WC_ABSPATH . 'includes/wc-conditional-functions.php';
17
require WC_ABSPATH . 'includes/wc-coupon-functions.php';
18
require WC_ABSPATH . 'includes/wc-user-functions.php';
19
require WC_ABSPATH . 'includes/wc-deprecated-functions.php';
20
require WC_ABSPATH . 'includes/wc-formatting-functions.php';
21
require WC_ABSPATH . 'includes/wc-order-functions.php';
22
require WC_ABSPATH . 'includes/wc-order-item-functions.php';
23
require WC_ABSPATH . 'includes/wc-page-functions.php';
24
require WC_ABSPATH . 'includes/wc-product-functions.php';
25
require WC_ABSPATH . 'includes/wc-stock-functions.php';
26
require WC_ABSPATH . 'includes/wc-account-functions.php';
27
require WC_ABSPATH . 'includes/wc-term-functions.php';
28
require WC_ABSPATH . 'includes/wc-attribute-functions.php';
29
require WC_ABSPATH . 'includes/wc-rest-functions.php';
30
require WC_ABSPATH . 'includes/wc-widget-functions.php';
31
require WC_ABSPATH . 'includes/wc-webhook-functions.php';
32
33
/**
34
 * Filters on data used in admin and frontend.
35
 */
36
add_filter( 'woocommerce_coupon_code', 'html_entity_decode' );
37
add_filter( 'woocommerce_coupon_code', 'sanitize_text_field' );
38
add_filter( 'woocommerce_coupon_code', 'wc_strtolower' );
39
add_filter( 'woocommerce_stock_amount', 'intval' ); // Stock amounts are integers by default.
40
add_filter( 'woocommerce_shipping_rate_label', 'sanitize_text_field' ); // Shipping rate label.
41
add_filter( 'woocommerce_attribute_label', 'wp_kses_post', 100 );
42
43
/**
44
 * Short Description (excerpt).
45
 */
46
if ( function_exists( 'do_blocks' ) ) {
47
	add_filter( 'woocommerce_short_description', 'do_blocks', 9 );
48
}
49
add_filter( 'woocommerce_short_description', 'wptexturize' );
50
add_filter( 'woocommerce_short_description', 'convert_smilies' );
51
add_filter( 'woocommerce_short_description', 'convert_chars' );
52
add_filter( 'woocommerce_short_description', 'wpautop' );
53
add_filter( 'woocommerce_short_description', 'shortcode_unautop' );
54
add_filter( 'woocommerce_short_description', 'prepend_attachment' );
55
add_filter( 'woocommerce_short_description', 'do_shortcode', 11 ); // After wpautop().
56
add_filter( 'woocommerce_short_description', 'wc_format_product_short_description', 9999999 );
57
add_filter( 'woocommerce_short_description', 'wc_do_oembeds' );
58
add_filter( 'woocommerce_short_description', array( $GLOBALS['wp_embed'], 'run_shortcode' ), 8 ); // Before wpautop().
59
60
/**
61
 * Define a constant if it is not already defined.
62
 *
63
 * @since 3.0.0
64
 * @param string $name  Constant name.
65 4
 * @param mixed  $value Value.
66 1
 */
67
function wc_maybe_define_constant( $name, $value ) {
68
	if ( ! defined( $name ) ) {
69
		define( $name, $value );
70
	}
71
}
72
73
/**
74
 * Create a new order programmatically.
75
 *
76
 * Returns a new order object on success which can then be used to add additional data.
77
 *
78
 * @param  array $args Order arguments.
79
 * @return WC_Order|WP_Error
80 97
 */
81
function wc_create_order( $args = array() ) {
82
	$default_args = array(
83
		'status'        => null,
84
		'customer_id'   => null,
85
		'customer_note' => null,
86
		'parent'        => null,
87
		'created_via'   => null,
88
		'cart_hash'     => null,
89
		'order_id'      => 0,
90 97
	);
91 97
92
	try {
93
		$args  = wp_parse_args( $args, $default_args );
94 97
		$order = new WC_Order( $args['order_id'] );
95
96
		// Update props that were set (not null).
97
		if ( ! is_null( $args['parent'] ) ) {
98 97
			$order->set_parent_id( absint( $args['parent'] ) );
99 97
		}
100
101
		if ( ! is_null( $args['status'] ) ) {
102 97
			$order->set_status( $args['status'] );
103 96
		}
104
105
		if ( ! is_null( $args['customer_note'] ) ) {
106 97
			$order->set_customer_note( $args['customer_note'] );
107 97
		}
108
109
		if ( ! is_null( $args['customer_id'] ) ) {
110 97
			$order->set_customer_id( is_numeric( $args['customer_id'] ) ? absint( $args['customer_id'] ) : 0 );
111 1
		}
112
113
		if ( ! is_null( $args['created_via'] ) ) {
114 97
			$order->set_created_via( sanitize_text_field( $args['created_via'] ) );
115 1
		}
116
117
		if ( ! is_null( $args['cart_hash'] ) ) {
118
			$order->set_cart_hash( sanitize_text_field( $args['cart_hash'] ) );
119 97
		}
120 97
121 97
		// Set these fields when creating a new order but not when updating an existing order.
122 97
		if ( ! $args['order_id'] ) {
123 97
			$order->set_currency( get_woocommerce_currency() );
124
			$order->set_prices_include_tax( 'yes' === get_option( 'woocommerce_prices_include_tax' ) );
125
			$order->set_customer_ip_address( WC_Geolocation::get_ip_address() );
126
			$order->set_customer_user_agent( wc_get_user_agent() );
127 97
		}
128
129
		// Update other order props set automatically.
130
		$order->save();
131
	} catch ( Exception $e ) {
132 97
		return new WP_Error( 'error', $e->getMessage() );
133
	}
134
135
	return $order;
136
}
137
138
/**
139
 * Update an order. Uses wc_create_order.
140
 *
141
 * @param  array $args Order arguments.
142 1
 * @return WC_Order|WP_Error
143 1
 */
144
function wc_update_order( $args ) {
145 1
	if ( empty( $args['order_id'] ) ) {
146
		return new WP_Error( __( 'Invalid order ID.', 'woocommerce' ) );
147
	}
148
	return wc_create_order( $args );
149
}
150
151
/**
152
 * Get template part (for templates like the shop-loop).
153
 *
154
 * WC_TEMPLATE_DEBUG_MODE will prevent overrides in themes from taking priority.
155
 *
156
 * @param mixed  $slug Template slug.
157 1
 * @param string $name Template name (default: '').
158
 */
159
function wc_get_template_part( $slug, $name = '' ) {
160 1
	$cache_key = sanitize_key( implode( '-', array( 'template-part', $slug, $name ) ) );
161
	$template  = (string) wp_cache_get( $cache_key, 'woocommerce' );
162
163
	if ( ! $template ) {
164
		if ( $name ) {
165 1
			$template = WC_TEMPLATE_DEBUG_MODE ? '' : locate_template(
166
				array(
167
					"{$slug}-{$name}.php",
168
					WC()->template_path() . "{$slug}-{$name}.php",
169
				)
170 1
			);
171 1
172
			if ( ! $template ) {
173
				$fallback = WC()->plugin_path() . "/templates/{$slug}-{$name}.php";
174
				$template = file_exists( $fallback ) ? $fallback : '';
175 1
			}
176
		}
177 1
178
		if ( ! $template ) {
179
			// If template file doesn't exist, look in yourtheme/slug.php and yourtheme/woocommerce/slug.php.
180
			$template = WC_TEMPLATE_DEBUG_MODE ? '' : locate_template(
181
				array(
182
					"{$slug}.php",
183
					WC()->template_path() . "{$slug}.php",
184
				)
185
			);
186
		}
187
188
		wp_cache_set( $cache_key, $template, 'woocommerce' );
189
	}
190
191 5
	// Allow 3rd party plugins to filter template file from their plugin.
192 5
	$template = apply_filters( 'wc_get_template_part', $template, $slug, $name );
193
194
	if ( $template ) {
195 5
		load_template( $template, false );
196
	}
197 5
}
198
199
/**
200
 * Get other templates (e.g. product attributes) passing attributes and including the file.
201
 *
202
 * @param string $template_name Template name.
203
 * @param array  $args          Arguments. (default: array).
204 5
 * @param string $template_path Template path. (default: '').
205
 * @param string $default_path  Default path. (default: '').
206 5
 */
207
function wc_get_template( $template_name, $args = array(), $template_path = '', $default_path = '' ) {
208 5
	$cache_key = sanitize_key( implode( '-', array( 'template', $template_name, $template_path, $default_path ) ) );
209
	$template  = (string) wp_cache_get( $cache_key, 'woocommerce' );
210 5
211
	if ( ! $template ) {
212
		$template = wc_locate_template( $template_name, $template_path, $default_path );
213
		wp_cache_set( $cache_key, $template, 'woocommerce' );
214
	}
215
216
	// Allow 3rd party plugin filter template file from their plugin.
217
	$filter_template = apply_filters( 'wc_get_template', $template, $template_name, $args, $template_path, $default_path );
218
219
	if ( $filter_template !== $template ) {
220
		if ( ! file_exists( $filter_template ) ) {
221
			/* translators: %s template */
222
			wc_doing_it_wrong( __FUNCTION__, sprintf( __( '%s does not exist.', 'woocommerce' ), '<code>' . $template . '</code>' ), '2.1' );
223
			return;
224
		}
225
		$template = $filter_template;
226
	}
227 1
228 1
	$action_args = array(
229 1
		'template_name' => $template_name,
230
		'template_path' => $template_path,
231
		'located'       => $template,
232
		'args'          => $args,
233
	);
234
235
	if ( ! empty( $args ) && is_array( $args ) ) {
236
		extract( $args ); // @codingStandardsIgnoreLine
237
	}
238
239
	do_action( 'woocommerce_before_template_part', $action_args['template_name'], $action_args['template_path'], $action_args['located'], $action_args['args'] );
240
241
	include $template;
242
243
	do_action( 'woocommerce_after_template_part', $action_args['template_name'], $action_args['template_path'], $action_args['located'], $action_args['args'] );
244
}
245
246 5
/**
247 5
 * Like wc_get_template, but returns the HTML instead of outputting.
248
 *
249
 * @see wc_get_template
250 5
 * @since 2.5.0
251 5
 * @param string $template_name Template name.
252
 * @param array  $args          Arguments. (default: array).
253
 * @param string $template_path Template path. (default: '').
254
 * @param string $default_path  Default path. (default: '').
255 5
 *
256
 * @return string
257 5
 */
258 5
function wc_get_template_html( $template_name, $args = array(), $template_path = '', $default_path = '' ) {
259
	ob_start();
260
	wc_get_template( $template_name, $args, $template_path, $default_path );
261
	return ob_get_clean();
262
}
263 5
/**
264 5
 * Locate a template and return the path for inclusion.
265
 *
266
 * This is the load order:
267
 *
268 5
 * yourtheme/$template_path/$template_name
269
 * yourtheme/$template_name
270
 * $default_path/$template_name
271
 *
272
 * @param string $template_name Template name.
273
 * @param string $template_path Template path. (default: '').
274
 * @param string $default_path  Default path. (default: '').
275
 * @return string
276
 */
277 550
function wc_locate_template( $template_name, $template_path = '', $default_path = '' ) {
278
	if ( ! $template_path ) {
279
		$template_path = WC()->template_path();
280
	}
281
282
	if ( ! $default_path ) {
283
		$default_path = WC()->plugin_path() . '/templates/';
284
	}
285
286 539
	// Look within passed path within the theme - this is priority.
287
	$template = locate_template(
288 539
		array(
289
			trailingslashit( $template_path ) . $template_name,
290
			$template_name,
291
		)
292
	);
293
294
	// Get default template/.
295
	if ( ! $template || WC_TEMPLATE_DEBUG_MODE ) {
296
		$template = $default_path . $template_name;
297
	}
298
299
	// Return what we found.
300
	return apply_filters( 'woocommerce_locate_template', $template, $template_name, $template_path );
301
}
302
303
/**
304
 * Get Base Currency Code.
305
 *
306
 * @return string
307
 */
308
function get_woocommerce_currency() {
309
	return apply_filters( 'woocommerce_currency', get_option( 'woocommerce_currency' ) );
310
}
311
312
/**
313
 * Get full list of currency codes.
314
 *
315
 * @return array
316
 */
317
function get_woocommerce_currencies() {
318
	static $currencies;
319
320
	if ( ! isset( $currencies ) ) {
321
		$currencies = array_unique(
322
			apply_filters(
323
				'woocommerce_currencies',
324
				array(
325
					'AED' => __( 'United Arab Emirates dirham', 'woocommerce' ),
326
					'AFN' => __( 'Afghan afghani', 'woocommerce' ),
327
					'ALL' => __( 'Albanian lek', 'woocommerce' ),
328
					'AMD' => __( 'Armenian dram', 'woocommerce' ),
329
					'ANG' => __( 'Netherlands Antillean guilder', 'woocommerce' ),
330
					'AOA' => __( 'Angolan kwanza', 'woocommerce' ),
331
					'ARS' => __( 'Argentine peso', 'woocommerce' ),
332
					'AUD' => __( 'Australian dollar', 'woocommerce' ),
333
					'AWG' => __( 'Aruban florin', 'woocommerce' ),
334
					'AZN' => __( 'Azerbaijani manat', 'woocommerce' ),
335
					'BAM' => __( 'Bosnia and Herzegovina convertible mark', 'woocommerce' ),
336
					'BBD' => __( 'Barbadian dollar', 'woocommerce' ),
337
					'BDT' => __( 'Bangladeshi taka', 'woocommerce' ),
338
					'BGN' => __( 'Bulgarian lev', 'woocommerce' ),
339
					'BHD' => __( 'Bahraini dinar', 'woocommerce' ),
340
					'BIF' => __( 'Burundian franc', 'woocommerce' ),
341
					'BMD' => __( 'Bermudian dollar', 'woocommerce' ),
342
					'BND' => __( 'Brunei dollar', 'woocommerce' ),
343
					'BOB' => __( 'Bolivian boliviano', 'woocommerce' ),
344
					'BRL' => __( 'Brazilian real', 'woocommerce' ),
345
					'BSD' => __( 'Bahamian dollar', 'woocommerce' ),
346
					'BTC' => __( 'Bitcoin', 'woocommerce' ),
347
					'BTN' => __( 'Bhutanese ngultrum', 'woocommerce' ),
348
					'BWP' => __( 'Botswana pula', 'woocommerce' ),
349
					'BYR' => __( 'Belarusian ruble (old)', 'woocommerce' ),
350
					'BYN' => __( 'Belarusian ruble', 'woocommerce' ),
351
					'BZD' => __( 'Belize dollar', 'woocommerce' ),
352
					'CAD' => __( 'Canadian dollar', 'woocommerce' ),
353
					'CDF' => __( 'Congolese franc', 'woocommerce' ),
354
					'CHF' => __( 'Swiss franc', 'woocommerce' ),
355
					'CLP' => __( 'Chilean peso', 'woocommerce' ),
356
					'CNY' => __( 'Chinese yuan', 'woocommerce' ),
357
					'COP' => __( 'Colombian peso', 'woocommerce' ),
358
					'CRC' => __( 'Costa Rican col&oacute;n', 'woocommerce' ),
359
					'CUC' => __( 'Cuban convertible peso', 'woocommerce' ),
360
					'CUP' => __( 'Cuban peso', 'woocommerce' ),
361
					'CVE' => __( 'Cape Verdean escudo', 'woocommerce' ),
362
					'CZK' => __( 'Czech koruna', 'woocommerce' ),
363
					'DJF' => __( 'Djiboutian franc', 'woocommerce' ),
364
					'DKK' => __( 'Danish krone', 'woocommerce' ),
365
					'DOP' => __( 'Dominican peso', 'woocommerce' ),
366
					'DZD' => __( 'Algerian dinar', 'woocommerce' ),
367
					'EGP' => __( 'Egyptian pound', 'woocommerce' ),
368
					'ERN' => __( 'Eritrean nakfa', 'woocommerce' ),
369
					'ETB' => __( 'Ethiopian birr', 'woocommerce' ),
370
					'EUR' => __( 'Euro', 'woocommerce' ),
371
					'FJD' => __( 'Fijian dollar', 'woocommerce' ),
372
					'FKP' => __( 'Falkland Islands pound', 'woocommerce' ),
373
					'GBP' => __( 'Pound sterling', 'woocommerce' ),
374
					'GEL' => __( 'Georgian lari', 'woocommerce' ),
375
					'GGP' => __( 'Guernsey pound', 'woocommerce' ),
376
					'GHS' => __( 'Ghana cedi', 'woocommerce' ),
377
					'GIP' => __( 'Gibraltar pound', 'woocommerce' ),
378
					'GMD' => __( 'Gambian dalasi', 'woocommerce' ),
379
					'GNF' => __( 'Guinean franc', 'woocommerce' ),
380
					'GTQ' => __( 'Guatemalan quetzal', 'woocommerce' ),
381
					'GYD' => __( 'Guyanese dollar', 'woocommerce' ),
382
					'HKD' => __( 'Hong Kong dollar', 'woocommerce' ),
383
					'HNL' => __( 'Honduran lempira', 'woocommerce' ),
384
					'HRK' => __( 'Croatian kuna', 'woocommerce' ),
385
					'HTG' => __( 'Haitian gourde', 'woocommerce' ),
386
					'HUF' => __( 'Hungarian forint', 'woocommerce' ),
387
					'IDR' => __( 'Indonesian rupiah', 'woocommerce' ),
388
					'ILS' => __( 'Israeli new shekel', 'woocommerce' ),
389
					'IMP' => __( 'Manx pound', 'woocommerce' ),
390
					'INR' => __( 'Indian rupee', 'woocommerce' ),
391
					'IQD' => __( 'Iraqi dinar', 'woocommerce' ),
392
					'IRR' => __( 'Iranian rial', 'woocommerce' ),
393
					'IRT' => __( 'Iranian toman', 'woocommerce' ),
394
					'ISK' => __( 'Icelandic kr&oacute;na', 'woocommerce' ),
395
					'JEP' => __( 'Jersey pound', 'woocommerce' ),
396
					'JMD' => __( 'Jamaican dollar', 'woocommerce' ),
397
					'JOD' => __( 'Jordanian dinar', 'woocommerce' ),
398
					'JPY' => __( 'Japanese yen', 'woocommerce' ),
399
					'KES' => __( 'Kenyan shilling', 'woocommerce' ),
400
					'KGS' => __( 'Kyrgyzstani som', 'woocommerce' ),
401
					'KHR' => __( 'Cambodian riel', 'woocommerce' ),
402
					'KMF' => __( 'Comorian franc', 'woocommerce' ),
403
					'KPW' => __( 'North Korean won', 'woocommerce' ),
404
					'KRW' => __( 'South Korean won', 'woocommerce' ),
405
					'KWD' => __( 'Kuwaiti dinar', 'woocommerce' ),
406
					'KYD' => __( 'Cayman Islands dollar', 'woocommerce' ),
407
					'KZT' => __( 'Kazakhstani tenge', 'woocommerce' ),
408
					'LAK' => __( 'Lao kip', 'woocommerce' ),
409
					'LBP' => __( 'Lebanese pound', 'woocommerce' ),
410
					'LKR' => __( 'Sri Lankan rupee', 'woocommerce' ),
411
					'LRD' => __( 'Liberian dollar', 'woocommerce' ),
412
					'LSL' => __( 'Lesotho loti', 'woocommerce' ),
413
					'LYD' => __( 'Libyan dinar', 'woocommerce' ),
414
					'MAD' => __( 'Moroccan dirham', 'woocommerce' ),
415
					'MDL' => __( 'Moldovan leu', 'woocommerce' ),
416
					'MGA' => __( 'Malagasy ariary', 'woocommerce' ),
417
					'MKD' => __( 'Macedonian denar', 'woocommerce' ),
418
					'MMK' => __( 'Burmese kyat', 'woocommerce' ),
419
					'MNT' => __( 'Mongolian t&ouml;gr&ouml;g', 'woocommerce' ),
420
					'MOP' => __( 'Macanese pataca', 'woocommerce' ),
421
					'MRO' => __( 'Mauritanian ouguiya', 'woocommerce' ),
422
					'MUR' => __( 'Mauritian rupee', 'woocommerce' ),
423
					'MVR' => __( 'Maldivian rufiyaa', 'woocommerce' ),
424
					'MWK' => __( 'Malawian kwacha', 'woocommerce' ),
425
					'MXN' => __( 'Mexican peso', 'woocommerce' ),
426
					'MYR' => __( 'Malaysian ringgit', 'woocommerce' ),
427
					'MZN' => __( 'Mozambican metical', 'woocommerce' ),
428
					'NAD' => __( 'Namibian dollar', 'woocommerce' ),
429
					'NGN' => __( 'Nigerian naira', 'woocommerce' ),
430
					'NIO' => __( 'Nicaraguan c&oacute;rdoba', 'woocommerce' ),
431
					'NOK' => __( 'Norwegian krone', 'woocommerce' ),
432
					'NPR' => __( 'Nepalese rupee', 'woocommerce' ),
433
					'NZD' => __( 'New Zealand dollar', 'woocommerce' ),
434
					'OMR' => __( 'Omani rial', 'woocommerce' ),
435
					'PAB' => __( 'Panamanian balboa', 'woocommerce' ),
436
					'PEN' => __( 'Sol', 'woocommerce' ),
437
					'PGK' => __( 'Papua New Guinean kina', 'woocommerce' ),
438
					'PHP' => __( 'Philippine peso', 'woocommerce' ),
439
					'PKR' => __( 'Pakistani rupee', 'woocommerce' ),
440
					'PLN' => __( 'Polish z&#x142;oty', 'woocommerce' ),
441
					'PRB' => __( 'Transnistrian ruble', 'woocommerce' ),
442
					'PYG' => __( 'Paraguayan guaran&iacute;', 'woocommerce' ),
443
					'QAR' => __( 'Qatari riyal', 'woocommerce' ),
444
					'RON' => __( 'Romanian leu', 'woocommerce' ),
445
					'RSD' => __( 'Serbian dinar', 'woocommerce' ),
446
					'RUB' => __( 'Russian ruble', 'woocommerce' ),
447
					'RWF' => __( 'Rwandan franc', 'woocommerce' ),
448
					'SAR' => __( 'Saudi riyal', 'woocommerce' ),
449
					'SBD' => __( 'Solomon Islands dollar', 'woocommerce' ),
450
					'SCR' => __( 'Seychellois rupee', 'woocommerce' ),
451
					'SDG' => __( 'Sudanese pound', 'woocommerce' ),
452
					'SEK' => __( 'Swedish krona', 'woocommerce' ),
453
					'SGD' => __( 'Singapore dollar', 'woocommerce' ),
454
					'SHP' => __( 'Saint Helena pound', 'woocommerce' ),
455
					'SLL' => __( 'Sierra Leonean leone', 'woocommerce' ),
456
					'SOS' => __( 'Somali shilling', 'woocommerce' ),
457
					'SRD' => __( 'Surinamese dollar', 'woocommerce' ),
458
					'SSP' => __( 'South Sudanese pound', 'woocommerce' ),
459
					'STD' => __( 'S&atilde;o Tom&eacute; and Pr&iacute;ncipe dobra', 'woocommerce' ),
460 539
					'SYP' => __( 'Syrian pound', 'woocommerce' ),
461
					'SZL' => __( 'Swazi lilangeni', 'woocommerce' ),
462
					'THB' => __( 'Thai baht', 'woocommerce' ),
463
					'TJS' => __( 'Tajikistani somoni', 'woocommerce' ),
464
					'TMT' => __( 'Turkmenistan manat', 'woocommerce' ),
465
					'TND' => __( 'Tunisian dinar', 'woocommerce' ),
466
					'TOP' => __( 'Tongan pa&#x2bb;anga', 'woocommerce' ),
467
					'TRY' => __( 'Turkish lira', 'woocommerce' ),
468
					'TTD' => __( 'Trinidad and Tobago dollar', 'woocommerce' ),
469
					'TWD' => __( 'New Taiwan dollar', 'woocommerce' ),
470
					'TZS' => __( 'Tanzanian shilling', 'woocommerce' ),
471 63
					'UAH' => __( 'Ukrainian hryvnia', 'woocommerce' ),
472 48
					'UGX' => __( 'Ugandan shilling', 'woocommerce' ),
473
					'USD' => __( 'United States (US) dollar', 'woocommerce' ),
474
					'UYU' => __( 'Uruguayan peso', 'woocommerce' ),
475 63
					'UZS' => __( 'Uzbekistani som', 'woocommerce' ),
476 63
					'VEF' => __( 'Venezuelan bol&iacute;var', 'woocommerce' ),
477
					'VES' => __( 'Bol&iacute;var soberano', 'woocommerce' ),
478 63
					'VND' => __( 'Vietnamese &#x111;&#x1ed3;ng', 'woocommerce' ),
479
					'VUV' => __( 'Vanuatu vatu', 'woocommerce' ),
480
					'WST' => __( 'Samoan t&#x101;l&#x101;', 'woocommerce' ),
481
					'XAF' => __( 'Central African CFA franc', 'woocommerce' ),
482
					'XCD' => __( 'East Caribbean dollar', 'woocommerce' ),
483
					'XOF' => __( 'West African CFA franc', 'woocommerce' ),
484
					'XPF' => __( 'CFP franc', 'woocommerce' ),
485
					'YER' => __( 'Yemeni rial', 'woocommerce' ),
486
					'ZAR' => __( 'South African rand', 'woocommerce' ),
487
					'ZMW' => __( 'Zambian kwacha', 'woocommerce' ),
488
				)
489
			)
490
		);
491
	}
492
493
	return $currencies;
494
}
495
496
497
/**
498
 * Get Currency symbol.
499
 *
500
 * @param string $currency Currency. (default: '').
501
 * @return string
502
 */
503
function get_woocommerce_currency_symbol( $currency = '' ) {
504
	if ( ! $currency ) {
505
		$currency = get_woocommerce_currency();
506
	}
507
508
	$symbols         = apply_filters(
509
		'woocommerce_currency_symbols',
510
		array(
511
			'AED' => '&#x62f;.&#x625;',
512
			'AFN' => '&#x60b;',
513
			'ALL' => 'L',
514
			'AMD' => 'AMD',
515
			'ANG' => '&fnof;',
516
			'AOA' => 'Kz',
517
			'ARS' => '&#36;',
518
			'AUD' => '&#36;',
519
			'AWG' => 'Afl.',
520
			'AZN' => 'AZN',
521
			'BAM' => 'KM',
522
			'BBD' => '&#36;',
523
			'BDT' => '&#2547;&nbsp;',
524
			'BGN' => '&#1083;&#1074;.',
525
			'BHD' => '.&#x62f;.&#x628;',
526
			'BIF' => 'Fr',
527
			'BMD' => '&#36;',
528
			'BND' => '&#36;',
529
			'BOB' => 'Bs.',
530
			'BRL' => '&#82;&#36;',
531
			'BSD' => '&#36;',
532
			'BTC' => '&#3647;',
533
			'BTN' => 'Nu.',
534
			'BWP' => 'P',
535
			'BYR' => 'Br',
536
			'BYN' => 'Br',
537
			'BZD' => '&#36;',
538
			'CAD' => '&#36;',
539
			'CDF' => 'Fr',
540
			'CHF' => '&#67;&#72;&#70;',
541
			'CLP' => '&#36;',
542
			'CNY' => '&yen;',
543
			'COP' => '&#36;',
544
			'CRC' => '&#x20a1;',
545
			'CUC' => '&#36;',
546
			'CUP' => '&#36;',
547
			'CVE' => '&#36;',
548
			'CZK' => '&#75;&#269;',
549
			'DJF' => 'Fr',
550
			'DKK' => 'DKK',
551
			'DOP' => 'RD&#36;',
552
			'DZD' => '&#x62f;.&#x62c;',
553
			'EGP' => 'EGP',
554
			'ERN' => 'Nfk',
555
			'ETB' => 'Br',
556
			'EUR' => '&euro;',
557
			'FJD' => '&#36;',
558
			'FKP' => '&pound;',
559
			'GBP' => '&pound;',
560
			'GEL' => '&#x20be;',
561
			'GGP' => '&pound;',
562
			'GHS' => '&#x20b5;',
563
			'GIP' => '&pound;',
564
			'GMD' => 'D',
565
			'GNF' => 'Fr',
566
			'GTQ' => 'Q',
567
			'GYD' => '&#36;',
568
			'HKD' => '&#36;',
569
			'HNL' => 'L',
570
			'HRK' => 'kn',
571
			'HTG' => 'G',
572
			'HUF' => '&#70;&#116;',
573
			'IDR' => 'Rp',
574
			'ILS' => '&#8362;',
575
			'IMP' => '&pound;',
576
			'INR' => '&#8377;',
577
			'IQD' => '&#x639;.&#x62f;',
578
			'IRR' => '&#xfdfc;',
579
			'IRT' => '&#x062A;&#x0648;&#x0645;&#x0627;&#x0646;',
580
			'ISK' => 'kr.',
581
			'JEP' => '&pound;',
582
			'JMD' => '&#36;',
583
			'JOD' => '&#x62f;.&#x627;',
584
			'JPY' => '&yen;',
585
			'KES' => 'KSh',
586
			'KGS' => '&#x441;&#x43e;&#x43c;',
587
			'KHR' => '&#x17db;',
588
			'KMF' => 'Fr',
589
			'KPW' => '&#x20a9;',
590
			'KRW' => '&#8361;',
591
			'KWD' => '&#x62f;.&#x643;',
592
			'KYD' => '&#36;',
593
			'KZT' => 'KZT',
594
			'LAK' => '&#8365;',
595
			'LBP' => '&#x644;.&#x644;',
596
			'LKR' => '&#xdbb;&#xdd4;',
597
			'LRD' => '&#36;',
598
			'LSL' => 'L',
599
			'LYD' => '&#x644;.&#x62f;',
600
			'MAD' => '&#x62f;.&#x645;.',
601
			'MDL' => 'MDL',
602
			'MGA' => 'Ar',
603
			'MKD' => '&#x434;&#x435;&#x43d;',
604
			'MMK' => 'Ks',
605
			'MNT' => '&#x20ae;',
606
			'MOP' => 'P',
607
			'MRO' => 'UM',
608
			'MUR' => '&#x20a8;',
609
			'MVR' => '.&#x783;',
610
			'MWK' => 'MK',
611
			'MXN' => '&#36;',
612
			'MYR' => '&#82;&#77;',
613
			'MZN' => 'MT',
614
			'NAD' => '&#36;',
615
			'NGN' => '&#8358;',
616
			'NIO' => 'C&#36;',
617
			'NOK' => '&#107;&#114;',
618
			'NPR' => '&#8360;',
619
			'NZD' => '&#36;',
620
			'OMR' => '&#x631;.&#x639;.',
621
			'PAB' => 'B/.',
622
			'PEN' => 'S/',
623
			'PGK' => 'K',
624
			'PHP' => '&#8369;',
625
			'PKR' => '&#8360;',
626
			'PLN' => '&#122;&#322;',
627
			'PRB' => '&#x440;.',
628
			'PYG' => '&#8370;',
629
			'QAR' => '&#x631;.&#x642;',
630
			'RMB' => '&yen;',
631
			'RON' => 'lei',
632
			'RSD' => '&#x434;&#x438;&#x43d;.',
633
			'RUB' => '&#8381;',
634
			'RWF' => 'Fr',
635
			'SAR' => '&#x631;.&#x633;',
636
			'SBD' => '&#36;',
637
			'SCR' => '&#x20a8;',
638
			'SDG' => '&#x62c;.&#x633;.',
639
			'SEK' => '&#107;&#114;',
640
			'SGD' => '&#36;',
641
			'SHP' => '&pound;',
642
			'SLL' => 'Le',
643 63
			'SOS' => 'Sh',
644
			'SRD' => '&#36;',
645 63
			'SSP' => '&pound;',
646
			'STD' => 'Db',
647
			'SYP' => '&#x644;.&#x633;',
648
			'SZL' => 'L',
649
			'THB' => '&#3647;',
650
			'TJS' => '&#x405;&#x41c;',
651
			'TMT' => 'm',
652
			'TND' => '&#x62f;.&#x62a;',
653
			'TOP' => 'T&#36;',
654
			'TRY' => '&#8378;',
655
			'TTD' => '&#36;',
656
			'TWD' => '&#78;&#84;&#36;',
657
			'TZS' => 'Sh',
658
			'UAH' => '&#8372;',
659
			'UGX' => 'UGX',
660
			'USD' => '&#36;',
661
			'UYU' => '&#36;',
662
			'UZS' => 'UZS',
663
			'VEF' => 'Bs F',
664
			'VES' => 'Bs.S',
665
			'VND' => '&#8363;',
666
			'VUV' => 'Vt',
667
			'WST' => 'T',
668
			'XAF' => 'CFA',
669
			'XCD' => '&#36;',
670
			'XOF' => 'CFA',
671
			'XPF' => 'Fr',
672 21
			'YER' => '&#xfdfc;',
673 21
			'ZAR' => '&#82;',
674
			'ZMW' => 'ZK',
675 21
		)
676 19
	);
677
	$currency_symbol = isset( $symbols[ $currency ] ) ? $symbols[ $currency ] : '';
678
679 3
	return apply_filters( 'woocommerce_currency_symbol', $currency_symbol, $currency );
680 3
}
681 3
682
/**
683 3
 * Send HTML emails from WooCommerce.
684 3
 *
685
 * @param mixed  $to          Receiver.
686 3
 * @param mixed  $subject     Subject.
687
 * @param mixed  $message     Message.
688
 * @param string $headers     Headers. (default: "Content-Type: text/html\r\n").
689
 * @param string $attachments Attachments. (default: "").
690
 */
691
function wc_mail( $to, $subject, $message, $headers = "Content-Type: text/html\r\n", $attachments = '' ) {
692 3
	$mailer = WC()->mailer();
693
694
	$mailer->send( $to, $subject, $message, $headers, $attachments );
695
}
696
697 1
/**
698
 * Return "theme support" values from the current theme, if set.
699
 *
700 3
 * @since  3.3.0
701
 * @param  string $prop Name of prop (or key::subkey for arrays of props) if you want a specific value. Leave blank to get all props as an array.
702
 * @param  mixed  $default Optional value to return if the theme does not declare support for a prop.
703 1
 * @return mixed  Value of prop(s).
704
 */
705
function wc_get_theme_support( $prop = '', $default = null ) {
706
	$theme_support = get_theme_support( 'woocommerce' );
707
	$theme_support = is_array( $theme_support ) ? $theme_support[0] : false;
708
709
	if ( ! $theme_support ) {
710
		return $default;
711
	}
712
713
	if ( $prop ) {
714
		$prop_stack = explode( '::', $prop );
715
		$prop_key   = array_shift( $prop_stack );
716
717
		if ( isset( $theme_support[ $prop_key ] ) ) {
718
			$value = $theme_support[ $prop_key ];
719
720 14
			if ( count( $prop_stack ) ) {
721
				foreach ( $prop_stack as $prop_key ) {
722
					if ( is_array( $value ) && isset( $value[ $prop_key ] ) ) {
723
						$value = $value[ $prop_key ];
724
					} else {
725 14
						$value = $default;
726
						break;
727 1
					}
728 1
				}
729 1
			}
730
		} else {
731 1
			$value = $default;
732
		}
733 14
734
		return $value;
735
	}
736 14
737 1
	return $theme_support;
738 14
}
739
740 14
/**
741 1
 * Get an image size by name or defined dimensions.
742
 *
743
 * The returned variable is filtered by woocommerce_get_image_size_{image_size} filter to
744 14
 * allow 3rd party customisation.
745 1
 *
746 1
 * Sizes defined by the theme take priority over settings. Settings are hidden when a theme
747 1
 * defines sizes.
748
 *
749 14
 * @param array|string $image_size Name of the image size to get, or an array of dimensions.
750 1
 * @return array Array of dimensions including width, height, and cropping mode. Cropping mode is 0 for no crop, and 1 for hard crop.
751 1
 */
752 1
function wc_get_image_size( $image_size ) {
753
	$size = array(
754 13
		'width'  => 600,
755 13
		'height' => 600,
756 13
		'crop'   => 1,
757
	);
758 13
759
	if ( is_array( $image_size ) ) {
760
		$size       = array(
761 13
			'width'  => isset( $image_size[0] ) ? absint( $image_size[0] ) : 600,
762
			'height' => isset( $image_size[1] ) ? absint( $image_size[1] ) : 600,
763
			'crop'   => isset( $image_size[2] ) ? absint( $image_size[2] ) : 1,
764
		);
765
		$image_size = $size['width'] . '_' . $size['height'];
766
	} else {
767 13
		$image_size = str_replace( 'woocommerce_', '', $image_size );
768 13
769 13
		// Legacy size mapping.
770 13
		if ( 'shop_single' === $image_size ) {
771 13
			$image_size = 'single';
772
		} elseif ( 'shop_catalog' === $image_size ) {
773
			$image_size = 'thumbnail';
774
		} elseif ( 'shop_thumbnail' === $image_size ) {
775
			$image_size = 'gallery_thumbnail';
776 14
		}
777
778
		if ( 'single' === $image_size ) {
779
			$size['width']  = absint( wc_get_theme_support( 'single_image_width', get_option( 'woocommerce_single_image_width', 600 ) ) );
780
			$size['height'] = '';
781
			$size['crop']   = 0;
782
783
		} elseif ( 'gallery_thumbnail' === $image_size ) {
784
			$size['width']  = absint( wc_get_theme_support( 'gallery_thumbnail_image_width', 100 ) );
785
			$size['height'] = $size['width'];
786
			$size['crop']   = 1;
787 1
788 1
		} elseif ( 'thumbnail' === $image_size ) {
789
			$size['width'] = absint( wc_get_theme_support( 'thumbnail_image_width', get_option( 'woocommerce_thumbnail_image_width', 300 ) ) );
790
			$cropping      = get_option( 'woocommerce_thumbnail_cropping', '1:1' );
791 1
792
			if ( 'uncropped' === $cropping ) {
793
				$size['height'] = '';
794
				$size['crop']   = 0;
795
			} elseif ( 'custom' === $cropping ) {
796
				$width          = max( 1, get_option( 'woocommerce_thumbnail_cropping_custom_width', '4' ) );
797
				$height         = max( 1, get_option( 'woocommerce_thumbnail_cropping_custom_height', '3' ) );
798
				$size['height'] = absint( round( ( $size['width'] / $width ) * $height ) );
799
				$size['crop']   = 1;
800 1
			} else {
801
				$cropping_split = explode( ':', $cropping );
802 1
				$width          = max( 1, current( $cropping_split ) );
803 1
				$height         = max( 1, end( $cropping_split ) );
804 1
				$size['height'] = absint( round( ( $size['width'] / $width ) * $height ) );
805
				$size['crop']   = 1;
806 1
			}
807
		}
808
	}
809
810
	return apply_filters( 'woocommerce_get_image_size_' . $image_size, $size );
811
}
812
813
/**
814 1
 * Queue some JavaScript code to be output in the footer.
815
 *
816 1
 * @param string $code Code.
817
 */
818
function wc_enqueue_js( $code ) {
819
	global $wc_queued_js;
820
821
	if ( empty( $wc_queued_js ) ) {
822
		$wc_queued_js = '';
823
	}
824
825
	$wc_queued_js .= "\n" . $code . "\n";
826
}
827
828
/**
829
 * Output any queued javascript code in the footer.
830
 */
831
function wc_print_js() {
832
	global $wc_queued_js;
833
834
	if ( ! empty( $wc_queued_js ) ) {
835
		// Sanitize.
836
		$wc_queued_js = wp_check_invalid_utf8( $wc_queued_js );
837
		$wc_queued_js = preg_replace( '/&#(x)?0*(?(1)27|39);?/i', "'", $wc_queued_js );
838
		$wc_queued_js = str_replace( "\r", '', $wc_queued_js );
839
840
		$js = "<!-- WooCommerce JavaScript -->\n<script type=\"text/javascript\">\njQuery(function($) { $wc_queued_js });\n</script>\n";
841
842
		/**
843
		 * Queued jsfilter.
844
		 *
845 1
		 * @since 2.6.0
846
		 * @param string $js JavaScript code.
847 1
		 */
848
		echo apply_filters( 'woocommerce_queued_js', $js ); // WPCS: XSS ok.
849 1
850 1
		unset( $wc_queued_js );
851
	}
852
}
853 1
854
/**
855
 * Set a cookie - wrapper for setcookie using WP constants.
856
 *
857
 * @param  string  $name   Name of the cookie being set.
858
 * @param  string  $value  Value of the cookie.
859
 * @param  integer $expire Expiry of the cookie.
860
 * @param  bool    $secure Whether the cookie should be served only over https.
861
 */
862
function wc_setcookie( $name, $value, $expire = 0, $secure = false ) {
863
	if ( ! headers_sent() ) {
864
		setcookie( $name, $value, $expire, COOKIEPATH ? COOKIEPATH : '/', COOKIE_DOMAIN, $secure, apply_filters( 'woocommerce_cookie_httponly', false, $name, $value, $expire, $secure ) );
0 ignored issues
show
introduced by
Due to using Batcache, server side based client related logic will not work, use JS instead.
Loading history...
865 2
	} elseif ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
866
		headers_sent( $file, $line );
867
		trigger_error( "{$name} cookie cannot be set - headers already sent by {$file} on line {$line}", E_USER_NOTICE ); // @codingStandardsIgnoreLine
868
	}
869
}
870
871
/**
872
 * Get the URL to the WooCommerce REST API.
873
 *
874
 * @since 2.1
875
 * @param string $path an endpoint to include in the URL.
876
 * @return string the URL.
877 2
 */
878
function get_woocommerce_api_url( $path ) {
879
	$version = defined( 'WC_API_REQUEST_VERSION' ) ? WC_API_REQUEST_VERSION : substr( WC_API::VERSION, 0, 1 );
880
881
	$url = get_home_url( null, "wc-api/v{$version}/", is_ssl() ? 'https' : 'http' );
882
883
	if ( ! empty( $path ) && is_string( $path ) ) {
884
		$url .= ltrim( $path, '/' );
885
	}
886
887 1
	return $url;
888
}
889 1
890 1
/**
891
 * Get a log file path.
892 1
 *
893 1
 * @since 2.2
894
 *
895
 * @param string $handle name.
896
 * @return string the log file path.
897 1
 */
898 1
function wc_get_log_file_path( $handle ) {
899 1
	return WC_Log_Handler_File::get_log_file_path( $handle );
900
}
901
902
/**
903 1
 * Get a log file name.
904
 *
905
 * @since 3.3
906
 *
907
 * @param string $handle Name.
908
 * @return string The log file name.
909
 */
910
function wc_get_log_file_name( $handle ) {
911
	return WC_Log_Handler_File::get_log_file_name( $handle );
912
}
913
914
/**
915
 * Recursively get page children.
916
 *
917
 * @param  int $page_id Page ID.
918
 * @return int[]
919
 */
920
function wc_get_page_children( $page_id ) {
921
	$page_ids = get_posts(
922
		array(
923
			'post_parent' => $page_id,
924
			'post_type'   => 'page',
925
			'numberposts' => -1, // @codingStandardsIgnoreLine
926
			'post_status' => 'any',
927
			'fields'      => 'ids',
928
		)
929
	);
930
931
	if ( ! empty( $page_ids ) ) {
932
		foreach ( $page_ids as $page_id ) {
933
			$page_ids = array_merge( $page_ids, wc_get_page_children( $page_id ) );
934
		}
935
	}
936
937
	return $page_ids;
938
}
939
940
/**
941
 * Flushes rewrite rules when the shop page (or it's children) gets saved.
942
 */
943
function flush_rewrite_rules_on_shop_page_save() {
944
	$screen    = get_current_screen();
945
	$screen_id = $screen ? $screen->id : '';
946
947
	// Check if this is the edit page.
948
	if ( 'page' !== $screen_id ) {
949
		return;
950
	}
951
952
	// Check if page is edited.
953
	if ( empty( $_GET['post'] ) || empty( $_GET['action'] ) || ( isset( $_GET['action'] ) && 'edit' !== $_GET['action'] ) ) { // WPCS: input var ok, CSRF ok.
0 ignored issues
show
introduced by
Detected usage of a non-sanitized input variable: $_GET
Loading history...
954
		return;
955
	}
956
957
	$post_id      = intval( $_GET['post'] ); // WPCS: input var ok, CSRF ok.
958
	$shop_page_id = wc_get_page_id( 'shop' );
959
960
	if ( $shop_page_id === $post_id || in_array( $post_id, wc_get_page_children( $shop_page_id ), true ) ) {
961
		do_action( 'woocommerce_flush_rewrite_rules' );
962
	}
963
}
964
add_action( 'admin_footer', 'flush_rewrite_rules_on_shop_page_save' );
965
966
/**
967
 * Various rewrite rule fixes.
968
 *
969
 * @since 2.2
970
 * @param array $rules Rules.
971
 * @return array
972
 */
973
function wc_fix_rewrite_rules( $rules ) {
974
	global $wp_rewrite;
975
976
	$permalinks = wc_get_permalink_structure();
977
978
	// Fix the rewrite rules when the product permalink have %product_cat% flag.
979
	if ( preg_match( '`/(.+)(/%product_cat%)`', $permalinks['product_rewrite_slug'], $matches ) ) {
980
		foreach ( $rules as $rule => $rewrite ) {
981
			if ( preg_match( '`^' . preg_quote( $matches[1], '`' ) . '/\(`', $rule ) && preg_match( '/^(index\.php\?product_cat)(?!(.*product))/', $rewrite ) ) {
982
				unset( $rules[ $rule ] );
983
			}
984
		}
985
	}
986
987
	// If the shop page is used as the base, we need to handle shop page subpages to avoid 404s.
988
	if ( ! $permalinks['use_verbose_page_rules'] ) {
989
		return $rules;
990 1
	}
991 1
992
	$shop_page_id = wc_get_page_id( 'shop' );
993
	if ( $shop_page_id ) {
994 1
		$page_rewrite_rules = array();
995
		$subpages           = wc_get_page_children( $shop_page_id );
996
997
		// Subpage rules.
998
		foreach ( $subpages as $subpage ) {
999
			$uri                                = get_page_uri( $subpage );
1000
			$page_rewrite_rules[ $uri . '/?$' ] = 'index.php?pagename=' . $uri;
1001
			$wp_generated_rewrite_rules         = $wp_rewrite->generate_rewrite_rules( $uri, EP_PAGES, true, true, false, false );
1002
			foreach ( $wp_generated_rewrite_rules as $key => $value ) {
1003
				$wp_generated_rewrite_rules[ $key ] = $value . '&pagename=' . $uri;
1004
			}
1005
			$page_rewrite_rules = array_merge( $page_rewrite_rules, $wp_generated_rewrite_rules );
1006
		}
1007
1008
		// Merge with rules.
1009
		$rules = array_merge( $page_rewrite_rules, $rules );
1010
	}
1011
1012
	return $rules;
1013
}
1014
add_filter( 'rewrite_rules_array', 'wc_fix_rewrite_rules' );
1015
1016
/**
1017
 * Prevent product attachment links from breaking when using complex rewrite structures.
1018
 *
1019
 * @param  string $link    Link.
1020
 * @param  int    $post_id Post ID.
1021
 * @return string
1022
 */
1023
function wc_fix_product_attachment_link( $link, $post_id ) {
1024
	$parent_type = get_post_type( wp_get_post_parent_id( $post_id ) );
1025
	if ( 'product' === $parent_type || 'product_variation' === $parent_type ) {
1026
		$link = home_url( '/?attachment_id=' . $post_id );
1027
	}
1028 456
	return $link;
1029 1
}
1030
add_filter( 'attachment_link', 'wc_fix_product_attachment_link', 10, 2 );
1031 456
1032 456
/**
1033
 * Protect downloads from ms-files.php in multisite.
1034
 *
1035 456
 * @param string $rewrite rewrite rules.
1036 456
 * @return string
1037
 */
1038
function wc_ms_protect_download_rewite_rules( $rewrite ) {
1039
	if ( ! is_multisite() || 'redirect' === get_option( 'woocommerce_file_download_method' ) ) {
1040
		return $rewrite;
1041
	}
1042
1043
	$rule  = "\n# WooCommerce Rules - Protect Files from ms-files.php\n\n";
1044
	$rule .= "<IfModule mod_rewrite.c>\n";
1045
	$rule .= "RewriteEngine On\n";
1046
	$rule .= "RewriteCond %{QUERY_STRING} file=woocommerce_uploads/ [NC]\n";
1047 451
	$rule .= "RewriteRule /ms-files.php$ - [F]\n";
1048
	$rule .= "</IfModule>\n\n";
1049 451
1050
	return $rule . $rewrite;
1051
}
1052
add_filter( 'mod_rewrite_rules', 'wc_ms_protect_download_rewite_rules' );
1053
1054
/**
1055
 * Formats a string in the format COUNTRY:STATE into an array.
1056
 *
1057
 * @since 2.3.0
1058
 * @param  string $country_string Country string.
1059
 * @return array
1060
 */
1061
function wc_format_country_state_string( $country_string ) {
1062 7
	if ( strstr( $country_string, ':' ) ) {
1063
		list( $country, $state ) = explode( ':', $country_string );
1064 7
	} else {
1065 7
		$country = $country_string;
1066 7
		$state   = '';
1067
	}
1068 7
	return array(
1069
		'country' => $country,
1070 7
		'state'   => $state,
1071 7
	);
1072
}
1073
1074
/**
1075 7
 * Get the store's base location.
1076 7
 *
1077
 * @since 2.3.0
1078 7
 * @return array
1079
 */
1080
function wc_get_base_location() {
1081
	$default = apply_filters( 'woocommerce_get_base_location', get_option( 'woocommerce_default_country' ) );
1082
1083
	return wc_format_country_state_string( $default );
1084
}
1085
1086
/**
1087
 * Get the customer's default location.
1088 7
 *
1089
 * Filtered, and set to base location or left blank. If cache-busting,
1090
 * this should only be used when 'location' is set in the querystring.
1091
 *
1092
 * @since 2.3.0
1093
 * @return array
1094
 */
1095
function wc_get_customer_default_location() {
1096
	$location = array();
1097
1098 105
	switch ( get_option( 'woocommerce_default_customer_address' ) ) {
1099
		case 'geolocation_ajax':
1100
		case 'geolocation':
1101
			// Exclude common bots from geolocation by user agent.
1102
			$ua = strtolower( wc_get_user_agent() );
1103
1104
			if ( ! strstr( $ua, 'bot' ) && ! strstr( $ua, 'spider' ) && ! strstr( $ua, 'crawl' ) ) {
1105
				$location = WC_Geolocation::geolocate_ip( '', false, false );
1106
			}
1107
1108
			// Base fallback.
1109
			if ( empty( $location['country'] ) ) {
1110
				$location = wc_format_country_state_string( apply_filters( 'woocommerce_customer_default_location', get_option( 'woocommerce_default_country' ) ) );
1111
			}
1112
			break;
1113
		case 'base':
1114
			$location = wc_format_country_state_string( apply_filters( 'woocommerce_customer_default_location', get_option( 'woocommerce_default_country' ) ) );
1115
			break;
1116
		default:
1117
			$countries = WC()->countries->get_allowed_countries();
1118
			$location  = wc_format_country_state_string( apply_filters( 'woocommerce_customer_default_location', 1 === count( $countries ) ? key( $countries ) : '' ) );
1119
			break;
1120
	}
1121
1122
	return apply_filters( 'woocommerce_customer_default_location_array', $location );
1123
}
1124
1125
/**
1126
 * Get user agent string.
1127
 *
1128
 * @since  3.0.0
1129
 * @return string
1130
 */
1131
function wc_get_user_agent() {
1132
	return isset( $_SERVER['HTTP_USER_AGENT'] ) ? wc_clean( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : ''; // @codingStandardsIgnoreLine
1133
}
1134
1135
// This function can be removed when WP 3.9.2 or greater is required.
1136
if ( ! function_exists( 'hash_equals' ) ) :
1137
	/**
1138 1
	 * Compare two strings in constant time.
1139
	 *
1140
	 * This function was added in PHP 5.6.
1141
	 * It can leak the length of a string.
1142 1
	 *
1143
	 * @since 3.9.2
1144
	 *
1145
	 * @param string $a Expected string.
1146
	 * @param string $b Actual string.
1147
	 * @return bool Whether strings are equal.
1148
	 */
1149
	function hash_equals( $a, $b ) {
1150
		$a_length = strlen( $a );
1151
		if ( strlen( $b ) !== $a_length ) {
1152
			return false;
1153
		}
1154
		$result = 0;
1155
1156
		// Do not attempt to "optimize" this.
1157
		for ( $i = 0; $i < $a_length; $i++ ) {
1158
			$result |= ord( $a[ $i ] ) ^ ord( $b[ $i ] );
1159
		}
1160
1161
		return 0 === $result;
1162
	}
1163
endif;
1164 1
1165 1
/**
1166 1
 * Generate a rand hash.
1167 1
 *
1168
 * @since  2.4.0
1169
 * @return string
1170 1
 */
1171 1
function wc_rand_hash() {
1172 1
	if ( ! function_exists( 'openssl_random_pseudo_bytes' ) ) {
1173
		return sha1( wp_rand() );
1174
	}
1175
1176
	return bin2hex( openssl_random_pseudo_bytes( 20 ) ); // @codingStandardsIgnoreLine
1177 1
}
1178
1179 1
/**
1180 1
 * WC API - Hash.
1181 1
 *
1182
 * @since  2.4.0
1183
 * @param  string $data Message to be hashed.
1184
 * @return string
1185 1
 */
1186 1
function wc_api_hash( $data ) {
1187
	return hash_hmac( 'sha256', $data, 'wc-api' );
1188 1
}
1189 1
1190
/**
1191
 * Find all possible combinations of values from the input array and return in a logical order.
1192 1
 *
1193 1
 * @since 2.5.0
1194 1
 * @param array $input Input.
1195
 * @return array
1196
 */
1197
function wc_array_cartesian( $input ) {
1198
	$input   = array_filter( $input );
1199
	$results = array();
1200
	$indexes = array();
1201
	$index   = 0;
1202 1
1203
	// Generate indexes from keys and values so we have a logical sort order.
1204
	foreach ( $input as $key => $values ) {
1205 1
		foreach ( $values as $value ) {
1206 1
			$indexes[ $key ][ $value ] = $index++;
1207
		}
1208
	}
1209 1
1210
	// Loop over the 2D array of indexes and generate all combinations.
1211
	foreach ( $indexes as $key => $values ) {
1212 1
		// When result is empty, fill with the values of the first looped array.
1213 1
		if ( empty( $results ) ) {
1214
			foreach ( $values as $value ) {
1215
				$results[] = array( $key => $value );
1216 1
			}
1217
		} else {
1218
			// Second and subsequent input sub-array merging.
1219 1
			foreach ( $results as $result_key => $result ) {
1220
				foreach ( $values as $value ) {
1221
					// If the key is not set, we can set it.
1222
					if ( ! isset( $results[ $result_key ][ $key ] ) ) {
1223
						$results[ $result_key ][ $key ] = $value;
1224
					} else {
1225
						// If the key is set, we can add a new combination to the results array.
1226
						$new_combination         = $results[ $result_key ];
1227
						$new_combination[ $key ] = $value;
1228
						$results[]               = $new_combination;
1229
					}
1230
				}
1231
			}
1232 1
		}
1233
	}
1234 1
1235
	// Sort the indexes.
1236 1
	arsort( $results );
1237
1238 1
	// Convert indexes back to values.
1239 1
	foreach ( $results as $result_key => $result ) {
1240 1
		$converted_values = array();
1241 1
1242 1
		// Sort the values.
1243 1
		arsort( $results[ $result_key ] );
1244
1245 1
		// Convert the values.
1246 1
		foreach ( $results[ $result_key ] as $key => $value ) {
1247
			$converted_values[ $key ] = array_search( $value, $indexes[ $key ], true );
1248
		}
1249
1250
		$results[ $result_key ] = $converted_values;
1251
	}
1252
1253
	return $results;
1254
}
1255
1256
/**
1257
 * Run a MySQL transaction query, if supported.
1258
 *
1259 1
 * @since 2.5.0
1260
 * @param string $type Types: start (default), commit, rollback.
1261
 * @param bool   $force use of transactions.
1262
 */
1263
function wc_transaction_query( $type = 'start', $force = false ) {
1264
	global $wpdb;
1265
1266
	$wpdb->hide_errors();
1267
1268
	wc_maybe_define_constant( 'WC_USE_TRANSACTIONS', true );
1269
1270 2
	if ( WC_USE_TRANSACTIONS || $force ) {
1271 2
		switch ( $type ) {
1272
			case 'commit':
1273 2
				$wpdb->query( 'COMMIT' );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
1274 1
				break;
1275
			case 'rollback':
1276
				$wpdb->query( 'ROLLBACK' );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
1277
				break;
1278 2
			default:
1279
				$wpdb->query( 'START TRANSACTION' );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
1280
				break;
1281
		}
1282
	}
1283
}
1284
1285
/**
1286
 * Gets the url to the cart page.
1287
 *
1288
 * @since  2.5.0
1289
 *
1290
 * @return string Url to cart page
1291
 */
1292
function wc_get_cart_url() {
1293
	return apply_filters( 'woocommerce_get_cart_url', wc_get_page_permalink( 'cart' ) );
1294
}
1295
1296
/**
1297
 * Gets the url to the checkout page.
1298
 *
1299
 * @since  2.5.0
1300
 *
1301
 * @return string Url to checkout page
1302
 */
1303
function wc_get_checkout_url() {
1304
	$checkout_url = wc_get_page_permalink( 'checkout' );
1305 View Code Duplication
	if ( $checkout_url ) {
1306
		// Force SSL if needed.
1307
		if ( is_ssl() || 'yes' === get_option( 'woocommerce_force_ssl_checkout' ) ) {
1308
			$checkout_url = str_replace( 'http:', 'https:', $checkout_url );
1309
		}
1310
	}
1311
1312
	return apply_filters( 'woocommerce_get_checkout_url', $checkout_url );
1313
}
1314 1
1315 1
/**
1316 1
 * Register a shipping method.
1317
 *
1318 1
 * @since 1.5.7
1319 1
 * @param string|object $shipping_method class name (string) or a class object.
1320
 */
1321 1
function woocommerce_register_shipping_method( $shipping_method ) {
1322 1
	WC()->shipping()->register_shipping_method( $shipping_method );
1323 1
}
1324 1
1325 1
if ( ! function_exists( 'wc_get_shipping_zone' ) ) {
1326 1
	/**
1327
	 * Get the shipping zone matching a given package from the cart.
1328
	 *
1329
	 * @since  2.6.0
1330 1
	 * @uses   WC_Shipping_Zones::get_zone_matching_package
1331
	 * @param  array $package Shipping package.
1332
	 * @return WC_Shipping_Zone
1333
	 */
1334
	function wc_get_shipping_zone( $package ) {
1335
		return WC_Shipping_Zones::get_zone_matching_package( $package );
1336
	}
1337
}
1338
1339
/**
1340
 * Get a nice name for credit card providers.
1341
 *
1342
 * @since  2.6.0
1343
 * @param  string $type Provider Slug/Type.
1344
 * @return string
1345
 */
1346
function wc_get_credit_card_type_label( $type ) {
1347
	// Normalize.
1348
	$type = strtolower( $type );
1349
	$type = str_replace( '-', ' ', $type );
1350
	$type = str_replace( '_', ' ', $type );
1351
1352
	$labels = apply_filters(
1353 10
		'woocommerce_credit_card_type_labels',
1354 10
		array(
1355
			'mastercard'       => __( 'MasterCard', 'woocommerce' ),
1356
			'visa'             => __( 'Visa', 'woocommerce' ),
1357
			'discover'         => __( 'Discover', 'woocommerce' ),
1358
			'american express' => __( 'American Express', 'woocommerce' ),
1359 10
			'diners'           => __( 'Diners', 'woocommerce' ),
1360
			'jcb'              => __( 'JCB', 'woocommerce' ),
1361
		)
1362
	);
1363
1364
	return apply_filters( 'woocommerce_get_credit_card_type_label', ( array_key_exists( $type, $labels ) ? $labels[ $type ] : ucfirst( $type ) ) );
1365
}
1366
1367
/**
1368
 * Outputs a "back" link so admin screens can easily jump back a page.
1369
 *
1370
 * @param string $label Title of the page to return to.
1371 129
 * @param string $url   URL of the page to return to.
1372 129
 */
1373
function wc_back_link( $label, $url ) {
1374 129
	echo '<small class="wc-admin-breadcrumb"><a href="' . esc_url( $url ) . '" aria-label="' . esc_attr( $label ) . '">&#x2934;</a></small>';
1375 129
}
1376 129
1377
/**
1378
 * Display a WooCommerce help tip.
1379 129
 *
1380 8
 * @since  2.5.0
1381
 *
1382
 * @param  string $tip        Help tip text.
1383 129
 * @param  bool   $allow_html Allow sanitized HTML if true or escape.
1384
 * @return string
1385
 */
1386
function wc_help_tip( $tip, $allow_html = false ) {
1387
	if ( $allow_html ) {
1388
		$tip = wc_sanitize_tooltip( $tip );
1389
	} else {
1390
		$tip = esc_attr( $tip );
1391
	}
1392
1393
	return '<span class="woocommerce-help-tip" data-tip="' . $tip . '"></span>';
1394
}
1395
1396
/**
1397
 * Return a list of potential postcodes for wildcard searching.
1398
 *
1399 1
 * @since 2.6.0
1400 1
 * @param  string $postcode Postcode.
1401 1
 * @param  string $country  Country to format postcode for matching.
1402
 * @return string[]
1403 1
 */
1404 1
function wc_get_wildcard_postcodes( $postcode, $country = '' ) {
1405 1
	$formatted_postcode = wc_format_postcode( $postcode, $country );
1406
	$length             = function_exists( 'mb_strlen' ) ? mb_strlen( $formatted_postcode ) : strlen( $formatted_postcode );
1407
	$postcodes          = array(
1408 1
		$postcode,
1409
		$formatted_postcode,
1410
		$formatted_postcode . '*',
1411
	);
1412
1413
	for ( $i = 0; $i < $length; $i ++ ) {
1414
		$postcodes[] = ( function_exists( 'mb_substr' ) ? mb_substr( $formatted_postcode, 0, ( $i + 1 ) * -1 ) : substr( $formatted_postcode, 0, ( $i + 1 ) * -1 ) ) . '*';
1415
	}
1416
1417
	return $postcodes;
1418
}
1419
1420
/**
1421
 * Used by shipping zones and taxes to compare a given $postcode to stored
1422
 * postcodes to find matches for numerical ranges, and wildcards.
1423
 *
1424
 * @since 2.6.0
1425
 * @param string $postcode           Postcode you want to match against stored postcodes.
1426
 * @param array  $objects            Array of postcode objects from Database.
1427
 * @param string $object_id_key      DB column name for the ID.
1428
 * @param string $object_compare_key DB column name for the value.
1429
 * @param string $country            Country from which this postcode belongs. Allows for formatting.
1430 1
 * @return array Array of matching object ID and matching values.
1431
 */
1432 1
function wc_postcode_location_matcher( $postcode, $objects, $object_id_key, $object_compare_key, $country = '' ) {
1433 1
	$postcode           = wc_normalize_postcode( $postcode );
1434
	$wildcard_postcodes = array_map( 'wc_clean', wc_get_wildcard_postcodes( $postcode, $country ) );
1435
	$matches            = array();
1436
1437 1
	foreach ( $objects as $object ) {
1438
		$object_id       = $object->$object_id_key;
1439
		$compare_against = $object->$object_compare_key;
1440
1441
		// Handle postcodes containing ranges.
1442
		if ( strstr( $compare_against, '...' ) ) {
1443
			$range = array_map( 'trim', explode( '...', $compare_against ) );
1444
1445
			if ( 2 !== count( $range ) ) {
1446
				continue;
1447
			}
1448
1449
			list( $min, $max ) = $range;
1450
1451 80
			// If the postcode is non-numeric, make it numeric.
1452 80
			if ( ! is_numeric( $min ) || ! is_numeric( $max ) ) {
1453
				$compare = wc_make_numeric_postcode( $postcode );
1454 80
				$min     = str_pad( wc_make_numeric_postcode( $min ), strlen( $compare ), '0' );
1455 80
				$max     = str_pad( wc_make_numeric_postcode( $max ), strlen( $compare ), '0' );
1456
			} else {
1457 80
				$compare = $postcode;
1458
			}
1459 80
1460
			if ( $compare >= $min && $compare <= $max ) {
1461 80
				$matches[ $object_id ]   = isset( $matches[ $object_id ] ) ? $matches[ $object_id ] : array();
1462 80
				$matches[ $object_id ][] = $compare_against;
1463 80
			}
1464
		} elseif ( in_array( $compare_against, $wildcard_postcodes, true ) ) {
1465
			// Wildcard and standard comparison.
1466
			$matches[ $object_id ]   = isset( $matches[ $object_id ] ) ? $matches[ $object_id ] : array();
1467
			$matches[ $object_id ][] = $compare_against;
1468 80
		}
1469
	}
1470
1471 80
	return $matches;
1472
}
1473
1474
/**
1475
 * Gets number of shipping methods currently enabled. Used to identify if
1476
 * shipping is configured.
1477
 *
1478
 * @since  2.6.0
1479
 * @param  bool $include_legacy Count legacy shipping methods too.
1480
 * @return int
1481 1
 */
1482 1
function wc_get_shipping_method_count( $include_legacy = false ) {
1483
	global $wpdb;
1484
1485
	$transient_name    = $include_legacy ? 'wc_shipping_method_count_legacy' : 'wc_shipping_method_count';
1486
	$transient_version = WC_Cache_Helper::get_transient_version( 'shipping' );
1487
	$transient_value   = get_transient( $transient_name );
1488
1489
	if ( isset( $transient_value['value'], $transient_value['version'] ) && $transient_value['version'] === $transient_version ) {
1490
		return absint( $transient_value['value'] );
1491
	}
1492
1493
	$method_count = absint( $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}woocommerce_shipping_zone_methods" ) );
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
1494
1495
	if ( $include_legacy ) {
1496
		// Count activated methods that don't support shipping zones.
1497
		$methods = WC()->shipping()->get_shipping_methods();
1498
1499
		foreach ( $methods as $method ) {
0 ignored issues
show
Bug introduced by
The expression $methods of type null|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1500
			if ( isset( $method->enabled ) && 'yes' === $method->enabled && ! $method->supports( 'shipping-zones' ) ) {
1501
				$method_count++;
1502
			}
1503
		}
1504
	}
1505 4
1506
	$transient_value = array(
1507
		'version' => $transient_version,
1508
		'value'   => $method_count,
1509
	);
1510
1511
	set_transient( $transient_name, $transient_value, DAY_IN_SECONDS * 30 );
1512
1513
	return $method_count;
1514
}
1515
1516
/**
1517 1
 * Wrapper for set_time_limit to see if it is enabled.
1518
 *
1519
 * @since 2.6.0
1520
 * @param int $limit Time limit.
1521
 */
1522
function wc_set_time_limit( $limit = 0 ) {
1523
	if ( function_exists( 'set_time_limit' ) && false === strpos( ini_get( 'disable_functions' ), 'set_time_limit' ) && ! ini_get( 'safe_mode' ) ) { // phpcs:ignore PHPCompatibility.IniDirectives.RemovedIniDirectives.safe_modeDeprecatedRemoved
1524
		@set_time_limit( $limit ); // @codingStandardsIgnoreLine
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1525
	}
1526
}
1527
1528
/**
1529
 * Wrapper for nocache_headers which also disables page caching.
1530
 *
1531
 * @since 3.2.4
1532
 */
1533
function wc_nocache_headers() {
1534 2
	WC_Cache_Helper::set_nocache_constants();
1535
	nocache_headers();
1536
}
1537
1538 2
/**
1539
 * Used to sort products attributes with uasort.
1540
 *
1541
 * @since 2.6.0
1542
 * @param array $a First attribute to compare.
1543
 * @param array $b Second attribute to compare.
1544
 * @return int
1545
 */
1546
function wc_product_attribute_uasort_comparison( $a, $b ) {
1547
	return wc_uasort_comparison( $a['position'], $b['position'] );
1548
}
1549
1550 7
/**
1551 3
 * Used to sort shipping zone methods with uasort.
1552
 *
1553 4
 * @since 3.0.0
1554
 * @param array $a First shipping zone method to compare.
1555
 * @param array $b Second shipping zone method to compare.
1556
 * @return int
1557
 */
1558
function wc_shipping_zone_method_order_uasort_comparison( $a, $b ) {
1559
	return wc_uasort_comparison( $a->method_order, $b->method_order );
1560
}
1561
1562
/**
1563 116
 * User to sort checkout fields based on priority with uasort.
1564
 *
1565 116
 * @since 3.5.1
1566 116
 * @param array $a First field to compare.
1567
 * @param array $b Second field to compare.
1568
 * @return int
1569
 */
1570
function wc_checkout_fields_uasort_comparison( $a, $b ) {
1571
	/*
1572
	 * We are not guaranteed to get a priority
1573
	 * setting. So don't compare if they don't
1574
	 * exist.
1575
	 */
1576
	if ( ! isset( $a['priority'], $b['priority'] ) ) {
1577
		return 0;
1578
	}
1579
1580 239
	return wc_uasort_comparison( $a['priority'], $b['priority'] );
1581 239
}
1582 239
1583
/**
1584 239
 * User to sort two values with ausort.
1585
 *
1586
 * @since 3.5.1
1587
 * @param int $a First value to compare.
1588
 * @param int $b Second value to compare.
1589
 * @return int
1590
 */
1591
function wc_uasort_comparison( $a, $b ) {
1592
	if ( $a === $b ) {
1593
		return 0;
1594
	}
1595
	return ( $a < $b ) ? -1 : 1;
1596 100
}
1597 100
1598 100
/**
1599
 * Sort values based on ascii, usefull for special chars in strings.
1600
 *
1601
 * @param string $a First value.
1602
 * @param string $b Second value.
1603
 * @return int
1604
 */
1605
function wc_ascii_uasort_comparison( $a, $b ) {
1606
	if ( function_exists( 'iconv' ) ) {
1607
		$a = iconv( 'UTF-8', 'ASCII//TRANSLIT', $a );
1608
		$b = iconv( 'UTF-8', 'ASCII//TRANSLIT', $b );
1609 100
	}
1610 100
	return strcmp( $a, $b );
1611
}
1612
1613
/**
1614
 * Get rounding mode for internal tax calculations.
1615
 *
1616
 * @since 3.2.4
1617
 * @return int
1618
 */
1619
function wc_get_tax_rounding_mode() {
1620
	$constant = WC_TAX_ROUNDING_MODE;
1621
1622 92
	if ( 'auto' === $constant ) {
1623 92
		return 'yes' === get_option( 'woocommerce_prices_include_tax', 'no' ) ? 2 : 1;
1624
	}
1625
1626 9
	return intval( $constant );
1627 5
}
1628
1629
/**
1630 9
 * Get rounding precision for internal WC calculations.
1631
 * Will increase the precision of wc_get_price_decimals by 2 decimals, unless WC_ROUNDING_PRECISION is set to a higher number.
1632
 *
1633
 * @since 2.6.3
1634
 * @return int
1635
 */
1636
function wc_get_rounding_precision() {
1637
	$precision = wc_get_price_decimals() + 2;
1638
	if ( absint( WC_ROUNDING_PRECISION ) > $precision ) {
1639
		$precision = absint( WC_ROUNDING_PRECISION );
1640
	}
1641 92
	return $precision;
1642 91
}
1643
1644
/**
1645 92
 * Add precision to a number and return a number.
1646 92
 *
1647
 * @since  3.2.0
1648
 * @param  float $value Number to add precision to.
1649 92
 * @param  bool  $round If should round after adding precision.
1650
 * @return int|float
1651
 */
1652
function wc_add_number_precision( $value, $round = true ) {
1653
	$cent_precision = pow( 10, wc_get_price_decimals() );
1654
	$value          = $value * $cent_precision;
1655
	return $round ? round( $value, wc_get_rounding_precision() - wc_get_price_decimals() ) : $value;
1656
}
1657
1658
/**
1659
 * Remove precision from a number and return a float.
1660
 *
1661
 * @since  3.2.0
1662
 * @param  float $value Number to add precision to.
1663
 * @return float
1664
 */
1665 7
function wc_remove_number_precision( $value ) {
1666
	$cent_precision = pow( 10, wc_get_price_decimals() );
1667 7
	return $value / $cent_precision;
1668
}
1669 7
1670 5
/**
1671
 * Add precision to an array of number and return an array of int.
1672
 *
1673 3
 * @since  3.2.0
1674
 * @param  array $value Number to add precision to.
1675 3
 * @param  bool  $round Should we round after adding precision?.
1676 2
 * @return int|array
1677
 */
1678 1
function wc_add_number_precision_deep( $value, $round = true ) {
1679 1
	if ( ! is_array( $value ) ) {
1680 1
		return wc_add_number_precision( $value, $round );
1681
	}
1682 1
1683 1
	foreach ( $value as $key => $sub_value ) {
1684 1
		$value[ $key ] = wc_add_number_precision_deep( $sub_value, $round );
1685 1
	}
1686
1687 1
	return $value;
1688
}
1689
1690 1
/**
1691
 * Remove precision from an array of number and return an array of int.
1692
 *
1693 3
 * @since  3.2.0
1694
 * @param  array $value Number to add precision to.
1695
 * @return int|array
1696
 */
1697
function wc_remove_number_precision_deep( $value ) {
1698
	if ( ! is_array( $value ) ) {
1699
		return wc_remove_number_precision( $value );
1700
	}
1701
1702
	foreach ( $value as $key => $sub_value ) {
1703
		$value[ $key ] = wc_remove_number_precision_deep( $sub_value );
1704
	}
1705
1706
	return $value;
1707
}
1708
1709
/**
1710
 * Get a shared logger instance.
1711
 *
1712
 * Use the woocommerce_logging_class filter to change the logging class. You may provide one of the following:
1713
 *     - a class name which will be instantiated as `new $class` with no arguments
1714
 *     - an instance which will be used directly as the logger
1715
 * In either case, the class or instance *must* implement WC_Logger_Interface.
1716
 *
1717
 * @see WC_Logger_Interface
1718
 *
1719
 * @return WC_Logger
1720
 */
1721
function wc_get_logger() {
1722
	static $logger = null;
1723
1724
	$class = apply_filters( 'woocommerce_logging_class', 'WC_Logger' );
1725
1726 2
	if ( null !== $logger && is_string( $class ) && is_a( $logger, $class ) ) {
1727 2
		return $logger;
1728
	}
1729
1730 2
	$implements = class_implements( $class );
1731 2
1732
	if ( is_array( $implements ) && in_array( 'WC_Logger_Interface', $implements, true ) ) {
1733
		$logger = is_object( $class ) ? $class : new $class();
1734 2
	} else {
1735 2
		wc_doing_it_wrong(
1736
			__FUNCTION__,
1737
			sprintf(
1738 2
				/* translators: 1: class name 2: woocommerce_logging_class 3: WC_Logger_Interface */
1739 2
				__( 'The class %1$s provided by %2$s filter must implement %3$s.', 'woocommerce' ),
1740
				'<code>' . esc_html( is_object( $class ) ? get_class( $class ) : $class ) . '</code>',
1741
				'<code>woocommerce_logging_class</code>',
1742
				'<code>WC_Logger_Interface</code>'
1743 2
			),
1744
			'3.0'
1745 2
		);
1746 2
1747 2
		$logger = is_a( $logger, 'WC_Logger' ) ? $logger : new WC_Logger();
1748 2
	}
1749 2
1750
	return $logger;
1751
}
1752 1
1753 1
/**
1754
 * Trigger logging cleanup using the logging class.
1755
 *
1756
 * @since 3.4.0
1757 1
 */
1758
function wc_cleanup_logs() {
1759
	$logger = wc_get_logger();
1760
1761
	if ( is_callable( array( $logger, 'clear_expired_logs' ) ) ) {
1762
		$logger->clear_expired_logs();
1763
	}
1764
}
1765
add_action( 'woocommerce_cleanup_logs', 'wc_cleanup_logs' );
1766
1767
/**
1768 4
 * Prints human-readable information about a variable.
1769
 *
1770
 * Some server environments blacklist some debugging functions. This function provides a safe way to
1771
 * turn an expression into a printable, readable form without calling blacklisted functions.
1772 4
 *
1773
 * @since 3.0
1774
 *
1775 4
 * @param mixed $expression The expression to be printed.
1776
 * @param bool  $return     Optional. Default false. Set to true to return the human-readable string.
1777 4
 * @return string|bool False if expression could not be printed. True if the expression was printed.
1778
 *     If $return is true, a string representation will be returned.
1779
 */
1780
function wc_print_r( $expression, $return = false ) {
1781
	$alternatives = array(
1782
		array(
1783
			'func' => 'print_r',
1784
			'args' => array( $expression, true ),
1785
		),
1786
		array(
1787
			'func' => 'var_export',
1788
			'args' => array( $expression, true ),
1789
		),
1790
		array(
1791
			'func' => 'json_encode',
1792
			'args' => array( $expression ),
1793 14
		),
1794 14
		array(
1795 10
			'func' => 'serialize',
1796
			'args' => array( $expression ),
1797 8
		),
1798
	);
1799
1800
	$alternatives = apply_filters( 'woocommerce_print_r_alternatives', $alternatives, $expression );
1801
1802
	foreach ( $alternatives as $alternative ) {
1803
		if ( function_exists( $alternative['func'] ) ) {
1804
			$res = call_user_func_array( $alternative['func'], $alternative['args'] );
1805
			if ( $return ) {
1806
				return $res;
1807
			}
1808
1809
			echo $res; // WPCS: XSS ok.
1810
			return true;
1811
		}
1812 8
	}
1813 8
1814
	return false;
1815 8
}
1816 8
1817
/**
1818
 * Registers the default log handler.
1819
 *
1820 8
 * @since 3.0
1821
 * @param array $handlers Handlers.
1822
 * @return array
1823 8
 */
1824
function wc_register_default_log_handler( $handlers ) {
1825
	if ( defined( 'WC_LOG_HANDLER' ) && class_exists( WC_LOG_HANDLER ) ) {
1826
		$handler_class   = WC_LOG_HANDLER;
1827
		$default_handler = new $handler_class();
1828
	} else {
1829
		$default_handler = new WC_Log_Handler_File();
1830
	}
1831
1832
	array_push( $handlers, $default_handler );
1833
1834
	return $handlers;
1835
}
1836
add_filter( 'woocommerce_register_log_handlers', 'wc_register_default_log_handler' );
1837
1838
/**
1839 1
 * Based on wp_list_pluck, this calls a method instead of returning a property.
1840 1
 *
1841 1
 * @since 3.0.0
1842
 * @param array      $list              List of objects or arrays.
1843 1
 * @param int|string $callback_or_field Callback method from the object to place instead of the entire object.
1844 1
 * @param int|string $index_key         Optional. Field from the object to use as keys for the new array.
1845 1
 *                                      Default null.
1846 1
 * @return array Array of values.
1847
 */
1848
function wc_list_pluck( $list, $callback_or_field, $index_key = null ) {
1849
	// Use wp_list_pluck if this isn't a callback.
1850
	$first_el = current( $list );
1851 1
	if ( ! is_object( $first_el ) || ! is_callable( array( $first_el, $callback_or_field ) ) ) {
1852
		return wp_list_pluck( $list, $callback_or_field, $index_key );
1853
	}
1854
	if ( ! $index_key ) {
1855 1
		/*
1856 1
		 * This is simple. Could at some point wrap array_column()
1857 1
		 * if we knew we had an array of arrays.
1858 1
		 */
1859
		foreach ( $list as $key => $value ) {
1860 1
			$list[ $key ] = $value->{$callback_or_field}();
1861
		}
1862
		return $list;
1863
	}
1864
1865
	/*
1866
	 * When index_key is not set for a particular item, push the value
1867
	 * to the end of the stack. This is how array_column() behaves.
1868
	 */
1869 1
	$newlist = array();
1870 1
	foreach ( $list as $value ) {
1871
		// Get index. @since 3.2.0 this supports a callback.
1872
		if ( is_callable( array( $value, $index_key ) ) ) {
1873 1
			$newlist[ $value->{$index_key}() ] = $value->{$callback_or_field}();
1874
		} elseif ( isset( $value->$index_key ) ) {
1875
			$newlist[ $value->$index_key ] = $value->{$callback_or_field}();
1876 1
		} else {
1877
			$newlist[] = $value->{$callback_or_field}();
1878
		}
1879
	}
1880
	return $newlist;
1881
}
1882
1883
/**
1884
 * Get permalink settings for things like products and taxonomies.
1885
 *
1886 1
 * As of 3.3.0, the permalink settings are stored to the option instead of
1887 1
 * being blank and inheritting from the locale. This speeds up page loading
1888
 * times by negating the need to switch locales on each page load.
1889
 *
1890 1
 * This is more inline with WP core behavior which does not localize slugs.
1891
 *
1892
 * @since  3.0.0
1893 1
 * @return array
1894
 */
1895
function wc_get_permalink_structure() {
1896
	$saved_permalinks = (array) get_option( 'woocommerce_permalinks', array() );
1897
	$permalinks       = wp_parse_args(
1898
		array_filter( $saved_permalinks ),
1899
		array(
1900
			'product_base'           => _x( 'product', 'slug', 'woocommerce' ),
1901
			'category_base'          => _x( 'product-category', 'slug', 'woocommerce' ),
1902
			'tag_base'               => _x( 'product-tag', 'slug', 'woocommerce' ),
1903
			'attribute_base'         => '',
1904
			'use_verbose_page_rules' => false,
1905
		)
1906
	);
1907
1908
	if ( $saved_permalinks !== $permalinks ) {
1909
		update_option( 'woocommerce_permalinks', $permalinks );
1910
	}
1911
1912
	$permalinks['product_rewrite_slug']   = untrailingslashit( $permalinks['product_base'] );
1913
	$permalinks['category_rewrite_slug']  = untrailingslashit( $permalinks['category_base'] );
1914
	$permalinks['tag_rewrite_slug']       = untrailingslashit( $permalinks['tag_base'] );
1915
	$permalinks['attribute_rewrite_slug'] = untrailingslashit( $permalinks['attribute_base'] );
1916
1917
	return $permalinks;
1918
}
1919
1920
/**
1921
 * Switch WooCommerce to site language.
1922
 *
1923
 * @since 3.1.0
1924
 */
1925
function wc_switch_to_site_locale() {
1926
	if ( function_exists( 'switch_to_locale' ) ) {
1927
		switch_to_locale( get_locale() );
1928
1929
		// Filter on plugin_locale so load_plugin_textdomain loads the correct locale.
1930
		add_filter( 'plugin_locale', 'get_locale' );
1931
1932
		// Init WC locale.
1933
		WC()->load_plugin_textdomain();
1934
	}
1935
}
1936
1937
/**
1938
 * Switch WooCommerce language to original.
1939
 *
1940
 * @since 3.1.0
1941
 */
1942
function wc_restore_locale() {
1943
	if ( function_exists( 'restore_previous_locale' ) ) {
1944
		restore_previous_locale();
1945
1946 21
		// Remove filter.
1947 1
		remove_filter( 'plugin_locale', 'get_locale' );
1948
1949
		// Init WC locale.
1950 21
		WC()->load_plugin_textdomain();
1951 21
	}
1952 21
}
1953
1954
/**
1955
 * Convert plaintext phone number to clickable phone number.
1956
 *
1957
 * Remove formatting and allow "+".
1958
 * Example and specs: https://developer.mozilla.org/en/docs/Web/HTML/Element/a#Creating_a_phone_link
1959
 *
1960
 * @since 3.1.0
1961
 *
1962
 * @param string $phone Content to convert phone number.
1963
 * @return string Content with converted phone number.
1964
 */
1965
function wc_make_phone_clickable( $phone ) {
1966
	$number = trim( preg_replace( '/[^\d|\+]/', '', $phone ) );
1967
1968
	return $number ? '<a href="tel:' . esc_attr( $number ) . '">' . esc_html( $phone ) . '</a>' : '';
1969
}
1970
1971
/**
1972
 * Get an item of post data if set, otherwise return a default value.
1973
 *
1974
 * @since  3.0.9
1975
 * @param  string $key     Meta key.
1976
 * @param  string $default Default value.
1977
 * @return mixed Value sanitized by wc_clean.
1978
 */
1979
function wc_get_post_data_by_key( $key, $default = '' ) {
1980
	return wc_clean( wp_unslash( wc_get_var( $_POST[ $key ], $default ) ) ); // @codingStandardsIgnoreLine
1981
}
1982
1983
/**
1984
 * Get data if set, otherwise return a default value or null. Prevents notices when data is not set.
1985
 *
1986
 * @since  3.2.0
1987
 * @param  mixed  $var     Variable.
1988
 * @param  string $default Default value.
1989
 * @return mixed
1990
 */
1991
function wc_get_var( &$var, $default = null ) {
1992
	return isset( $var ) ? $var : $default;
1993
}
1994
1995
/**
1996
 * Read in WooCommerce headers when reading plugin headers.
1997
 *
1998
 * @since 3.2.0
1999
 * @param array $headers Headers.
2000
 * @return array
2001
 */
2002
function wc_enable_wc_plugin_headers( $headers ) {
2003 2
	if ( ! class_exists( 'WC_Plugin_Updates' ) ) {
2004
		include_once dirname( __FILE__ ) . '/admin/plugin-updates/class-wc-plugin-updates.php';
2005
	}
2006
2007
	$headers['WCRequires'] = WC_Plugin_Updates::VERSION_REQUIRED_HEADER;
2008 2
	$headers['WCTested']   = WC_Plugin_Updates::VERSION_TESTED_HEADER;
2009
	return $headers;
2010 2
}
2011
add_filter( 'extra_plugin_headers', 'wc_enable_wc_plugin_headers' );
2012
2013
/**
2014
 * Prevent auto-updating the WooCommerce plugin on major releases if there are untested extensions active.
2015 2
 *
2016
 * @since 3.2.0
2017 2
 * @param  bool   $should_update If should update.
2018
 * @param  object $plugin        Plugin data.
2019
 * @return bool
2020
 */
2021
function wc_prevent_dangerous_auto_updates( $should_update, $plugin ) {
2022
	if ( ! isset( $plugin->plugin, $plugin->new_version ) ) {
2023
		return $should_update;
2024
	}
2025
2026
	if ( 'woocommerce/woocommerce.php' !== $plugin->plugin ) {
2027
		return $should_update;
2028
	}
2029
2030
	if ( ! class_exists( 'WC_Plugin_Updates' ) ) {
2031
		include_once dirname( __FILE__ ) . '/admin/plugin-updates/class-wc-plugin-updates.php';
2032
	}
2033
2034
	$new_version      = wc_clean( $plugin->new_version );
2035
	$plugin_updates   = new WC_Plugin_Updates();
2036
	$untested_plugins = $plugin_updates->get_untested_plugins( $new_version, 'major' );
0 ignored issues
show
Bug introduced by
It seems like $new_version defined by wc_clean($plugin->new_version) on line 2034 can also be of type array; however, WC_Plugin_Updates::get_untested_plugins() does only seem to accept string, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
2037
	if ( ! empty( $untested_plugins ) ) {
2038
		return false;
2039
	}
2040
2041
	return $should_update;
2042
}
2043
add_filter( 'auto_update_plugin', 'wc_prevent_dangerous_auto_updates', 99, 2 );
2044
2045
/**
2046
 * Delete expired transients.
2047
 *
2048
 * Deletes all expired transients. The multi-table delete syntax is used.
2049
 * to delete the transient record from table a, and the corresponding.
2050
 * transient_timeout record from table b.
2051
 *
2052
 * Based on code inside core's upgrade_network() function.
2053 1
 *
2054
 * @since 3.2.0
2055
 * @return int Number of transients that were cleared.
2056
 */
2057
function wc_delete_expired_transients() {
2058
	global $wpdb;
2059
2060
	$sql  = "DELETE a, b FROM $wpdb->options a, $wpdb->options b
2061
		WHERE a.option_name LIKE %s
2062
		AND a.option_name NOT LIKE %s
2063
		AND b.option_name = CONCAT( '_transient_timeout_', SUBSTRING( a.option_name, 12 ) )
2064
		AND b.option_value < %d";
2065
	$rows = $wpdb->query( $wpdb->prepare( $sql, $wpdb->esc_like( '_transient_' ) . '%', $wpdb->esc_like( '_transient_timeout_' ) . '%', time() ) ); // WPCS: unprepared SQL ok.
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
2066
2067
	$sql   = "DELETE a, b FROM $wpdb->options a, $wpdb->options b
2068
		WHERE a.option_name LIKE %s
2069
		AND a.option_name NOT LIKE %s
2070
		AND b.option_name = CONCAT( '_site_transient_timeout_', SUBSTRING( a.option_name, 17 ) )
2071
		AND b.option_value < %d";
2072
	$rows2 = $wpdb->query( $wpdb->prepare( $sql, $wpdb->esc_like( '_site_transient_' ) . '%', $wpdb->esc_like( '_site_transient_timeout_' ) . '%', time() ) ); // WPCS: unprepared SQL ok.
0 ignored issues
show
introduced by
Usage of a direct database call is discouraged.
Loading history...
introduced by
Usage of a direct database call without caching is prohibited. Use wp_cache_get / wp_cache_set.
Loading history...
2073
2074
	return absint( $rows + $rows2 );
2075
}
2076
add_action( 'woocommerce_installed', 'wc_delete_expired_transients' );
2077
2078
/**
2079 1
 * Make a URL relative, if possible.
2080
 *
2081
 * @since 3.2.0
2082
 * @param string $url URL to make relative.
2083
 * @return string
2084 1
 */
2085
function wc_get_relative_url( $url ) {
2086
	return wc_is_external_resource( $url ) ? $url : str_replace( array( 'http://', 'https://' ), '//', $url );
2087
}
2088 1
2089 1
/**
2090 1
 * See if a resource is remote.
2091 1
 *
2092 1
 * @since 3.2.0
2093 1
 * @param string $url URL to check.
2094
 * @return bool
2095
 */
2096 1
function wc_is_external_resource( $url ) {
2097 1
	$wp_base = str_replace( array( 'http://', 'https://' ), '//', get_home_url( null, '/', 'http' ) );
2098 1
2099 1
	return strstr( $url, '://' ) && ! strstr( $url, $wp_base );
2100 1
}
2101 1
2102 1
/**
2103 1
 * See if theme/s is activate or not.
2104 1
 *
2105 1
 * @since 3.3.0
2106
 * @param string|array $theme Theme name or array of theme names to check.
2107 1
 * @return boolean
2108
 */
2109
function wc_is_active_theme( $theme ) {
2110
	return is_array( $theme ) ? in_array( get_template(), $theme, true ) : get_template() === $theme;
2111
}
2112
2113
/**
2114
 * Cleans up session data - cron callback.
2115
 *
2116
 * @since 3.3.0
2117
 */
2118 83
function wc_cleanup_session_data() {
2119 83
	$session_class = apply_filters( 'woocommerce_session_handler', 'WC_Session_Handler' );
2120
	$session       = new $session_class();
2121
2122
	if ( is_callable( array( $session, 'cleanup_sessions' ) ) ) {
2123
		$session->cleanup_sessions();
2124
	}
2125
}
2126
add_action( 'woocommerce_cleanup_sessions', 'wc_cleanup_session_data' );
2127
2128
/**
2129
 * Convert a decimal (e.g. 3.5) to a fraction (e.g. 7/2).
2130
 * From: https://www.designedbyaturtle.co.uk/2015/converting-a-decimal-to-a-fraction-in-php/
2131
 *
2132
 * @param float $decimal the decimal number.
2133
 * @return array|bool a 1/2 would be [1, 2] array (this can be imploded with '/' to form a string).
2134
 */
2135
function wc_decimal_to_fraction( $decimal ) {
2136
	if ( 0 > $decimal || ! is_numeric( $decimal ) ) {
2137
		// Negative digits need to be passed in as positive numbers and prefixed as negative once the response is imploded.
2138 1
		return false;
2139 1
	}
2140 1
2141
	if ( 0 === $decimal ) {
2142
		return array( 0, 1 );
2143 1
	}
2144
2145
	$tolerance   = 1.e-4;
2146
	$numerator   = 1;
2147
	$h2          = 0;
2148
	$denominator = 0;
2149
	$k2          = 1;
2150
	$b           = 1 / $decimal;
2151
2152
	do {
2153
		$b           = 1 / $b;
2154
		$a           = floor( $b );
2155 16
		$aux         = $numerator;
2156
		$numerator   = $a * $numerator + $h2;
2157
		$h2          = $aux;
2158
		$aux         = $denominator;
2159
		$denominator = $a * $denominator + $k2;
2160
		$k2          = $aux;
2161
		$b           = $b - $a;
2162 16
	} while ( abs( $decimal - $numerator / $denominator ) > $decimal * $tolerance );
2163 16
2164
	return array( $numerator, $denominator );
2165
}
2166
2167
/**
2168
 * Round discount.
2169 16
 *
2170 16
 * @param  double $value Amount to round.
2171
 * @param  int    $precision DP to round.
2172
 * @return float
2173
 */
2174
function wc_round_discount( $value, $precision ) {
2175
	if ( version_compare( PHP_VERSION, '5.3.0', '>=' ) ) {
2176
		return round( $value, $precision, WC_DISCOUNT_ROUNDING_MODE ); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctionParameters.round_modeFound
2177
	}
2178
2179
	if ( 2 === WC_DISCOUNT_ROUNDING_MODE ) {
2180
		return wc_legacy_round_half_down( $value, $precision );
2181
	}
2182
2183
	return round( $value, $precision );
2184
}
2185
2186
/**
2187
 * Return the html selected attribute if stringified $value is found in array of stringified $options
2188
 * or if stringified $value is the same as scalar stringified $options.
2189
 *
2190
 * @param string|int       $value   Value to find within options.
2191
 * @param string|int|array $options Options to go through when looking for value.
2192
 * @return string
2193
 */
2194
function wc_selected( $value, $options ) {
2195 View Code Duplication
	if ( is_array( $options ) ) {
2196
		$options = array_map( 'strval', $options );
2197
		return selected( in_array( (string) $value, $options, true ), true, false );
2198
	}
2199
2200
	return selected( $value, $options, false );
2201
}
2202
2203
/**
2204
 * Retrieves the MySQL server version. Based on $wpdb.
2205
 *
2206
 * @since 3.4.1
2207
 * @return array Vesion information.
2208
 */
2209
function wc_get_server_database_version() {
2210
	global $wpdb;
2211
2212
	if ( empty( $wpdb->is_mysql ) ) {
2213
		return array(
2214
			'string' => '',
2215
			'number' => '',
2216
		);
2217
	}
2218
2219
	if ( $wpdb->use_mysqli ) {
2220
		$server_info = mysqli_get_server_info( $wpdb->dbh ); // @codingStandardsIgnoreLine.
2221
	} else {
2222
		$server_info = mysql_get_server_info( $wpdb->dbh ); // @codingStandardsIgnoreLine.
2223
	}
2224
2225
	return array(
2226
		'string' => $server_info,
2227
		'number' => preg_replace( '/([^\d.]+).*/', '', $server_info ),
2228
	);
2229
}
2230