· 6 years ago · Feb 28, 2020, 01:30 PM
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
11if ( ! defined( 'ABSPATH' ) ) {
12 exit;
13}
14
15// Include core functions (available in both admin and frontend).
16require WC_ABSPATH . 'includes/wc-conditional-functions.php';
17require WC_ABSPATH . 'includes/wc-coupon-functions.php';
18require WC_ABSPATH . 'includes/wc-user-functions.php';
19require WC_ABSPATH . 'includes/wc-deprecated-functions.php';
20require WC_ABSPATH . 'includes/wc-formatting-functions.php';
21require WC_ABSPATH . 'includes/wc-order-functions.php';
22require WC_ABSPATH . 'includes/wc-order-item-functions.php';
23require WC_ABSPATH . 'includes/wc-page-functions.php';
24require WC_ABSPATH . 'includes/wc-product-functions.php';
25require WC_ABSPATH . 'includes/wc-stock-functions.php';
26require WC_ABSPATH . 'includes/wc-account-functions.php';
27require WC_ABSPATH . 'includes/wc-term-functions.php';
28require WC_ABSPATH . 'includes/wc-attribute-functions.php';
29require WC_ABSPATH . 'includes/wc-rest-functions.php';
30require WC_ABSPATH . 'includes/wc-widget-functions.php';
31require WC_ABSPATH . 'includes/wc-webhook-functions.php';
32
33/**
34 * Filters on data used in admin and frontend.
35 */
36add_filter( 'woocommerce_coupon_code', 'html_entity_decode' );
37add_filter( 'woocommerce_coupon_code', 'wc_sanitize_coupon_code' );
38add_filter( 'woocommerce_coupon_code', 'wc_strtolower' );
39add_filter( 'woocommerce_stock_amount', 'intval' ); // Stock amounts are integers by default.
40add_filter( 'woocommerce_shipping_rate_label', 'sanitize_text_field' ); // Shipping rate label.
41add_filter( 'woocommerce_attribute_label', 'wp_kses_post', 100 );
42
43/**
44 * Short Description (excerpt).
45 */
46if ( function_exists( 'do_blocks' ) ) {
47 add_filter( 'woocommerce_short_description', 'do_blocks', 9 );
48}
49add_filter( 'woocommerce_short_description', 'wptexturize' );
50add_filter( 'woocommerce_short_description', 'convert_smilies' );
51add_filter( 'woocommerce_short_description', 'convert_chars' );
52add_filter( 'woocommerce_short_description', 'wpautop' );
53add_filter( 'woocommerce_short_description', 'shortcode_unautop' );
54add_filter( 'woocommerce_short_description', 'prepend_attachment' );
55add_filter( 'woocommerce_short_description', 'do_shortcode', 11 ); // After wpautop().
56add_filter( 'woocommerce_short_description', 'wc_format_product_short_description', 9999999 );
57add_filter( 'woocommerce_short_description', 'wc_do_oembeds' );
58add_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 * @param mixed $value Value.
66 */
67function 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 */
81function 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 );
91
92 try {
93 $args = wp_parse_args( $args, $default_args );
94 $order = new WC_Order( $args['order_id'] );
95
96 // Update props that were set (not null).
97 if ( ! is_null( $args['parent'] ) ) {
98 $order->set_parent_id( absint( $args['parent'] ) );
99 }
100
101 if ( ! is_null( $args['status'] ) ) {
102 $order->set_status( $args['status'] );
103 }
104
105 if ( ! is_null( $args['customer_note'] ) ) {
106 $order->set_customer_note( $args['customer_note'] );
107 }
108
109 if ( ! is_null( $args['customer_id'] ) ) {
110 $order->set_customer_id( is_numeric( $args['customer_id'] ) ? absint( $args['customer_id'] ) : 0 );
111 }
112
113 if ( ! is_null( $args['created_via'] ) ) {
114 $order->set_created_via( sanitize_text_field( $args['created_via'] ) );
115 }
116
117 if ( ! is_null( $args['cart_hash'] ) ) {
118 $order->set_cart_hash( sanitize_text_field( $args['cart_hash'] ) );
119 }
120
121 // Set these fields when creating a new order but not when updating an existing order.
122 if ( ! $args['order_id'] ) {
123 $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 }
128
129 // Update other order props set automatically.
130 $order->save();
131 } catch ( Exception $e ) {
132 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 * @return WC_Order|WP_Error
143 */
144function wc_update_order( $args ) {
145 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 * @param string $name Template name (default: '').
158 */
159function wc_get_template_part( $slug, $name = '' ) {
160 $cache_key = sanitize_key( implode( '-', array( 'template-part', $slug, $name, WC_VERSION ) ) );
161 $template = (string) wp_cache_get( $cache_key, 'woocommerce' );
162
163 if ( ! $template ) {
164 if ( $name ) {
165 $template = WC_TEMPLATE_DEBUG_MODE ? '' : locate_template(
166 array(
167 "{$slug}-{$name}.php",
168 WC()->template_path() . "{$slug}-{$name}.php",
169 )
170 );
171
172 if ( ! $template ) {
173 $fallback = WC()->plugin_path() . "/templates/{$slug}-{$name}.php";
174 $template = file_exists( $fallback ) ? $fallback : '';
175 }
176 }
177
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 // Allow 3rd party plugins to filter template file from their plugin.
192 $template = apply_filters( 'wc_get_template_part', $template, $slug, $name );
193
194 if ( $template ) {
195 load_template( $template, false );
196 }
197}
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 * @param string $template_path Template path. (default: '').
205 * @param string $default_path Default path. (default: '').
206 */
207function wc_get_template( $template_name, $args = array(), $template_path = '', $default_path = '' ) {
208 $cache_key = sanitize_key( implode( '-', array( 'template', $template_name, $template_path, $default_path, WC_VERSION ) ) );
209 $template = (string) wp_cache_get( $cache_key, 'woocommerce' );
210
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
228 $action_args = array(
229 '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 if ( isset( $args['action_args'] ) ) {
237 wc_doing_it_wrong(
238 __FUNCTION__,
239 __( 'action_args should not be overwritten when calling wc_get_template.', 'woocommerce' ),
240 '3.6.0'
241 );
242 unset( $args['action_args'] );
243 }
244 extract( $args ); // @codingStandardsIgnoreLine
245 }
246
247 do_action( 'woocommerce_before_template_part', $action_args['template_name'], $action_args['template_path'], $action_args['located'], $action_args['args'] );
248
249 include $action_args['located'];
250
251 do_action( 'woocommerce_after_template_part', $action_args['template_name'], $action_args['template_path'], $action_args['located'], $action_args['args'] );
252}
253
254/**
255 * Like wc_get_template, but returns the HTML instead of outputting.
256 *
257 * @see wc_get_template
258 * @since 2.5.0
259 * @param string $template_name Template name.
260 * @param array $args Arguments. (default: array).
261 * @param string $template_path Template path. (default: '').
262 * @param string $default_path Default path. (default: '').
263 *
264 * @return string
265 */
266function wc_get_template_html( $template_name, $args = array(), $template_path = '', $default_path = '' ) {
267 ob_start();
268 wc_get_template( $template_name, $args, $template_path, $default_path );
269 return ob_get_clean();
270}
271/**
272 * Locate a template and return the path for inclusion.
273 *
274 * This is the load order:
275 *
276 * yourtheme/$template_path/$template_name
277 * yourtheme/$template_name
278 * $default_path/$template_name
279 *
280 * @param string $template_name Template name.
281 * @param string $template_path Template path. (default: '').
282 * @param string $default_path Default path. (default: '').
283 * @return string
284 */
285function wc_locate_template( $template_name, $template_path = '', $default_path = '' ) {
286 if ( ! $template_path ) {
287 $template_path = WC()->template_path();
288 }
289
290 if ( ! $default_path ) {
291 $default_path = WC()->plugin_path() . '/templates/';
292 }
293
294 // Look within passed path within the theme - this is priority.
295 $template = locate_template(
296 array(
297 trailingslashit( $template_path ) . $template_name,
298 $template_name,
299 )
300 );
301
302 // Get default template/.
303 if ( ! $template || WC_TEMPLATE_DEBUG_MODE ) {
304 $template = $default_path . $template_name;
305 }
306
307 // Return what we found.
308 return apply_filters( 'woocommerce_locate_template', $template, $template_name, $template_path );
309}
310
311/**
312 * Get Base Currency Code.
313 *
314 * @return string
315 */
316function get_woocommerce_currency() {
317 return apply_filters( 'woocommerce_currency', get_option( 'woocommerce_currency' ) );
318}
319
320/**
321 * Get full list of currency codes.
322 *
323 * Currency Symbols and mames should follow the Unicode CLDR recommendation (http://cldr.unicode.org/translation/currency-names)
324 *
325 * @return array
326 */
327function get_woocommerce_currencies() {
328 static $currencies;
329
330 if ( ! isset( $currencies ) ) {
331 $currencies = array_unique(
332 apply_filters(
333 'woocommerce_currencies',
334 array(
335 'AED' => __( 'United Arab Emirates dirham', 'woocommerce' ),
336 'AFN' => __( 'Afghan afghani', 'woocommerce' ),
337 'ALL' => __( 'Albanian lek', 'woocommerce' ),
338 'AMD' => __( 'Armenian dram', 'woocommerce' ),
339 'ANG' => __( 'Netherlands Antillean guilder', 'woocommerce' ),
340 'AOA' => __( 'Angolan kwanza', 'woocommerce' ),
341 'ARS' => __( 'Argentine peso', 'woocommerce' ),
342 'AUD' => __( 'Australian dollar', 'woocommerce' ),
343 'AWG' => __( 'Aruban florin', 'woocommerce' ),
344 'AZN' => __( 'Azerbaijani manat', 'woocommerce' ),
345 'BAM' => __( 'Bosnia and Herzegovina convertible mark', 'woocommerce' ),
346 'BBD' => __( 'Barbadian dollar', 'woocommerce' ),
347 'BDT' => __( 'Bangladeshi taka', 'woocommerce' ),
348 'BGN' => __( 'Bulgarian lev', 'woocommerce' ),
349 'BHD' => __( 'Bahraini dinar', 'woocommerce' ),
350 'BIF' => __( 'Burundian franc', 'woocommerce' ),
351 'BMD' => __( 'Bermudian dollar', 'woocommerce' ),
352 'BND' => __( 'Brunei dollar', 'woocommerce' ),
353 'BOB' => __( 'Bolivian boliviano', 'woocommerce' ),
354 'BRL' => __( 'Brazilian real', 'woocommerce' ),
355 'BSD' => __( 'Bahamian dollar', 'woocommerce' ),
356 'BTC' => __( 'Bitcoin', 'woocommerce' ),
357 'BTN' => __( 'Bhutanese ngultrum', 'woocommerce' ),
358 'BWP' => __( 'Botswana pula', 'woocommerce' ),
359 'BYR' => __( 'Belarusian ruble (old)', 'woocommerce' ),
360 'BYN' => __( 'Belarusian ruble', 'woocommerce' ),
361 'BZD' => __( 'Belize dollar', 'woocommerce' ),
362 'CAD' => __( 'Canadian dollar', 'woocommerce' ),
363 'CDF' => __( 'Congolese franc', 'woocommerce' ),
364 'CHF' => __( 'Swiss franc', 'woocommerce' ),
365 'CLP' => __( 'Chilean peso', 'woocommerce' ),
366 'CNY' => __( 'Chinese yuan', 'woocommerce' ),
367 'COP' => __( 'Colombian peso', 'woocommerce' ),
368 'CRC' => __( 'Costa Rican colón', 'woocommerce' ),
369 'CUC' => __( 'Cuban convertible peso', 'woocommerce' ),
370 'CUP' => __( 'Cuban peso', 'woocommerce' ),
371 'CVE' => __( 'Cape Verdean escudo', 'woocommerce' ),
372 'CZK' => __( 'Czech koruna', 'woocommerce' ),
373 'DJF' => __( 'Djiboutian franc', 'woocommerce' ),
374 'DKK' => __( 'Danish krone', 'woocommerce' ),
375 'DOP' => __( 'Dominican peso', 'woocommerce' ),
376 'DZD' => __( 'Algerian dinar', 'woocommerce' ),
377 'EGP' => __( 'Egyptian pound', 'woocommerce' ),
378 'ERN' => __( 'Eritrean nakfa', 'woocommerce' ),
379 'ETB' => __( 'Ethiopian birr', 'woocommerce' ),
380 'EUR' => __( 'Euro', 'woocommerce' ),
381 'FJD' => __( 'Fijian dollar', 'woocommerce' ),
382 'FKP' => __( 'Falkland Islands pound', 'woocommerce' ),
383 'GBP' => __( 'Pound sterling', 'woocommerce' ),
384 'GEL' => __( 'Georgian lari', 'woocommerce' ),
385 'GGP' => __( 'Guernsey pound', 'woocommerce' ),
386 'GHS' => __( 'Ghana cedi', 'woocommerce' ),
387 'GIP' => __( 'Gibraltar pound', 'woocommerce' ),
388 'GMD' => __( 'Gambian dalasi', 'woocommerce' ),
389 'GNF' => __( 'Guinean franc', 'woocommerce' ),
390 'GTQ' => __( 'Guatemalan quetzal', 'woocommerce' ),
391 'GYD' => __( 'Guyanese dollar', 'woocommerce' ),
392 'HKD' => __( 'Hong Kong dollar', 'woocommerce' ),
393 'HNL' => __( 'Honduran lempira', 'woocommerce' ),
394 'HRK' => __( 'Croatian kuna', 'woocommerce' ),
395 'HTG' => __( 'Haitian gourde', 'woocommerce' ),
396 'HUF' => __( 'Hungarian forint', 'woocommerce' ),
397 'IDR' => __( 'Indonesian rupiah', 'woocommerce' ),
398 'ILS' => __( 'Israeli new shekel', 'woocommerce' ),
399 'IMP' => __( 'Manx pound', 'woocommerce' ),
400 'INR' => __( 'Indian rupee', 'woocommerce' ),
401 'IQD' => __( 'Iraqi dinar', 'woocommerce' ),
402 'IRR' => __( 'Iranian rial', 'woocommerce' ),
403 'IRT' => __( 'Iranian toman', 'woocommerce' ),
404 'ISK' => __( 'Icelandic króna', 'woocommerce' ),
405 'JEP' => __( 'Jersey pound', 'woocommerce' ),
406 'JMD' => __( 'Jamaican dollar', 'woocommerce' ),
407 'JOD' => __( 'Jordanian dinar', 'woocommerce' ),
408 'JPY' => __( 'Japanese yen', 'woocommerce' ),
409 'KES' => __( 'Kenyan shilling', 'woocommerce' ),
410 'KGS' => __( 'Kyrgyzstani som', 'woocommerce' ),
411 'KHR' => __( 'Cambodian riel', 'woocommerce' ),
412 'KMF' => __( 'Comorian franc', 'woocommerce' ),
413 'KPW' => __( 'North Korean won', 'woocommerce' ),
414 'KRW' => __( 'South Korean won', 'woocommerce' ),
415 'KWD' => __( 'Kuwaiti dinar', 'woocommerce' ),
416 'KYD' => __( 'Cayman Islands dollar', 'woocommerce' ),
417 'KZT' => __( 'Kazakhstani tenge', 'woocommerce' ),
418 'LAK' => __( 'Lao kip', 'woocommerce' ),
419 'LBP' => __( 'Lebanese pound', 'woocommerce' ),
420 'LKR' => __( 'Sri Lankan rupee', 'woocommerce' ),
421 'LRD' => __( 'Liberian dollar', 'woocommerce' ),
422 'LSL' => __( 'Lesotho loti', 'woocommerce' ),
423 'LYD' => __( 'Libyan dinar', 'woocommerce' ),
424 'MAD' => __( 'Moroccan dirham', 'woocommerce' ),
425 'MDL' => __( 'Moldovan leu', 'woocommerce' ),
426 'MGA' => __( 'Malagasy ariary', 'woocommerce' ),
427 'MKD' => __( 'Macedonian denar', 'woocommerce' ),
428 'MMK' => __( 'Burmese kyat', 'woocommerce' ),
429 'MNT' => __( 'Mongolian tögrög', 'woocommerce' ),
430 'MOP' => __( 'Macanese pataca', 'woocommerce' ),
431 'MRU' => __( 'Mauritanian ouguiya', 'woocommerce' ),
432 'MUR' => __( 'Mauritian rupee', 'woocommerce' ),
433 'MVR' => __( 'Maldivian rufiyaa', 'woocommerce' ),
434 'MWK' => __( 'Malawian kwacha', 'woocommerce' ),
435 'MXN' => __( 'Mexican peso', 'woocommerce' ),
436 'MYR' => __( 'Malaysian ringgit', 'woocommerce' ),
437 'MZN' => __( 'Mozambican metical', 'woocommerce' ),
438 'NAD' => __( 'Namibian dollar', 'woocommerce' ),
439 'NGN' => __( 'Nigerian naira', 'woocommerce' ),
440 'NIO' => __( 'Nicaraguan córdoba', 'woocommerce' ),
441 'NOK' => __( 'Norwegian krone', 'woocommerce' ),
442 'NPR' => __( 'Nepalese rupee', 'woocommerce' ),
443 'NZD' => __( 'New Zealand dollar', 'woocommerce' ),
444 'OMR' => __( 'Omani rial', 'woocommerce' ),
445 'PAB' => __( 'Panamanian balboa', 'woocommerce' ),
446 'PEN' => __( 'Sol', 'woocommerce' ),
447 'PGK' => __( 'Papua New Guinean kina', 'woocommerce' ),
448 'PHP' => __( 'Philippine peso', 'woocommerce' ),
449 'PKR' => __( 'Pakistani rupee', 'woocommerce' ),
450 'PLN' => __( 'Polish złoty', 'woocommerce' ),
451 'PRB' => __( 'Transnistrian ruble', 'woocommerce' ),
452 'PYG' => __( 'Paraguayan guaraní', 'woocommerce' ),
453 'QAR' => __( 'Qatari riyal', 'woocommerce' ),
454 'RON' => __( 'Romanian leu', 'woocommerce' ),
455 'RSD' => __( 'Serbian dinar', 'woocommerce' ),
456 'RUB' => __( 'Russian ruble', 'woocommerce' ),
457 'RWF' => __( 'Rwandan franc', 'woocommerce' ),
458 'SAR' => __( 'Saudi riyal', 'woocommerce' ),
459 'SBD' => __( 'Solomon Islands dollar', 'woocommerce' ),
460 'SCR' => __( 'Seychellois rupee', 'woocommerce' ),
461 'SDG' => __( 'Sudanese pound', 'woocommerce' ),
462 'SEK' => __( 'Swedish krona', 'woocommerce' ),
463 'SGD' => __( 'Singapore dollar', 'woocommerce' ),
464 'SHP' => __( 'Saint Helena pound', 'woocommerce' ),
465 'SLL' => __( 'Sierra Leonean leone', 'woocommerce' ),
466 'SOS' => __( 'Somali shilling', 'woocommerce' ),
467 'SRD' => __( 'Surinamese dollar', 'woocommerce' ),
468 'SSP' => __( 'South Sudanese pound', 'woocommerce' ),
469 'STN' => __( 'São Tomé and Príncipe dobra', 'woocommerce' ),
470 'SYP' => __( 'Syrian pound', 'woocommerce' ),
471 'SZL' => __( 'Swazi lilangeni', 'woocommerce' ),
472 'THB' => __( 'Thai baht', 'woocommerce' ),
473 'TJS' => __( 'Tajikistani somoni', 'woocommerce' ),
474 'TMT' => __( 'Turkmenistan manat', 'woocommerce' ),
475 'TND' => __( 'Tunisian dinar', 'woocommerce' ),
476 'TOP' => __( 'Tongan paʻanga', 'woocommerce' ),
477 'TRY' => __( 'Turkish lira', 'woocommerce' ),
478 'TTD' => __( 'Trinidad and Tobago dollar', 'woocommerce' ),
479 'TWD' => __( 'New Taiwan dollar', 'woocommerce' ),
480 'TZS' => __( 'Tanzanian shilling', 'woocommerce' ),
481 'UAH' => __( 'Ukrainian hryvnia', 'woocommerce' ),
482 'UGX' => __( 'Ugandan shilling', 'woocommerce' ),
483 'USD' => __( 'United States (US) dollar', 'woocommerce' ),
484 'UYU' => __( 'Uruguayan peso', 'woocommerce' ),
485 'UZS' => __( 'Uzbekistani som', 'woocommerce' ),
486 'VEF' => __( 'Venezuelan bolívar', 'woocommerce' ),
487 'VES' => __( 'Bolívar soberano', 'woocommerce' ),
488 'VND' => __( 'Vietnamese đồng', 'woocommerce' ),
489 'VUV' => __( 'Vanuatu vatu', 'woocommerce' ),
490 'WST' => __( 'Samoan tālā', 'woocommerce' ),
491 'XAF' => __( 'Central African CFA franc', 'woocommerce' ),
492 'XCD' => __( 'East Caribbean dollar', 'woocommerce' ),
493 'XOF' => __( 'West African CFA franc', 'woocommerce' ),
494 'XPF' => __( 'CFP franc', 'woocommerce' ),
495 'YER' => __( 'Yemeni rial', 'woocommerce' ),
496 'ZAR' => __( 'South African rand', 'woocommerce' ),
497 'ZMW' => __( 'Zambian kwacha', 'woocommerce' ),
498 )
499 )
500 );
501 }
502
503 return $currencies;
504}
505
506
507/**
508 * Get Currency symbol.
509 *
510 * Currency Symbols and mames should follow the Unicode CLDR recommendation (http://cldr.unicode.org/translation/currency-names)
511 *
512 * @param string $currency Currency. (default: '').
513 * @return string
514 */
515function get_woocommerce_currency_symbol( $currency = '' ) {
516 if ( ! $currency ) {
517 $currency = get_woocommerce_currency();
518 }
519
520 $symbols = apply_filters(
521 'woocommerce_currency_symbols',
522 array(
523 'AED' => 'د.إ',
524 'AFN' => '؋',
525 'ALL' => 'L',
526 'AMD' => 'AMD',
527 'ANG' => 'ƒ',
528 'AOA' => 'Kz',
529 'ARS' => '$',
530 'AUD' => '$',
531 'AWG' => 'Afl.',
532 'AZN' => 'AZN',
533 'BAM' => 'KM',
534 'BBD' => '$',
535 'BDT' => '৳ ',
536 'BGN' => 'лв.',
537 'BHD' => '.د.ب',
538 'BIF' => 'Fr',
539 'BMD' => '$',
540 'BND' => '$',
541 'BOB' => 'Bs.',
542 'BRL' => 'R$',
543 'BSD' => '$',
544 'BTC' => '฿',
545 'BTN' => 'Nu.',
546 'BWP' => 'P',
547 'BYR' => 'Br',
548 'BYN' => 'Br',
549 'BZD' => '$',
550 'CAD' => '$',
551 'CDF' => 'Fr',
552 'CHF' => 'CHF',
553 'CLP' => '$',
554 'CNY' => '¥',
555 'COP' => '$',
556 'CRC' => '₡',
557 'CUC' => '$',
558 'CUP' => '$',
559 'CVE' => '$',
560 'CZK' => 'Kč',
561 'DJF' => 'Fr',
562 'DKK' => 'DKK',
563 'DOP' => 'RD$',
564 'DZD' => 'د.ج',
565 'EGP' => 'EGP',
566 'ERN' => 'Nfk',
567 'ETB' => 'Br',
568 'EUR' => '€',
569 'FJD' => '$',
570 'FKP' => '£',
571 'GBP' => '£',
572 'GEL' => '₾',
573 'GGP' => '£',
574 'GHS' => '₵',
575 'GIP' => '£',
576 'GMD' => 'D',
577 'GNF' => 'Fr',
578 'GTQ' => 'Q',
579 'GYD' => '$',
580 'HKD' => '$',
581 'HNL' => 'L',
582 'HRK' => 'kn',
583 'HTG' => 'G',
584 'HUF' => 'Ft',
585 'IDR' => 'Rp',
586 'ILS' => '₪',
587 'IMP' => '£',
588 'INR' => '₹',
589 'IQD' => 'ع.د',
590 'IRR' => '﷼',
591 'IRT' => 'تومان',
592 'ISK' => 'kr.',
593 'JEP' => '£',
594 'JMD' => '$',
595 'JOD' => 'د.ا',
596 'JPY' => '¥',
597 'KES' => 'KSh',
598 'KGS' => 'сом',
599 'KHR' => '៛',
600 'KMF' => 'Fr',
601 'KPW' => '₩',
602 'KRW' => '₩',
603 'KWD' => 'د.ك',
604 'KYD' => '$',
605 'KZT' => 'KZT',
606 'LAK' => '₭',
607 'LBP' => 'ل.ل',
608 'LKR' => 'රු',
609 'LRD' => '$',
610 'LSL' => 'L',
611 'LYD' => 'ل.د',
612 'MAD' => 'د.م.',
613 'MDL' => 'MDL',
614 'MGA' => 'Ar',
615 'MKD' => 'ден',
616 'MMK' => 'Ks',
617 'MNT' => '₮',
618 'MOP' => 'P',
619 'MRU' => 'UM',
620 'MUR' => '₨',
621 'MVR' => '.ރ',
622 'MWK' => 'MK',
623 'MXN' => '$',
624 'MYR' => 'RM',
625 'MZN' => 'MT',
626 'NAD' => 'N$',
627 'NGN' => '₦',
628 'NIO' => 'C$',
629 'NOK' => 'kr',
630 'NPR' => '₨',
631 'NZD' => '$',
632 'OMR' => 'ر.ع.',
633 'PAB' => 'B/.',
634 'PEN' => 'S/',
635 'PGK' => 'K',
636 'PHP' => '₱',
637 'PKR' => '₨',
638 'PLN' => 'zł',
639 'PRB' => 'р.',
640 'PYG' => '₲',
641 'QAR' => 'ر.ق',
642 'RMB' => '¥',
643 'RON' => 'lei',
644 'RSD' => 'дин.',
645 'RUB' => '₽',
646 'RWF' => 'Fr',
647 'SAR' => 'ر.س',
648 'SBD' => '$',
649 'SCR' => '₨',
650 'SDG' => 'ج.س.',
651 'SEK' => 'kr',
652 'SGD' => '$',
653 'SHP' => '£',
654 'SLL' => 'Le',
655 'SOS' => 'Sh',
656 'SRD' => '$',
657 'SSP' => '£',
658 'STN' => 'Db',
659 'SYP' => 'ل.س',
660 'SZL' => 'L',
661 'THB' => '฿',
662 'TJS' => 'ЅМ',
663 'TMT' => 'm',
664 'TND' => 'د.ت',
665 'TOP' => 'T$',
666 'TRY' => '₺',
667 'TTD' => '$',
668 'TWD' => 'NT$',
669 'TZS' => 'Sh',
670 'UAH' => '₴',
671 'UGX' => 'UGX',
672 'USD' => '$',
673 'UYU' => '$',
674 'UZS' => 'UZS',
675 'VEF' => 'Bs F',
676 'VES' => 'Bs.S',
677 'VND' => '₫',
678 'VUV' => 'Vt',
679 'WST' => 'T',
680 'XAF' => 'CFA',
681 'XCD' => '$',
682 'XOF' => 'CFA',
683 'XPF' => 'Fr',
684 'YER' => '﷼',
685 'ZAR' => 'R',
686 'ZMW' => 'ZK',
687 )
688 );
689 $currency_symbol = isset( $symbols[ $currency ] ) ? $symbols[ $currency ] : '';
690
691 return apply_filters( 'woocommerce_currency_symbol', $currency_symbol, $currency );
692}
693
694/**
695 * Send HTML emails from WooCommerce.
696 *
697 * @param mixed $to Receiver.
698 * @param mixed $subject Subject.
699 * @param mixed $message Message.
700 * @param string $headers Headers. (default: "Content-Type: text/html\r\n").
701 * @param string $attachments Attachments. (default: "").
702 * @return bool
703 */
704function wc_mail( $to, $subject, $message, $headers = "Content-Type: text/html\r\n", $attachments = '' ) {
705 $mailer = WC()->mailer();
706
707 return $mailer->send( $to, $subject, $message, $headers, $attachments );
708}
709
710/**
711 * Return "theme support" values from the current theme, if set.
712 *
713 * @since 3.3.0
714 * @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.
715 * @param mixed $default Optional value to return if the theme does not declare support for a prop.
716 * @return mixed Value of prop(s).
717 */
718function wc_get_theme_support( $prop = '', $default = null ) {
719 $theme_support = get_theme_support( 'woocommerce' );
720 $theme_support = is_array( $theme_support ) ? $theme_support[0] : false;
721
722 if ( ! $theme_support ) {
723 return $default;
724 }
725
726 if ( $prop ) {
727 $prop_stack = explode( '::', $prop );
728 $prop_key = array_shift( $prop_stack );
729
730 if ( isset( $theme_support[ $prop_key ] ) ) {
731 $value = $theme_support[ $prop_key ];
732
733 if ( count( $prop_stack ) ) {
734 foreach ( $prop_stack as $prop_key ) {
735 if ( is_array( $value ) && isset( $value[ $prop_key ] ) ) {
736 $value = $value[ $prop_key ];
737 } else {
738 $value = $default;
739 break;
740 }
741 }
742 }
743 } else {
744 $value = $default;
745 }
746
747 return $value;
748 }
749
750 return $theme_support;
751}
752
753/**
754 * Get an image size by name or defined dimensions.
755 *
756 * The returned variable is filtered by woocommerce_get_image_size_{image_size} filter to
757 * allow 3rd party customisation.
758 *
759 * Sizes defined by the theme take priority over settings. Settings are hidden when a theme
760 * defines sizes.
761 *
762 * @param array|string $image_size Name of the image size to get, or an array of dimensions.
763 * @return array Array of dimensions including width, height, and cropping mode. Cropping mode is 0 for no crop, and 1 for hard crop.
764 */
765function wc_get_image_size( $image_size ) {
766 $cache_key = 'size-' . ( is_array( $image_size ) ? implode( '-', $image_size ) : $image_size );
767 $size = wp_cache_get( $cache_key, 'woocommerce' );
768
769 if ( $size ) {
770 return $size;
771 }
772
773 $size = array(
774 'width' => 600,
775 'height' => 600,
776 'crop' => 1,
777 );
778
779 if ( is_array( $image_size ) ) {
780 $size = array(
781 'width' => isset( $image_size[0] ) ? absint( $image_size[0] ) : 600,
782 'height' => isset( $image_size[1] ) ? absint( $image_size[1] ) : 600,
783 'crop' => isset( $image_size[2] ) ? absint( $image_size[2] ) : 1,
784 );
785 $image_size = $size['width'] . '_' . $size['height'];
786 } else {
787 $image_size = str_replace( 'woocommerce_', '', $image_size );
788
789 // Legacy size mapping.
790 if ( 'shop_single' === $image_size ) {
791 $image_size = 'single';
792 } elseif ( 'shop_catalog' === $image_size ) {
793 $image_size = 'thumbnail';
794 } elseif ( 'shop_thumbnail' === $image_size ) {
795 $image_size = 'gallery_thumbnail';
796 }
797
798 if ( 'single' === $image_size ) {
799 $size['width'] = absint( wc_get_theme_support( 'single_image_width', get_option( 'woocommerce_single_image_width', 600 ) ) );
800 $size['height'] = '';
801 $size['crop'] = 0;
802
803 } elseif ( 'gallery_thumbnail' === $image_size ) {
804 $size['width'] = absint( wc_get_theme_support( 'gallery_thumbnail_image_width', 100 ) );
805 $size['height'] = $size['width'];
806 $size['crop'] = 1;
807
808 } elseif ( 'thumbnail' === $image_size ) {
809 $size['width'] = absint( wc_get_theme_support( 'thumbnail_image_width', get_option( 'woocommerce_thumbnail_image_width', 300 ) ) );
810 $cropping = get_option( 'woocommerce_thumbnail_cropping', '1:1' );
811
812 if ( 'uncropped' === $cropping ) {
813 $size['height'] = '';
814 $size['crop'] = 0;
815 } elseif ( 'custom' === $cropping ) {
816 $width = max( 1, get_option( 'woocommerce_thumbnail_cropping_custom_width', '4' ) );
817 $height = max( 1, get_option( 'woocommerce_thumbnail_cropping_custom_height', '3' ) );
818 $size['height'] = absint( round( ( $size['width'] / $width ) * $height ) );
819 $size['crop'] = 1;
820 } else {
821 $cropping_split = explode( ':', $cropping );
822 $width = max( 1, current( $cropping_split ) );
823 $height = max( 1, end( $cropping_split ) );
824 $size['height'] = absint( round( ( $size['width'] / $width ) * $height ) );
825 $size['crop'] = 1;
826 }
827 }
828 }
829
830 $size = apply_filters( 'woocommerce_get_image_size_' . $image_size, $size );
831
832 wp_cache_set( $cache_key, $size, 'woocommerce' );
833
834 return $size;
835}
836
837/**
838 * Queue some JavaScript code to be output in the footer.
839 *
840 * @param string $code Code.
841 */
842function wc_enqueue_js( $code ) {
843 global $wc_queued_js;
844
845 if ( empty( $wc_queued_js ) ) {
846 $wc_queued_js = '';
847 }
848
849 $wc_queued_js .= "\n" . $code . "\n";
850}
851
852/**
853 * Output any queued javascript code in the footer.
854 */
855function wc_print_js() {
856 global $wc_queued_js;
857
858 if ( ! empty( $wc_queued_js ) ) {
859 // Sanitize.
860 $wc_queued_js = wp_check_invalid_utf8( $wc_queued_js );
861 $wc_queued_js = preg_replace( '/&#(x)?0*(?(1)27|39);?/i', "'", $wc_queued_js );
862 $wc_queued_js = str_replace( "\r", '', $wc_queued_js );
863
864 $js = "<!-- WooCommerce JavaScript -->\n<script type=\"text/javascript\">\njQuery(function($) { $wc_queued_js });\n</script>\n";
865
866 /**
867 * Queued jsfilter.
868 *
869 * @since 2.6.0
870 * @param string $js JavaScript code.
871 */
872 echo apply_filters( 'woocommerce_queued_js', $js ); // WPCS: XSS ok.
873
874 unset( $wc_queued_js );
875 }
876}
877
878/**
879 * Set a cookie - wrapper for setcookie using WP constants.
880 *
881 * @param string $name Name of the cookie being set.
882 * @param string $value Value of the cookie.
883 * @param integer $expire Expiry of the cookie.
884 * @param bool $secure Whether the cookie should be served only over https.
885 * @param bool $httponly Whether the cookie is only accessible over HTTP, not scripting languages like JavaScript. @since 3.6.0.
886 */
887function wc_setcookie( $name, $value, $expire = 0, $secure = false, $httponly = false ) {
888 if ( ! headers_sent() ) {
889 setcookie( $name, $value, $expire, COOKIEPATH ? COOKIEPATH : '/', COOKIE_DOMAIN, $secure, apply_filters( 'woocommerce_cookie_httponly', $httponly, $name, $value, $expire, $secure ) );
890 } elseif ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
891 headers_sent( $file, $line );
892 trigger_error( "{$name} cookie cannot be set - headers already sent by {$file} on line {$line}", E_USER_NOTICE ); // @codingStandardsIgnoreLine
893 }
894}
895
896/**
897 * Get the URL to the WooCommerce REST API.
898 *
899 * @since 2.1
900 * @param string $path an endpoint to include in the URL.
901 * @return string the URL.
902 */
903function get_woocommerce_api_url( $path ) {
904 $version = defined( 'WC_API_REQUEST_VERSION' ) ? WC_API_REQUEST_VERSION : substr( WC_API::VERSION, 0, 1 );
905
906 $url = get_home_url( null, "wc-api/v{$version}/", is_ssl() ? 'https' : 'http' );
907
908 if ( ! empty( $path ) && is_string( $path ) ) {
909 $url .= ltrim( $path, '/' );
910 }
911
912 return $url;
913}
914
915/**
916 * Get a log file path.
917 *
918 * @since 2.2
919 *
920 * @param string $handle name.
921 * @return string the log file path.
922 */
923function wc_get_log_file_path( $handle ) {
924 return WC_Log_Handler_File::get_log_file_path( $handle );
925}
926
927/**
928 * Get a log file name.
929 *
930 * @since 3.3
931 *
932 * @param string $handle Name.
933 * @return string The log file name.
934 */
935function wc_get_log_file_name( $handle ) {
936 return WC_Log_Handler_File::get_log_file_name( $handle );
937}
938
939/**
940 * Recursively get page children.
941 *
942 * @param int $page_id Page ID.
943 * @return int[]
944 */
945function wc_get_page_children( $page_id ) {
946 $page_ids = get_posts(
947 array(
948 'post_parent' => $page_id,
949 'post_type' => 'page',
950 'numberposts' => -1, // @codingStandardsIgnoreLine
951 'post_status' => 'any',
952 'fields' => 'ids',
953 )
954 );
955
956 if ( ! empty( $page_ids ) ) {
957 foreach ( $page_ids as $page_id ) {
958 $page_ids = array_merge( $page_ids, wc_get_page_children( $page_id ) );
959 }
960 }
961
962 return $page_ids;
963}
964
965/**
966 * Flushes rewrite rules when the shop page (or it's children) gets saved.
967 */
968function flush_rewrite_rules_on_shop_page_save() {
969 $screen = get_current_screen();
970 $screen_id = $screen ? $screen->id : '';
971
972 // Check if this is the edit page.
973 if ( 'page' !== $screen_id ) {
974 return;
975 }
976
977 // Check if page is edited.
978 if ( empty( $_GET['post'] ) || empty( $_GET['action'] ) || ( isset( $_GET['action'] ) && 'edit' !== $_GET['action'] ) ) { // WPCS: input var ok, CSRF ok.
979 return;
980 }
981
982 $post_id = intval( $_GET['post'] ); // WPCS: input var ok, CSRF ok.
983 $shop_page_id = wc_get_page_id( 'shop' );
984
985 if ( $shop_page_id === $post_id || in_array( $post_id, wc_get_page_children( $shop_page_id ), true ) ) {
986 do_action( 'woocommerce_flush_rewrite_rules' );
987 }
988}
989add_action( 'admin_footer', 'flush_rewrite_rules_on_shop_page_save' );
990
991/**
992 * Various rewrite rule fixes.
993 *
994 * @since 2.2
995 * @param array $rules Rules.
996 * @return array
997 */
998function wc_fix_rewrite_rules( $rules ) {
999 global $wp_rewrite;
1000
1001 $permalinks = wc_get_permalink_structure();
1002
1003 // Fix the rewrite rules when the product permalink have %product_cat% flag.
1004 if ( preg_match( '`/(.+)(/%product_cat%)`', $permalinks['product_rewrite_slug'], $matches ) ) {
1005 foreach ( $rules as $rule => $rewrite ) {
1006 if ( preg_match( '`^' . preg_quote( $matches[1], '`' ) . '/\(`', $rule ) && preg_match( '/^(index\.php\?product_cat)(?!(.*product))/', $rewrite ) ) {
1007 unset( $rules[ $rule ] );
1008 }
1009 }
1010 }
1011
1012 // If the shop page is used as the base, we need to handle shop page subpages to avoid 404s.
1013 if ( ! $permalinks['use_verbose_page_rules'] ) {
1014 return $rules;
1015 }
1016
1017 $shop_page_id = wc_get_page_id( 'shop' );
1018 if ( $shop_page_id ) {
1019 $page_rewrite_rules = array();
1020 $subpages = wc_get_page_children( $shop_page_id );
1021
1022 // Subpage rules.
1023 foreach ( $subpages as $subpage ) {
1024 $uri = get_page_uri( $subpage );
1025 $page_rewrite_rules[ $uri . '/?$' ] = 'index.php?pagename=' . $uri;
1026 $wp_generated_rewrite_rules = $wp_rewrite->generate_rewrite_rules( $uri, EP_PAGES, true, true, false, false );
1027 foreach ( $wp_generated_rewrite_rules as $key => $value ) {
1028 $wp_generated_rewrite_rules[ $key ] = $value . '&pagename=' . $uri;
1029 }
1030 $page_rewrite_rules = array_merge( $page_rewrite_rules, $wp_generated_rewrite_rules );
1031 }
1032
1033 // Merge with rules.
1034 $rules = array_merge( $page_rewrite_rules, $rules );
1035 }
1036
1037 return $rules;
1038}
1039add_filter( 'rewrite_rules_array', 'wc_fix_rewrite_rules' );
1040
1041/**
1042 * Prevent product attachment links from breaking when using complex rewrite structures.
1043 *
1044 * @param string $link Link.
1045 * @param int $post_id Post ID.
1046 * @return string
1047 */
1048function wc_fix_product_attachment_link( $link, $post_id ) {
1049 $parent_type = get_post_type( wp_get_post_parent_id( $post_id ) );
1050 if ( 'product' === $parent_type || 'product_variation' === $parent_type ) {
1051 $link = home_url( '/?attachment_id=' . $post_id );
1052 }
1053 return $link;
1054}
1055add_filter( 'attachment_link', 'wc_fix_product_attachment_link', 10, 2 );
1056
1057/**
1058 * Protect downloads from ms-files.php in multisite.
1059 *
1060 * @param string $rewrite rewrite rules.
1061 * @return string
1062 */
1063function wc_ms_protect_download_rewite_rules( $rewrite ) {
1064 if ( ! is_multisite() || 'redirect' === get_option( 'woocommerce_file_download_method' ) ) {
1065 return $rewrite;
1066 }
1067
1068 $rule = "\n# WooCommerce Rules - Protect Files from ms-files.php\n\n";
1069 $rule .= "<IfModule mod_rewrite.c>\n";
1070 $rule .= "RewriteEngine On\n";
1071 $rule .= "RewriteCond %{QUERY_STRING} file=woocommerce_uploads/ [NC]\n";
1072 $rule .= "RewriteRule /ms-files.php$ - [F]\n";
1073 $rule .= "</IfModule>\n\n";
1074
1075 return $rule . $rewrite;
1076}
1077add_filter( 'mod_rewrite_rules', 'wc_ms_protect_download_rewite_rules' );
1078
1079/**
1080 * Formats a string in the format COUNTRY:STATE into an array.
1081 *
1082 * @since 2.3.0
1083 * @param string $country_string Country string.
1084 * @return array
1085 */
1086function wc_format_country_state_string( $country_string ) {
1087 if ( strstr( $country_string, ':' ) ) {
1088 list( $country, $state ) = explode( ':', $country_string );
1089 } else {
1090 $country = $country_string;
1091 $state = '';
1092 }
1093 return array(
1094 'country' => $country,
1095 'state' => $state,
1096 );
1097}
1098
1099/**
1100 * Get the store's base location.
1101 *
1102 * @since 2.3.0
1103 * @return array
1104 */
1105function wc_get_base_location() {
1106 $default = apply_filters( 'woocommerce_get_base_location', get_option( 'woocommerce_default_country' ) );
1107
1108 return wc_format_country_state_string( $default );
1109}
1110
1111/**
1112 * Get the customer's default location.
1113 *
1114 * Filtered, and set to base location or left blank. If cache-busting,
1115 * this should only be used when 'location' is set in the querystring.
1116 *
1117 * @since 2.3.0
1118 * @return array
1119 */
1120function wc_get_customer_default_location() {
1121 $set_default_location_to = get_option( 'woocommerce_default_customer_address', 'base' );
1122 $default_location = '' === $set_default_location_to ? '' : get_option( 'woocommerce_default_country', '' );
1123 $location = wc_format_country_state_string( apply_filters( 'woocommerce_customer_default_location', $default_location ) );
1124
1125 // Geolocation takes priority if used and if geolocation is possible.
1126 if ( 'geolocation' === $set_default_location_to || 'geolocation_ajax' === $set_default_location_to ) {
1127 $ua = wc_get_user_agent();
1128
1129 // Exclude common bots from geolocation by user agent.
1130 if ( ! stristr( $ua, 'bot' ) && ! stristr( $ua, 'spider' ) && ! stristr( $ua, 'crawl' ) ) {
1131 $geolocation = WC_Geolocation::geolocate_ip( '', true, false );
1132
1133 if ( ! empty( $geolocation['country'] ) ) {
1134 $location = $geolocation;
1135 }
1136 }
1137 }
1138
1139 // Once we have a location, ensure it's valid, otherwise fallback to a valid location.
1140 $allowed_country_codes = WC()->countries->get_allowed_countries();
1141
1142 if ( ! empty( $location['country'] ) && ! array_key_exists( $location['country'], $allowed_country_codes ) ) {
1143 $location['country'] = current( array_keys( $allowed_country_codes ) );
1144 $location['state'] = '';
1145 }
1146
1147 return apply_filters( 'woocommerce_customer_default_location_array', $location );
1148}
1149
1150/**
1151 * Get user agent string.
1152 *
1153 * @since 3.0.0
1154 * @return string
1155 */
1156function wc_get_user_agent() {
1157 return isset( $_SERVER['HTTP_USER_AGENT'] ) ? wc_clean( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : ''; // @codingStandardsIgnoreLine
1158}
1159
1160// This function can be removed when WP 3.9.2 or greater is required.
1161if ( ! function_exists( 'hash_equals' ) ) :
1162 /**
1163 * Compare two strings in constant time.
1164 *
1165 * This function was added in PHP 5.6.
1166 * It can leak the length of a string.
1167 *
1168 * @since 3.9.2
1169 *
1170 * @param string $a Expected string.
1171 * @param string $b Actual string.
1172 * @return bool Whether strings are equal.
1173 */
1174 function hash_equals( $a, $b ) {
1175 $a_length = strlen( $a );
1176 if ( strlen( $b ) !== $a_length ) {
1177 return false;
1178 }
1179 $result = 0;
1180
1181 // Do not attempt to "optimize" this.
1182 for ( $i = 0; $i < $a_length; $i++ ) {
1183 $result |= ord( $a[ $i ] ) ^ ord( $b[ $i ] );
1184 }
1185
1186 return 0 === $result;
1187 }
1188endif;
1189
1190/**
1191 * Generate a rand hash.
1192 *
1193 * @since 2.4.0
1194 * @return string
1195 */
1196function wc_rand_hash() {
1197 if ( ! function_exists( 'openssl_random_pseudo_bytes' ) ) {
1198 return sha1( wp_rand() );
1199 }
1200
1201 return bin2hex( openssl_random_pseudo_bytes( 20 ) ); // @codingStandardsIgnoreLine
1202}
1203
1204/**
1205 * WC API - Hash.
1206 *
1207 * @since 2.4.0
1208 * @param string $data Message to be hashed.
1209 * @return string
1210 */
1211function wc_api_hash( $data ) {
1212 return hash_hmac( 'sha256', $data, 'wc-api' );
1213}
1214
1215/**
1216 * Find all possible combinations of values from the input array and return in a logical order.
1217 *
1218 * @since 2.5.0
1219 * @param array $input Input.
1220 * @return array
1221 */
1222function wc_array_cartesian( $input ) {
1223 $input = array_filter( $input );
1224 $results = array();
1225 $indexes = array();
1226 $index = 0;
1227
1228 // Generate indexes from keys and values so we have a logical sort order.
1229 foreach ( $input as $key => $values ) {
1230 foreach ( $values as $value ) {
1231 $indexes[ $key ][ $value ] = $index++;
1232 }
1233 }
1234
1235 // Loop over the 2D array of indexes and generate all combinations.
1236 foreach ( $indexes as $key => $values ) {
1237 // When result is empty, fill with the values of the first looped array.
1238 if ( empty( $results ) ) {
1239 foreach ( $values as $value ) {
1240 $results[] = array( $key => $value );
1241 }
1242 } else {
1243 // Second and subsequent input sub-array merging.
1244 foreach ( $results as $result_key => $result ) {
1245 foreach ( $values as $value ) {
1246 // If the key is not set, we can set it.
1247 if ( ! isset( $results[ $result_key ][ $key ] ) ) {
1248 $results[ $result_key ][ $key ] = $value;
1249 } else {
1250 // If the key is set, we can add a new combination to the results array.
1251 $new_combination = $results[ $result_key ];
1252 $new_combination[ $key ] = $value;
1253 $results[] = $new_combination;
1254 }
1255 }
1256 }
1257 }
1258 }
1259
1260 // Sort the indexes.
1261 arsort( $results );
1262
1263 // Convert indexes back to values.
1264 foreach ( $results as $result_key => $result ) {
1265 $converted_values = array();
1266
1267 // Sort the values.
1268 arsort( $results[ $result_key ] );
1269
1270 // Convert the values.
1271 foreach ( $results[ $result_key ] as $key => $value ) {
1272 $converted_values[ $key ] = array_search( $value, $indexes[ $key ], true );
1273 }
1274
1275 $results[ $result_key ] = $converted_values;
1276 }
1277
1278 return $results;
1279}
1280
1281/**
1282 * Run a MySQL transaction query, if supported.
1283 *
1284 * @since 2.5.0
1285 * @param string $type Types: start (default), commit, rollback.
1286 * @param bool $force use of transactions.
1287 */
1288function wc_transaction_query( $type = 'start', $force = false ) {
1289 global $wpdb;
1290
1291 $wpdb->hide_errors();
1292
1293 wc_maybe_define_constant( 'WC_USE_TRANSACTIONS', true );
1294
1295 if ( WC_USE_TRANSACTIONS || $force ) {
1296 switch ( $type ) {
1297 case 'commit':
1298 $wpdb->query( 'COMMIT' );
1299 break;
1300 case 'rollback':
1301 $wpdb->query( 'ROLLBACK' );
1302 break;
1303 default:
1304 $wpdb->query( 'START TRANSACTION' );
1305 break;
1306 }
1307 }
1308}
1309
1310/**
1311 * Gets the url to the cart page.
1312 *
1313 * @since 2.5.0
1314 *
1315 * @return string Url to cart page
1316 */
1317function wc_get_cart_url() {
1318 return apply_filters( 'woocommerce_get_cart_url', wc_get_page_permalink( 'cart' ) );
1319}
1320
1321/**
1322 * Gets the url to the checkout page.
1323 *
1324 * @since 2.5.0
1325 *
1326 * @return string Url to checkout page
1327 */
1328function wc_get_checkout_url() {
1329 $checkout_url = wc_get_page_permalink( 'checkout' );
1330 if ( $checkout_url ) {
1331 // Force SSL if needed.
1332 if ( is_ssl() || 'yes' === get_option( 'woocommerce_force_ssl_checkout' ) ) {
1333 $checkout_url = str_replace( 'http:', 'https:', $checkout_url );
1334 }
1335 }
1336
1337 return apply_filters( 'woocommerce_get_checkout_url', $checkout_url );
1338}
1339
1340/**
1341 * Register a shipping method.
1342 *
1343 * @since 1.5.7
1344 * @param string|object $shipping_method class name (string) or a class object.
1345 */
1346function woocommerce_register_shipping_method( $shipping_method ) {
1347 WC()->shipping()->register_shipping_method( $shipping_method );
1348}
1349
1350if ( ! function_exists( 'wc_get_shipping_zone' ) ) {
1351 /**
1352 * Get the shipping zone matching a given package from the cart.
1353 *
1354 * @since 2.6.0
1355 * @uses WC_Shipping_Zones::get_zone_matching_package
1356 * @param array $package Shipping package.
1357 * @return WC_Shipping_Zone
1358 */
1359 function wc_get_shipping_zone( $package ) {
1360 return WC_Shipping_Zones::get_zone_matching_package( $package );
1361 }
1362}
1363
1364/**
1365 * Get a nice name for credit card providers.
1366 *
1367 * @since 2.6.0
1368 * @param string $type Provider Slug/Type.
1369 * @return string
1370 */
1371function wc_get_credit_card_type_label( $type ) {
1372 // Normalize.
1373 $type = strtolower( $type );
1374 $type = str_replace( '-', ' ', $type );
1375 $type = str_replace( '_', ' ', $type );
1376
1377 $labels = apply_filters(
1378 'woocommerce_credit_card_type_labels',
1379 array(
1380 'mastercard' => __( 'MasterCard', 'woocommerce' ),
1381 'visa' => __( 'Visa', 'woocommerce' ),
1382 'discover' => __( 'Discover', 'woocommerce' ),
1383 'american express' => __( 'American Express', 'woocommerce' ),
1384 'diners' => __( 'Diners', 'woocommerce' ),
1385 'jcb' => __( 'JCB', 'woocommerce' ),
1386 )
1387 );
1388
1389 return apply_filters( 'woocommerce_get_credit_card_type_label', ( array_key_exists( $type, $labels ) ? $labels[ $type ] : ucfirst( $type ) ) );
1390}
1391
1392/**
1393 * Outputs a "back" link so admin screens can easily jump back a page.
1394 *
1395 * @param string $label Title of the page to return to.
1396 * @param string $url URL of the page to return to.
1397 */
1398function wc_back_link( $label, $url ) {
1399 echo '<small class="wc-admin-breadcrumb"><a href="' . esc_url( $url ) . '" aria-label="' . esc_attr( $label ) . '">⤴</a></small>';
1400}
1401
1402/**
1403 * Display a WooCommerce help tip.
1404 *
1405 * @since 2.5.0
1406 *
1407 * @param string $tip Help tip text.
1408 * @param bool $allow_html Allow sanitized HTML if true or escape.
1409 * @return string
1410 */
1411function wc_help_tip( $tip, $allow_html = false ) {
1412 if ( $allow_html ) {
1413 $tip = wc_sanitize_tooltip( $tip );
1414 } else {
1415 $tip = esc_attr( $tip );
1416 }
1417
1418 return '<span class="woocommerce-help-tip" data-tip="' . $tip . '"></span>';
1419}
1420
1421/**
1422 * Return a list of potential postcodes for wildcard searching.
1423 *
1424 * @since 2.6.0
1425 * @param string $postcode Postcode.
1426 * @param string $country Country to format postcode for matching.
1427 * @return string[]
1428 */
1429function wc_get_wildcard_postcodes( $postcode, $country = '' ) {
1430 $formatted_postcode = wc_format_postcode( $postcode, $country );
1431 $length = function_exists( 'mb_strlen' ) ? mb_strlen( $formatted_postcode ) : strlen( $formatted_postcode );
1432 $postcodes = array(
1433 $postcode,
1434 $formatted_postcode,
1435 $formatted_postcode . '*',
1436 );
1437
1438 for ( $i = 0; $i < $length; $i ++ ) {
1439 $postcodes[] = ( function_exists( 'mb_substr' ) ? mb_substr( $formatted_postcode, 0, ( $i + 1 ) * -1 ) : substr( $formatted_postcode, 0, ( $i + 1 ) * -1 ) ) . '*';
1440 }
1441
1442 return $postcodes;
1443}
1444
1445/**
1446 * Used by shipping zones and taxes to compare a given $postcode to stored
1447 * postcodes to find matches for numerical ranges, and wildcards.
1448 *
1449 * @since 2.6.0
1450 * @param string $postcode Postcode you want to match against stored postcodes.
1451 * @param array $objects Array of postcode objects from Database.
1452 * @param string $object_id_key DB column name for the ID.
1453 * @param string $object_compare_key DB column name for the value.
1454 * @param string $country Country from which this postcode belongs. Allows for formatting.
1455 * @return array Array of matching object ID and matching values.
1456 */
1457function wc_postcode_location_matcher( $postcode, $objects, $object_id_key, $object_compare_key, $country = '' ) {
1458 $postcode = wc_normalize_postcode( $postcode );
1459 $wildcard_postcodes = array_map( 'wc_clean', wc_get_wildcard_postcodes( $postcode, $country ) );
1460 $matches = array();
1461
1462 foreach ( $objects as $object ) {
1463 $object_id = $object->$object_id_key;
1464 $compare_against = $object->$object_compare_key;
1465
1466 // Handle postcodes containing ranges.
1467 if ( strstr( $compare_against, '...' ) ) {
1468 $range = array_map( 'trim', explode( '...', $compare_against ) );
1469
1470 if ( 2 !== count( $range ) ) {
1471 continue;
1472 }
1473
1474 list( $min, $max ) = $range;
1475
1476 // If the postcode is non-numeric, make it numeric.
1477 if ( ! is_numeric( $min ) || ! is_numeric( $max ) ) {
1478 $compare = wc_make_numeric_postcode( $postcode );
1479 $min = str_pad( wc_make_numeric_postcode( $min ), strlen( $compare ), '0' );
1480 $max = str_pad( wc_make_numeric_postcode( $max ), strlen( $compare ), '0' );
1481 } else {
1482 $compare = $postcode;
1483 }
1484
1485 if ( $compare >= $min && $compare <= $max ) {
1486 $matches[ $object_id ] = isset( $matches[ $object_id ] ) ? $matches[ $object_id ] : array();
1487 $matches[ $object_id ][] = $compare_against;
1488 }
1489 } elseif ( in_array( $compare_against, $wildcard_postcodes, true ) ) {
1490 // Wildcard and standard comparison.
1491 $matches[ $object_id ] = isset( $matches[ $object_id ] ) ? $matches[ $object_id ] : array();
1492 $matches[ $object_id ][] = $compare_against;
1493 }
1494 }
1495
1496 return $matches;
1497}
1498
1499/**
1500 * Gets number of shipping methods currently enabled. Used to identify if
1501 * shipping is configured.
1502 *
1503 * @since 2.6.0
1504 * @param bool $include_legacy Count legacy shipping methods too.
1505 * @return int
1506 */
1507function wc_get_shipping_method_count( $include_legacy = false ) {
1508 global $wpdb;
1509
1510 $transient_name = $include_legacy ? 'wc_shipping_method_count_legacy' : 'wc_shipping_method_count';
1511 $transient_version = WC_Cache_Helper::get_transient_version( 'shipping' );
1512 $transient_value = get_transient( $transient_name );
1513
1514 if ( isset( $transient_value['value'], $transient_value['version'] ) && $transient_value['version'] === $transient_version ) {
1515 return absint( $transient_value['value'] );
1516 }
1517
1518 $method_count = absint( $wpdb->get_var( "SELECT COUNT(*) FROM {$wpdb->prefix}woocommerce_shipping_zone_methods" ) );
1519
1520 if ( $include_legacy ) {
1521 // Count activated methods that don't support shipping zones.
1522 $methods = WC()->shipping()->get_shipping_methods();
1523
1524 foreach ( $methods as $method ) {
1525 if ( isset( $method->enabled ) && 'yes' === $method->enabled && ! $method->supports( 'shipping-zones' ) ) {
1526 $method_count++;
1527 }
1528 }
1529 }
1530
1531 $transient_value = array(
1532 'version' => $transient_version,
1533 'value' => $method_count,
1534 );
1535
1536 set_transient( $transient_name, $transient_value, DAY_IN_SECONDS * 30 );
1537
1538 return $method_count;
1539}
1540
1541/**
1542 * Wrapper for set_time_limit to see if it is enabled.
1543 *
1544 * @since 2.6.0
1545 * @param int $limit Time limit.
1546 */
1547function wc_set_time_limit( $limit = 0 ) {
1548 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
1549 @set_time_limit( $limit ); // @codingStandardsIgnoreLine
1550 }
1551}
1552
1553/**
1554 * Wrapper for nocache_headers which also disables page caching.
1555 *
1556 * @since 3.2.4
1557 */
1558function wc_nocache_headers() {
1559 WC_Cache_Helper::set_nocache_constants();
1560 nocache_headers();
1561}
1562
1563/**
1564 * Used to sort products attributes with uasort.
1565 *
1566 * @since 2.6.0
1567 * @param array $a First attribute to compare.
1568 * @param array $b Second attribute to compare.
1569 * @return int
1570 */
1571function wc_product_attribute_uasort_comparison( $a, $b ) {
1572 return wc_uasort_comparison( $a['position'], $b['position'] );
1573}
1574
1575/**
1576 * Used to sort shipping zone methods with uasort.
1577 *
1578 * @since 3.0.0
1579 * @param array $a First shipping zone method to compare.
1580 * @param array $b Second shipping zone method to compare.
1581 * @return int
1582 */
1583function wc_shipping_zone_method_order_uasort_comparison( $a, $b ) {
1584 return wc_uasort_comparison( $a->method_order, $b->method_order );
1585}
1586
1587/**
1588 * User to sort checkout fields based on priority with uasort.
1589 *
1590 * @since 3.5.1
1591 * @param array $a First field to compare.
1592 * @param array $b Second field to compare.
1593 * @return int
1594 */
1595function wc_checkout_fields_uasort_comparison( $a, $b ) {
1596 /*
1597 * We are not guaranteed to get a priority
1598 * setting. So don't compare if they don't
1599 * exist.
1600 */
1601 if ( ! isset( $a['priority'], $b['priority'] ) ) {
1602 return 0;
1603 }
1604
1605 return wc_uasort_comparison( $a['priority'], $b['priority'] );
1606}
1607
1608/**
1609 * User to sort two values with ausort.
1610 *
1611 * @since 3.5.1
1612 * @param int $a First value to compare.
1613 * @param int $b Second value to compare.
1614 * @return int
1615 */
1616function wc_uasort_comparison( $a, $b ) {
1617 if ( $a === $b ) {
1618 return 0;
1619 }
1620 return ( $a < $b ) ? -1 : 1;
1621}
1622
1623/**
1624 * Sort values based on ascii, usefull for special chars in strings.
1625 *
1626 * @param string $a First value.
1627 * @param string $b Second value.
1628 * @return int
1629 */
1630function wc_ascii_uasort_comparison( $a, $b ) {
1631 if ( function_exists( 'iconv' ) && defined( 'ICONV_IMPL' ) && @strcasecmp( ICONV_IMPL, 'unknown' ) !== 0 ) {
1632 $a = @iconv( 'UTF-8', 'ASCII//TRANSLIT//IGNORE', $a );
1633 $b = @iconv( 'UTF-8', 'ASCII//TRANSLIT//IGNORE', $b );
1634 }
1635 return strcmp( $a, $b );
1636}
1637
1638/**
1639 * Get rounding mode for internal tax calculations.
1640 *
1641 * @since 3.2.4
1642 * @return int
1643 */
1644function wc_get_tax_rounding_mode() {
1645 $constant = WC_TAX_ROUNDING_MODE;
1646
1647 if ( 'auto' === $constant ) {
1648 return 'yes' === get_option( 'woocommerce_prices_include_tax', 'no' ) ? 2 : 1;
1649 }
1650
1651 return intval( $constant );
1652}
1653
1654/**
1655 * Get rounding precision for internal WC calculations.
1656 * Will increase the precision of wc_get_price_decimals by 2 decimals, unless WC_ROUNDING_PRECISION is set to a higher number.
1657 *
1658 * @since 2.6.3
1659 * @return int
1660 */
1661function wc_get_rounding_precision() {
1662 $precision = wc_get_price_decimals() + 2;
1663 if ( absint( WC_ROUNDING_PRECISION ) > $precision ) {
1664 $precision = absint( WC_ROUNDING_PRECISION );
1665 }
1666 return $precision;
1667}
1668
1669/**
1670 * Add precision to a number and return a number.
1671 *
1672 * @since 3.2.0
1673 * @param float $value Number to add precision to.
1674 * @param bool $round If should round after adding precision.
1675 * @return int|float
1676 */
1677function wc_add_number_precision( $value, $round = true ) {
1678 $cent_precision = pow( 10, wc_get_price_decimals() );
1679 $value = $value * $cent_precision;
1680 return $round ? round( $value, wc_get_rounding_precision() - wc_get_price_decimals() ) : $value;
1681}
1682
1683/**
1684 * Remove precision from a number and return a float.
1685 *
1686 * @since 3.2.0
1687 * @param float $value Number to add precision to.
1688 * @return float
1689 */
1690function wc_remove_number_precision( $value ) {
1691 $cent_precision = pow( 10, wc_get_price_decimals() );
1692 return $value / $cent_precision;
1693}
1694
1695/**
1696 * Add precision to an array of number and return an array of int.
1697 *
1698 * @since 3.2.0
1699 * @param array $value Number to add precision to.
1700 * @param bool $round Should we round after adding precision?.
1701 * @return int|array
1702 */
1703function wc_add_number_precision_deep( $value, $round = true ) {
1704 if ( ! is_array( $value ) ) {
1705 return wc_add_number_precision( $value, $round );
1706 }
1707
1708 foreach ( $value as $key => $sub_value ) {
1709 $value[ $key ] = wc_add_number_precision_deep( $sub_value, $round );
1710 }
1711
1712 return $value;
1713}
1714
1715/**
1716 * Remove precision from an array of number and return an array of int.
1717 *
1718 * @since 3.2.0
1719 * @param array $value Number to add precision to.
1720 * @return int|array
1721 */
1722function wc_remove_number_precision_deep( $value ) {
1723 if ( ! is_array( $value ) ) {
1724 return wc_remove_number_precision( $value );
1725 }
1726
1727 foreach ( $value as $key => $sub_value ) {
1728 $value[ $key ] = wc_remove_number_precision_deep( $sub_value );
1729 }
1730
1731 return $value;
1732}
1733
1734/**
1735 * Get a shared logger instance.
1736 *
1737 * Use the woocommerce_logging_class filter to change the logging class. You may provide one of the following:
1738 * - a class name which will be instantiated as `new $class` with no arguments
1739 * - an instance which will be used directly as the logger
1740 * In either case, the class or instance *must* implement WC_Logger_Interface.
1741 *
1742 * @see WC_Logger_Interface
1743 *
1744 * @return WC_Logger
1745 */
1746function wc_get_logger() {
1747 static $logger = null;
1748
1749 $class = apply_filters( 'woocommerce_logging_class', 'WC_Logger' );
1750
1751 if ( null !== $logger && is_string( $class ) && is_a( $logger, $class ) ) {
1752 return $logger;
1753 }
1754
1755 $implements = class_implements( $class );
1756
1757 if ( is_array( $implements ) && in_array( 'WC_Logger_Interface', $implements, true ) ) {
1758 $logger = is_object( $class ) ? $class : new $class();
1759 } else {
1760 wc_doing_it_wrong(
1761 __FUNCTION__,
1762 sprintf(
1763 /* translators: 1: class name 2: woocommerce_logging_class 3: WC_Logger_Interface */
1764 __( 'The class %1$s provided by %2$s filter must implement %3$s.', 'woocommerce' ),
1765 '<code>' . esc_html( is_object( $class ) ? get_class( $class ) : $class ) . '</code>',
1766 '<code>woocommerce_logging_class</code>',
1767 '<code>WC_Logger_Interface</code>'
1768 ),
1769 '3.0'
1770 );
1771
1772 $logger = is_a( $logger, 'WC_Logger' ) ? $logger : new WC_Logger();
1773 }
1774
1775 return $logger;
1776}
1777
1778/**
1779 * Trigger logging cleanup using the logging class.
1780 *
1781 * @since 3.4.0
1782 */
1783function wc_cleanup_logs() {
1784 $logger = wc_get_logger();
1785
1786 if ( is_callable( array( $logger, 'clear_expired_logs' ) ) ) {
1787 $logger->clear_expired_logs();
1788 }
1789}
1790add_action( 'woocommerce_cleanup_logs', 'wc_cleanup_logs' );
1791
1792/**
1793 * Prints human-readable information about a variable.
1794 *
1795 * Some server environments blacklist some debugging functions. This function provides a safe way to
1796 * turn an expression into a printable, readable form without calling blacklisted functions.
1797 *
1798 * @since 3.0
1799 *
1800 * @param mixed $expression The expression to be printed.
1801 * @param bool $return Optional. Default false. Set to true to return the human-readable string.
1802 * @return string|bool False if expression could not be printed. True if the expression was printed.
1803 * If $return is true, a string representation will be returned.
1804 */
1805function wc_print_r( $expression, $return = false ) {
1806 $alternatives = array(
1807 array(
1808 'func' => 'print_r',
1809 'args' => array( $expression, true ),
1810 ),
1811 array(
1812 'func' => 'var_export',
1813 'args' => array( $expression, true ),
1814 ),
1815 array(
1816 'func' => 'json_encode',
1817 'args' => array( $expression ),
1818 ),
1819 array(
1820 'func' => 'serialize',
1821 'args' => array( $expression ),
1822 ),
1823 );
1824
1825 $alternatives = apply_filters( 'woocommerce_print_r_alternatives', $alternatives, $expression );
1826
1827 foreach ( $alternatives as $alternative ) {
1828 if ( function_exists( $alternative['func'] ) ) {
1829 $res = $alternative['func']( ...$alternative['args'] );
1830 if ( $return ) {
1831 return $res;
1832 }
1833
1834 echo $res; // WPCS: XSS ok.
1835 return true;
1836 }
1837 }
1838
1839 return false;
1840}
1841
1842/**
1843 * Registers the default log handler.
1844 *
1845 * @since 3.0
1846 * @param array $handlers Handlers.
1847 * @return array
1848 */
1849function wc_register_default_log_handler( $handlers ) {
1850 if ( defined( 'WC_LOG_HANDLER' ) && class_exists( WC_LOG_HANDLER ) ) {
1851 $handler_class = WC_LOG_HANDLER;
1852 $default_handler = new $handler_class();
1853 } else {
1854 $default_handler = new WC_Log_Handler_File();
1855 }
1856
1857 array_push( $handlers, $default_handler );
1858
1859 return $handlers;
1860}
1861add_filter( 'woocommerce_register_log_handlers', 'wc_register_default_log_handler' );
1862
1863/**
1864 * Based on wp_list_pluck, this calls a method instead of returning a property.
1865 *
1866 * @since 3.0.0
1867 * @param array $list List of objects or arrays.
1868 * @param int|string $callback_or_field Callback method from the object to place instead of the entire object.
1869 * @param int|string $index_key Optional. Field from the object to use as keys for the new array.
1870 * Default null.
1871 * @return array Array of values.
1872 */
1873function wc_list_pluck( $list, $callback_or_field, $index_key = null ) {
1874 // Use wp_list_pluck if this isn't a callback.
1875 $first_el = current( $list );
1876 if ( ! is_object( $first_el ) || ! is_callable( array( $first_el, $callback_or_field ) ) ) {
1877 return wp_list_pluck( $list, $callback_or_field, $index_key );
1878 }
1879 if ( ! $index_key ) {
1880 /*
1881 * This is simple. Could at some point wrap array_column()
1882 * if we knew we had an array of arrays.
1883 */
1884 foreach ( $list as $key => $value ) {
1885 $list[ $key ] = $value->{$callback_or_field}();
1886 }
1887 return $list;
1888 }
1889
1890 /*
1891 * When index_key is not set for a particular item, push the value
1892 * to the end of the stack. This is how array_column() behaves.
1893 */
1894 $newlist = array();
1895 foreach ( $list as $value ) {
1896 // Get index. @since 3.2.0 this supports a callback.
1897 if ( is_callable( array( $value, $index_key ) ) ) {
1898 $newlist[ $value->{$index_key}() ] = $value->{$callback_or_field}();
1899 } elseif ( isset( $value->$index_key ) ) {
1900 $newlist[ $value->$index_key ] = $value->{$callback_or_field}();
1901 } else {
1902 $newlist[] = $value->{$callback_or_field}();
1903 }
1904 }
1905 return $newlist;
1906}
1907
1908/**
1909 * Get permalink settings for things like products and taxonomies.
1910 *
1911 * As of 3.3.0, the permalink settings are stored to the option instead of
1912 * being blank and inheritting from the locale. This speeds up page loading
1913 * times by negating the need to switch locales on each page load.
1914 *
1915 * This is more inline with WP core behavior which does not localize slugs.
1916 *
1917 * @since 3.0.0
1918 * @return array
1919 */
1920function wc_get_permalink_structure() {
1921 $saved_permalinks = (array) get_option( 'woocommerce_permalinks', array() );
1922 $permalinks = wp_parse_args(
1923 array_filter( $saved_permalinks ),
1924 array(
1925 'product_base' => _x( 'product', 'slug', 'woocommerce' ),
1926 'category_base' => _x( 'product-category', 'slug', 'woocommerce' ),
1927 'tag_base' => _x( 'product-tag', 'slug', 'woocommerce' ),
1928 'attribute_base' => '',
1929 'use_verbose_page_rules' => false,
1930 )
1931 );
1932
1933 if ( $saved_permalinks !== $permalinks ) {
1934 update_option( 'woocommerce_permalinks', $permalinks );
1935 }
1936
1937 $permalinks['product_rewrite_slug'] = untrailingslashit( $permalinks['product_base'] );
1938 $permalinks['category_rewrite_slug'] = untrailingslashit( $permalinks['category_base'] );
1939 $permalinks['tag_rewrite_slug'] = untrailingslashit( $permalinks['tag_base'] );
1940 $permalinks['attribute_rewrite_slug'] = untrailingslashit( $permalinks['attribute_base'] );
1941
1942 return $permalinks;
1943}
1944
1945/**
1946 * Switch WooCommerce to site language.
1947 *
1948 * @since 3.1.0
1949 */
1950function wc_switch_to_site_locale() {
1951 if ( function_exists( 'switch_to_locale' ) ) {
1952 switch_to_locale( get_locale() );
1953
1954 // Filter on plugin_locale so load_plugin_textdomain loads the correct locale.
1955 add_filter( 'plugin_locale', 'get_locale' );
1956
1957 // Init WC locale.
1958 WC()->load_plugin_textdomain();
1959 }
1960}
1961
1962/**
1963 * Switch WooCommerce language to original.
1964 *
1965 * @since 3.1.0
1966 */
1967function wc_restore_locale() {
1968 if ( function_exists( 'restore_previous_locale' ) ) {
1969 restore_previous_locale();
1970
1971 // Remove filter.
1972 remove_filter( 'plugin_locale', 'get_locale' );
1973
1974 // Init WC locale.
1975 WC()->load_plugin_textdomain();
1976 }
1977}
1978
1979/**
1980 * Convert plaintext phone number to clickable phone number.
1981 *
1982 * Remove formatting and allow "+".
1983 * Example and specs: https://developer.mozilla.org/en/docs/Web/HTML/Element/a#Creating_a_phone_link
1984 *
1985 * @since 3.1.0
1986 *
1987 * @param string $phone Content to convert phone number.
1988 * @return string Content with converted phone number.
1989 */
1990function wc_make_phone_clickable( $phone ) {
1991 $number = trim( preg_replace( '/[^\d|\+]/', '', $phone ) );
1992
1993 return $number ? '<a href="tel:' . esc_attr( $number ) . '">' . esc_html( $phone ) . '</a>' : '';
1994}
1995
1996/**
1997 * Get an item of post data if set, otherwise return a default value.
1998 *
1999 * @since 3.0.9
2000 * @param string $key Meta key.
2001 * @param string $default Default value.
2002 * @return mixed Value sanitized by wc_clean.
2003 */
2004function wc_get_post_data_by_key( $key, $default = '' ) {
2005 return wc_clean( wp_unslash( wc_get_var( $_POST[ $key ], $default ) ) ); // @codingStandardsIgnoreLine
2006}
2007
2008/**
2009 * Get data if set, otherwise return a default value or null. Prevents notices when data is not set.
2010 *
2011 * @since 3.2.0
2012 * @param mixed $var Variable.
2013 * @param string $default Default value.
2014 * @return mixed
2015 */
2016function wc_get_var( &$var, $default = null ) {
2017 return isset( $var ) ? $var : $default;
2018}
2019
2020/**
2021 * Read in WooCommerce headers when reading plugin headers.
2022 *
2023 * @since 3.2.0
2024 * @param array $headers Headers.
2025 * @return array
2026 */
2027function wc_enable_wc_plugin_headers( $headers ) {
2028 if ( ! class_exists( 'WC_Plugin_Updates' ) ) {
2029 include_once dirname( __FILE__ ) . '/admin/plugin-updates/class-wc-plugin-updates.php';
2030 }
2031
2032 // WC requires at least - allows developers to define which version of WooCommerce the plugin requires to run.
2033 $headers[] = WC_Plugin_Updates::VERSION_REQUIRED_HEADER;
2034
2035 // WC tested up to - allows developers to define which version of WooCommerce they have tested up to.
2036 $headers[] = WC_Plugin_Updates::VERSION_TESTED_HEADER;
2037
2038 // Woo - This is used in WooCommerce extensions and is picked up by the helper.
2039 $headers[] = 'Woo';
2040
2041 return $headers;
2042}
2043add_filter( 'extra_theme_headers', 'wc_enable_wc_plugin_headers' );
2044add_filter( 'extra_plugin_headers', 'wc_enable_wc_plugin_headers' );
2045
2046/**
2047 * Prevent auto-updating the WooCommerce plugin on major releases if there are untested extensions active.
2048 *
2049 * @since 3.2.0
2050 * @param bool $should_update If should update.
2051 * @param object $plugin Plugin data.
2052 * @return bool
2053 */
2054function wc_prevent_dangerous_auto_updates( $should_update, $plugin ) {
2055 if ( ! isset( $plugin->plugin, $plugin->new_version ) ) {
2056 return $should_update;
2057 }
2058
2059 if ( 'woocommerce/woocommerce.php' !== $plugin->plugin ) {
2060 return $should_update;
2061 }
2062
2063 if ( ! class_exists( 'WC_Plugin_Updates' ) ) {
2064 include_once dirname( __FILE__ ) . '/admin/plugin-updates/class-wc-plugin-updates.php';
2065 }
2066
2067 $new_version = wc_clean( $plugin->new_version );
2068 $plugin_updates = new WC_Plugin_Updates();
2069 $untested_plugins = $plugin_updates->get_untested_plugins( $new_version, 'major' );
2070 if ( ! empty( $untested_plugins ) ) {
2071 return false;
2072 }
2073
2074 return $should_update;
2075}
2076add_filter( 'auto_update_plugin', 'wc_prevent_dangerous_auto_updates', 99, 2 );
2077
2078/**
2079 * Delete expired transients.
2080 *
2081 * Deletes all expired transients. The multi-table delete syntax is used.
2082 * to delete the transient record from table a, and the corresponding.
2083 * transient_timeout record from table b.
2084 *
2085 * Based on code inside core's upgrade_network() function.
2086 *
2087 * @since 3.2.0
2088 * @return int Number of transients that were cleared.
2089 */
2090function wc_delete_expired_transients() {
2091 global $wpdb;
2092
2093 $sql = "DELETE a, b FROM $wpdb->options a, $wpdb->options b
2094 WHERE a.option_name LIKE %s
2095 AND a.option_name NOT LIKE %s
2096 AND b.option_name = CONCAT( '_transient_timeout_', SUBSTRING( a.option_name, 12 ) )
2097 AND b.option_value < %d";
2098 $rows = $wpdb->query( $wpdb->prepare( $sql, $wpdb->esc_like( '_transient_' ) . '%', $wpdb->esc_like( '_transient_timeout_' ) . '%', time() ) ); // WPCS: unprepared SQL ok.
2099
2100 $sql = "DELETE a, b FROM $wpdb->options a, $wpdb->options b
2101 WHERE a.option_name LIKE %s
2102 AND a.option_name NOT LIKE %s
2103 AND b.option_name = CONCAT( '_site_transient_timeout_', SUBSTRING( a.option_name, 17 ) )
2104 AND b.option_value < %d";
2105 $rows2 = $wpdb->query( $wpdb->prepare( $sql, $wpdb->esc_like( '_site_transient_' ) . '%', $wpdb->esc_like( '_site_transient_timeout_' ) . '%', time() ) ); // WPCS: unprepared SQL ok.
2106
2107 return absint( $rows + $rows2 );
2108}
2109add_action( 'woocommerce_installed', 'wc_delete_expired_transients' );
2110
2111/**
2112 * Make a URL relative, if possible.
2113 *
2114 * @since 3.2.0
2115 * @param string $url URL to make relative.
2116 * @return string
2117 */
2118function wc_get_relative_url( $url ) {
2119 return wc_is_external_resource( $url ) ? $url : str_replace( array( 'http://', 'https://' ), '//', $url );
2120}
2121
2122/**
2123 * See if a resource is remote.
2124 *
2125 * @since 3.2.0
2126 * @param string $url URL to check.
2127 * @return bool
2128 */
2129function wc_is_external_resource( $url ) {
2130 $wp_base = str_replace( array( 'http://', 'https://' ), '//', get_home_url( null, '/', 'http' ) );
2131
2132 return strstr( $url, '://' ) && ! strstr( $url, $wp_base );
2133}
2134
2135/**
2136 * See if theme/s is activate or not.
2137 *
2138 * @since 3.3.0
2139 * @param string|array $theme Theme name or array of theme names to check.
2140 * @return boolean
2141 */
2142function wc_is_active_theme( $theme ) {
2143 return is_array( $theme ) ? in_array( get_template(), $theme, true ) : get_template() === $theme;
2144}
2145
2146/**
2147 * Is the site using a default WP theme?
2148 *
2149 * @return boolean
2150 */
2151function wc_is_wp_default_theme_active() {
2152 return wc_is_active_theme(
2153 array(
2154 'twentytwenty',
2155 'twentynineteen',
2156 'twentyseventeen',
2157 'twentysixteen',
2158 'twentyfifteen',
2159 'twentyfourteen',
2160 'twentythirteen',
2161 'twentyeleven',
2162 'twentytwelve',
2163 'twentyten',
2164 )
2165 );
2166}
2167
2168/**
2169 * Cleans up session data - cron callback.
2170 *
2171 * @since 3.3.0
2172 */
2173function wc_cleanup_session_data() {
2174 $session_class = apply_filters( 'woocommerce_session_handler', 'WC_Session_Handler' );
2175 $session = new $session_class();
2176
2177 if ( is_callable( array( $session, 'cleanup_sessions' ) ) ) {
2178 $session->cleanup_sessions();
2179 }
2180}
2181add_action( 'woocommerce_cleanup_sessions', 'wc_cleanup_session_data' );
2182
2183/**
2184 * Convert a decimal (e.g. 3.5) to a fraction (e.g. 7/2).
2185 * From: https://www.designedbyaturtle.co.uk/2015/converting-a-decimal-to-a-fraction-in-php/
2186 *
2187 * @param float $decimal the decimal number.
2188 * @return array|bool a 1/2 would be [1, 2] array (this can be imploded with '/' to form a string).
2189 */
2190function wc_decimal_to_fraction( $decimal ) {
2191 if ( 0 > $decimal || ! is_numeric( $decimal ) ) {
2192 // Negative digits need to be passed in as positive numbers and prefixed as negative once the response is imploded.
2193 return false;
2194 }
2195
2196 if ( 0 === $decimal ) {
2197 return array( 0, 1 );
2198 }
2199
2200 $tolerance = 1.e-4;
2201 $numerator = 1;
2202 $h2 = 0;
2203 $denominator = 0;
2204 $k2 = 1;
2205 $b = 1 / $decimal;
2206
2207 do {
2208 $b = 1 / $b;
2209 $a = floor( $b );
2210 $aux = $numerator;
2211 $numerator = $a * $numerator + $h2;
2212 $h2 = $aux;
2213 $aux = $denominator;
2214 $denominator = $a * $denominator + $k2;
2215 $k2 = $aux;
2216 $b = $b - $a;
2217 } while ( abs( $decimal - $numerator / $denominator ) > $decimal * $tolerance );
2218
2219 return array( $numerator, $denominator );
2220}
2221
2222/**
2223 * Round discount.
2224 *
2225 * @param double $value Amount to round.
2226 * @param int $precision DP to round.
2227 * @return float
2228 */
2229function wc_round_discount( $value, $precision ) {
2230 if ( version_compare( PHP_VERSION, '5.3.0', '>=' ) ) {
2231 return round( $value, $precision, WC_DISCOUNT_ROUNDING_MODE ); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctionParameters.round_modeFound
2232 }
2233
2234 if ( 2 === WC_DISCOUNT_ROUNDING_MODE ) {
2235 return wc_legacy_round_half_down( $value, $precision );
2236 }
2237
2238 return round( $value, $precision );
2239}
2240
2241/**
2242 * Return the html selected attribute if stringified $value is found in array of stringified $options
2243 * or if stringified $value is the same as scalar stringified $options.
2244 *
2245 * @param string|int $value Value to find within options.
2246 * @param string|int|array $options Options to go through when looking for value.
2247 * @return string
2248 */
2249function wc_selected( $value, $options ) {
2250 if ( is_array( $options ) ) {
2251 $options = array_map( 'strval', $options );
2252 return selected( in_array( (string) $value, $options, true ), true, false );
2253 }
2254
2255 return selected( $value, $options, false );
2256}
2257
2258/**
2259 * Retrieves the MySQL server version. Based on $wpdb.
2260 *
2261 * @since 3.4.1
2262 * @return array Vesion information.
2263 */
2264function wc_get_server_database_version() {
2265 global $wpdb;
2266
2267 if ( empty( $wpdb->is_mysql ) ) {
2268 return array(
2269 'string' => '',
2270 'number' => '',
2271 );
2272 }
2273
2274 if ( $wpdb->use_mysqli ) {
2275 $server_info = mysqli_get_server_info( $wpdb->dbh ); // @codingStandardsIgnoreLine.
2276 } else {
2277 $server_info = mysql_get_server_info( $wpdb->dbh ); // @codingStandardsIgnoreLine.
2278 }
2279
2280 return array(
2281 'string' => $server_info,
2282 'number' => preg_replace( '/([^\d.]+).*/', '', $server_info ),
2283 );
2284}
2285
2286/**
2287 * Initialize and load the cart functionality.
2288 *
2289 * @since 3.6.4
2290 * @return void
2291 */
2292function wc_load_cart() {
2293 if ( ! did_action( 'before_woocommerce_init' ) || doing_action( 'before_woocommerce_init' ) ) {
2294 /* translators: 1: wc_load_cart 2: woocommerce_init */
2295 wc_doing_it_wrong( __FUNCTION__, sprintf( __( '%1$s should not be called before the %2$s action.', 'woocommerce' ), 'wc_load_cart', 'woocommerce_init' ), '3.7' );
2296 return;
2297 }
2298
2299 WC()->initialize_session();
2300 WC()->initialize_cart();
2301}