PluginProbe ʕ •ᴥ•ʔ
WooCommerce / 4.6.4
WooCommerce v4.6.4
10.8.1 10.8.0 10.8.0-rc.1 10.8.0-beta.2 10.8.0-beta.1 7.8.0-beta.1 7.8.0-beta.2 7.8.0-rc.1 7.8.0-rc.2 7.8.1 7.8.2 7.8.3 7.8.4 7.9.0 7.9.0-beta.1 7.9.0-beta.2 7.9.0-rc.2 7.9.0-rc.3 7.9.1 7.9.2 8.0.0 8.0.0-beta.1 8.0.0-beta.2 8.0.0-rc.1 8.0.0-rc.2 8.0.1 8.0.2 8.0.3 8.0.4 8.0.5 8.1.0 8.1.0-beta.1 8.1.0-rc.1 8.1.0-rc.2 8.1.1 8.1.2 8.1.3 8.1.4 8.2.0 8.2.0-beta.1 8.2.0-rc.1 8.2.0-rc.2 8.2.1 8.2.2 8.2.3 8.2.4 8.2.5 8.3.0 8.3.0-beta.1 8.3.0-rc.1 8.3.0-rc.2 8.3.1 8.3.2 8.3.3 8.3.4 8.4.0 8.4.0-beta.1 8.4.0-rc.1 8.4.1 8.4.2 8.4.3 8.5.0 8.5.0-beta.1 8.5.0-rc.1 8.5.1 8.5.2 8.5.3 8.5.4 8.5.5 8.6.0 8.6.0-beta.1 8.6.0-rc.1 8.6.1 8.6.2 8.6.3 8.6.4 8.7.0 8.7.0-beta.1 8.7.0-beta.2 8.7.0-rc.1 8.7.1 8.7.2 8.7.3 8.8.0 8.8.0-beta.1 8.8.0-rc.1 8.8.1 8.8.2 8.8.3 8.8.4 8.8.5 8.8.6 8.8.7 8.9.0 8.9.0-beta.1 8.9.0-rc.1 8.9.1 8.9.2 8.9.3 8.9.4 8.9.5 9.0.0 9.0.0-beta.1 9.0.0-beta.2 9.0.0-rc.1 9.0.1 9.0.2 9.0.3 9.0.4 9.1.0 9.1.0-beta.1 9.1.0-rc.1 9.1.1 9.1.2 9.1.3 9.1.4 9.1.5 9.1.6 9.2.0 9.2.0-beta.1 9.2.0-rc.1 9.2.1 9.2.2 9.2.3 9.2.4 9.2.5 9.3.0 9.3.0-beta.1 9.3.0-rc.1 9.3.1 9.3.2 9.3.3 9.3.4 9.3.5 9.3.6 9.4.0 9.4.0-beta.1 9.4.0-beta.2 9.4.0-rc.1 9.4.0-rc.2 9.4.0-rc.3 9.4.0-rc.4 9.4.1 9.4.2 9.4.3 9.4.4 9.4.5 9.5.0 9.5.0-beta.1 9.5.0-beta.2 9.5.0-rc.1 9.5.1 9.5.2 9.5.3 9.5.4 9.6.0 9.6.0-beta.1 9.6.0-beta.2 9.6.0-rc.1 9.6.1 9.6.2 9.6.3 9.6.4 9.7.0 9.7.0-beta.1 9.7.0-rc.1 9.7.1 9.7.2 9.7.3 9.8.0 9.8.0-beta.1 9.8.0-rc.1 9.8.1 9.8.2 9.8.3 9.8.4 9.8.5 9.8.6 9.8.7 9.9.0 9.9.0-beta.1 9.9.0-rc.1 9.9.1 9.9.2 9.9.3 9.9.4 9.9.5 9.9.6 9.9.7 3.7.3 7.1.2 3.8.0 7.2.0 3.8.0-beta.1 7.2.0-beta.1 3.8.0-rc.1 7.2.0-beta.2 3.8.0-rc.2 7.2.0-rc.1 3.8.1 7.2.0-rc.2 3.8.2 7.2.1 3.8.3 7.2.2 3.9.0 7.2.3 3.9.0-beta.1 7.2.4 3.9.0-beta.2 7.3.0 3.9.0-rc.1 7.3.0-beta.1 3.9.0-rc.2 7.3.0-beta.2 3.9.0-rc.3 7.3.0-rc.1 3.9.0-rc.4 7.3.0-rc.2 3.9.1 7.3.1 3.9.2 7.4.0 3.9.3 7.4.0-beta.1 3.9.4 7.4.0-beta.2 3.9.5 7.4.0-rc.1 4.0.0 7.4.0-rc.2 4.0.0-beta.1 7.4.1 4.0.0-rc.1 7.4.2 4.0.0-rc.2 7.5.0 4.0.1 7.5.0-beta.1 4.0.2 7.5.0-beta.2 4.0.3 7.5.0-rc.1 4.0.4 7.5.1 4.1.0 7.5.2 4.1.0-beta.1 7.6.0 4.1.0-beta.2 7.6.0-beta.1 4.1.0-rc.1 7.6.0-beta.2 4.1.0-rc.2 7.6.0-rc.1 4.1.1 7.6.0-rc.2 4.1.2 7.6.0-rc.3 4.1.3 7.6.1 4.1.4 7.6.2 4.2.0 7.7.0 4.2.0-RC.1 7.7.0-beta.1 4.2.0-RC.2 7.7.0-beta.2 4.2.0-beta.1 7.7.0-rc.1 4.2.1 7.7.1 4.2.2 7.7.2 4.2.3 7.7.3 4.2.4 7.8.0 4.2.5 4.3.0 4.3.0-beta.1 4.3.0-rc.1 4.3.0-rc.2 4.3.0-rc.3 4.3.1 4.3.2 4.3.3 4.3.4 4.3.5 4.3.6 4.4.0 4.4.0-beta.1 4.4.0-rc.1 4.4.1 4.4.2 4.4.3 4.4.4 4.5.0 4.5.0-beta.1 4.5.0-rc.1 4.5.0-rc.3 4.5.1 4.5.2 4.5.3 4.5.4 4.5.5 4.6.0 4.6.0-beta.1 4.6.0-rc.1 4.6.1 4.6.2 4.6.3 4.6.4 4.6.5 4.7.0 4.7.0-beta.1 4.7.0-beta.2 4.7.0-rc.1 4.7.1 4.7.1-beta.1 4.7.2 4.7.3 4.7.4 4.8.0 4.8.0-beta.1 4.8.0-rc.1 4.8.0-rc.2 4.8.1 4.8.2 4.8.3 4.9.0 4.9.0-beta.1 4.9.0-rc.1 4.9.0-rc.2 4.9.1 4.9.2 4.9.3 4.9.4 4.9.5 5.0.0 5.0.0-beta.1 5.0.0-beta.2 5.0.0-rc.1 5.0.0-rc.2 5.0.0-rc.3 5.0.1 5.0.2 5.0.3 5.1.0 5.1.0-beta.1 5.1.0-rc.1 trunk 5.1.1 10.0.0 5.1.2 10.0.0-rc.1 5.1.3 10.0.0-rc.2 5.2.0 10.0.1 5.2.0-beta.1 10.0.2 5.2.0-rc.1 10.0.3 5.2.0-rc.2 10.0.4 5.2.1 10.0.5 5.2.2 10.0.6 5.2.3 10.1.0 5.2.4 10.1.0-rc.1 5.2.5 10.1.0-rc.2 5.3.0 10.1.0-rc.3 5.3.0-beta.1 10.1.0-rc.4 5.3.0-rc.1 10.1.1 5.3.0-rc.2 10.1.2 5.3.1 10.1.3 5.3.2 10.1.4 5.3.3 10.2.0 5.4.0 10.2.0-beta.1 5.4.0-beta.1 10.2.0-beta.2 5.4.0-rc.1 10.2.0-rc.1 5.4.1 10.2.1 5.4.2 10.2.2 5.4.3 10.2.3 5.4.4 10.2.4 5.4.5 10.3.0 5.5.0 10.3.0-beta.1 5.5.0-beta.1 10.3.0-beta.2 5.5.0-rc.1 10.3.0-rc.1 5.5.0-rc.2 10.3.0-rc.2 5.5.1 10.3.1 5.5.2 10.3.2 5.5.3 10.3.3 5.5.4 10.3.4 5.5.5 10.3.5 5.6.0 10.3.6 5.6.0-beta.1 10.3.7 5.6.0-rc.1 10.3.8 5.6.0-rc.2 10.4.0 5.6.1 10.4.0-beta.1 5.6.2 10.4.0-beta.2 5.6.3 10.4.0-rc.1 5.7.0 10.4.1 5.7.0-beta.1 10.4.2 5.7.0-rc.1 10.4.3 5.7.1 10.4.4 5.7.2 10.5.0 5.7.3 10.5.0-beta.1 5.8.0 10.5.0-beta.2 5.8.0-beta.1 10.5.0-rc.1 5.8.0-beta.2 10.5.0-rc.2 5.8.0-rc.1 10.5.0-rc.3 5.8.1 10.5.1 5.8.2 10.5.2 5.9.0 10.5.3 5.9.0-beta.1 10.6.0 5.9.0-rc.1 10.6.0-beta.1 5.9.0-rc.2 10.6.0-beta.2 5.9.1 10.6.0-rc.1 5.9.2 10.6.1 6.0.0 10.6.2 6.0.0-beta.1 10.7.0 6.0.0-rc.1 10.7.0-beta.1 6.0.1 10.7.0-beta.2 6.0.2 10.7.0-rc.1 6.1.0 3.0.0 6.1.0-beta.1 3.0.1 6.1.0-rc.1 3.0.2 6.1.0-rc.2 3.0.3 6.1.1 3.0.4 6.1.2 3.0.5 6.1.3 3.0.6 6.2.0 3.0.7 6.2.0-beta.1 3.0.8 6.2.0-rc.1 3.0.9 6.2.0-rc.2 3.1.0 6.2.1 3.1.1 6.2.2 3.1.2 6.2.3 3.2.0 6.3.0 3.2.1 6.3.0-beta.1 3.2.2 6.3.0-rc.1 3.2.3 6.3.0-rc.2 3.2.4 6.3.1 3.2.5 6.3.2 3.2.6 6.4.0 3.3.0 6.4.0-beta.1 3.3.1 6.4.0-rc.1 3.3.2 6.4.1 3.3.2-rc.1 6.4.2 3.3.3 6.5.0 3.3.4 6.5.0-beta.1 3.3.5 6.5.0-rc.1 3.3.6 6.5.0-rc.2 3.4.0 6.5.1 3.4.0-beta.1 6.5.2 3.4.0-rc.2 6.6.0 3.4.1 6.6.0-beta.1 3.4.2 6.6.0-rc.1 3.4.3 6.6.0-rc.2 3.4.4 6.6.1 3.4.5 6.6.2 3.4.6 6.7.0 3.4.7 6.7.0-beta.1 3.4.8 6.7.0-beta.2 3.5.0 6.7.0-rc.1 3.5.0-beta.1 6.7.1 3.5.0-rc.1 6.8.0 3.5.0-rc.2 6.8.0-beta.1 3.5.1 6.8.0-beta.2 3.5.10 6.8.0-rc.1 3.5.2 6.8.1 3.5.3 6.8.2 3.5.4 6.8.3 3.5.5 6.9.0 3.5.6 6.9.0-beta.1 3.5.7 6.9.0-beta.2 3.5.8 6.9.0-rc.1 3.5.9 6.9.1 3.6.0 6.9.2 3.6.0-beta.1 6.9.3 3.6.0-rc.1 6.9.4 3.6.0-rc.2 6.9.5 3.6.0-rc.3 7.0.0 3.6.1 7.0.0-beta.1 3.6.2 7.0.0-beta.2 3.6.3 7.0.0-beta.3 3.6.4 7.0.0-rc.1 3.6.5 7.0.0-rc.2 3.6.6 7.0.1 3.6.7 7.0.2 3.7.0 7.1.0 3.7.0-beta.1 7.1.0-beta.1 3.7.0-rc.1 7.1.0-beta.2 3.7.0-rc.2 7.1.0-rc.1 3.7.1 7.1.0-rc.2 3.7.2 7.1.1
woocommerce / includes / wc-formatting-functions.php
woocommerce / includes Last commit date
abstracts 5 years ago admin 5 years ago cli 5 years ago customizer 5 years ago data-stores 4 years ago emails 5 years ago export 5 years ago gateways 5 years ago import 5 years ago integrations 5 years ago interfaces 5 years ago legacy 5 years ago libraries 5 years ago log-handlers 5 years ago payment-tokens 5 years ago queue 5 years ago rest-api 5 years ago shipping 5 years ago shortcodes 5 years ago theme-support 5 years ago tracks 5 years ago traits 5 years ago walkers 5 years ago wccom-site 5 years ago widgets 5 years ago class-wc-ajax.php 5 years ago class-wc-api.php 5 years ago class-wc-auth.php 5 years ago class-wc-autoloader.php 5 years ago class-wc-background-emailer.php 5 years ago class-wc-background-updater.php 5 years ago class-wc-breadcrumb.php 5 years ago class-wc-cache-helper.php 5 years ago class-wc-cart-fees.php 5 years ago class-wc-cart-session.php 5 years ago class-wc-cart-totals.php 5 years ago class-wc-cart.php 5 years ago class-wc-checkout.php 5 years ago class-wc-cli.php 5 years ago class-wc-comments.php 5 years ago class-wc-countries.php 5 years ago class-wc-coupon.php 5 years ago class-wc-customer-download-log.php 5 years ago class-wc-customer-download.php 5 years ago class-wc-customer.php 5 years ago class-wc-data-exception.php 5 years ago class-wc-data-store.php 5 years ago class-wc-datetime.php 5 years ago class-wc-deprecated-action-hooks.php 5 years ago class-wc-deprecated-filter-hooks.php 5 years ago class-wc-discounts.php 5 years ago class-wc-download-handler.php 5 years ago class-wc-emails.php 5 years ago class-wc-embed.php 5 years ago class-wc-form-handler.php 5 years ago class-wc-frontend-scripts.php 5 years ago class-wc-geo-ip.php 5 years ago class-wc-geolite-integration.php 5 years ago class-wc-geolocation.php 5 years ago class-wc-https.php 5 years ago class-wc-install.php 4 years ago class-wc-integrations.php 5 years ago class-wc-log-levels.php 5 years ago class-wc-logger.php 5 years ago class-wc-meta-data.php 5 years ago class-wc-order-factory.php 5 years ago class-wc-order-item-coupon.php 5 years ago class-wc-order-item-fee.php 5 years ago class-wc-order-item-meta.php 5 years ago class-wc-order-item-product.php 5 years ago class-wc-order-item-shipping.php 5 years ago class-wc-order-item-tax.php 5 years ago class-wc-order-item.php 5 years ago class-wc-order-query.php 5 years ago class-wc-order-refund.php 5 years ago class-wc-order.php 5 years ago class-wc-payment-gateways.php 5 years ago class-wc-payment-tokens.php 5 years ago class-wc-post-data.php 5 years ago class-wc-post-types.php 5 years ago class-wc-privacy-background-process.php 5 years ago class-wc-privacy-erasers.php 5 years ago class-wc-privacy-exporters.php 5 years ago class-wc-privacy.php 5 years ago class-wc-product-attribute.php 5 years ago class-wc-product-download.php 5 years ago class-wc-product-external.php 5 years ago class-wc-product-factory.php 5 years ago class-wc-product-grouped.php 5 years ago class-wc-product-query.php 5 years ago class-wc-product-simple.php 5 years ago class-wc-product-variable.php 5 years ago class-wc-product-variation.php 5 years ago class-wc-query.php 5 years ago class-wc-rate-limiter.php 5 years ago class-wc-regenerate-images-request.php 5 years ago class-wc-regenerate-images.php 5 years ago class-wc-register-wp-admin-settings.php 5 years ago class-wc-rest-authentication.php 5 years ago class-wc-rest-exception.php 5 years ago class-wc-session-handler.php 5 years ago class-wc-shipping-rate.php 5 years ago class-wc-shipping-zone.php 5 years ago class-wc-shipping-zones.php 5 years ago class-wc-shipping.php 5 years ago class-wc-shortcodes.php 5 years ago class-wc-structured-data.php 5 years ago class-wc-tax.php 5 years ago class-wc-template-loader.php 5 years ago class-wc-tracker.php 5 years ago class-wc-validation.php 5 years ago class-wc-webhook.php 5 years ago class-woocommerce.php 4 years ago wc-account-functions.php 5 years ago wc-attribute-functions.php 5 years ago wc-cart-functions.php 5 years ago wc-conditional-functions.php 5 years ago wc-core-functions.php 5 years ago wc-coupon-functions.php 5 years ago wc-deprecated-functions.php 5 years ago wc-formatting-functions.php 5 years ago wc-notice-functions.php 5 years ago wc-order-functions.php 5 years ago wc-order-item-functions.php 5 years ago wc-page-functions.php 5 years ago wc-product-functions.php 5 years ago wc-rest-functions.php 5 years ago wc-stock-functions.php 5 years ago wc-template-functions.php 5 years ago wc-template-hooks.php 5 years ago wc-term-functions.php 5 years ago wc-update-functions.php 4 years ago wc-user-functions.php 5 years ago wc-webhook-functions.php 5 years ago wc-widget-functions.php 5 years ago
wc-formatting-functions.php
1479 lines
1 <?php
2 /**
3 * WooCommerce Formatting
4 *
5 * Functions for formatting data.
6 *
7 * @package WooCommerce\Functions
8 * @version 2.1.0
9 */
10
11 defined( 'ABSPATH' ) || exit;
12
13 /**
14 * Converts a string (e.g. 'yes' or 'no') to a bool.
15 *
16 * @since 3.0.0
17 * @param string|bool $string String to convert. If a bool is passed it will be returned as-is.
18 * @return bool
19 */
20 function wc_string_to_bool( $string ) {
21 return is_bool( $string ) ? $string : ( 'yes' === strtolower( $string ) || 1 === $string || 'true' === strtolower( $string ) || '1' === $string );
22 }
23
24 /**
25 * Converts a bool to a 'yes' or 'no'.
26 *
27 * @since 3.0.0
28 * @param bool|string $bool Bool to convert. If a string is passed it will first be converted to a bool.
29 * @return string
30 */
31 function wc_bool_to_string( $bool ) {
32 if ( ! is_bool( $bool ) ) {
33 $bool = wc_string_to_bool( $bool );
34 }
35 return true === $bool ? 'yes' : 'no';
36 }
37
38 /**
39 * Explode a string into an array by $delimiter and remove empty values.
40 *
41 * @since 3.0.0
42 * @param string $string String to convert.
43 * @param string $delimiter Delimiter, defaults to ','.
44 * @return array
45 */
46 function wc_string_to_array( $string, $delimiter = ',' ) {
47 return is_array( $string ) ? $string : array_filter( explode( $delimiter, $string ) );
48 }
49
50 /**
51 * Sanitize taxonomy names. Slug format (no spaces, lowercase).
52 * Urldecode is used to reverse munging of UTF8 characters.
53 *
54 * @param string $taxonomy Taxonomy name.
55 * @return string
56 */
57 function wc_sanitize_taxonomy_name( $taxonomy ) {
58 return apply_filters( 'sanitize_taxonomy_name', urldecode( sanitize_title( urldecode( $taxonomy ) ) ), $taxonomy );
59 }
60
61 /**
62 * Sanitize permalink values before insertion into DB.
63 *
64 * Cannot use wc_clean because it sometimes strips % chars and breaks the user's setting.
65 *
66 * @since 2.6.0
67 * @param string $value Permalink.
68 * @return string
69 */
70 function wc_sanitize_permalink( $value ) {
71 global $wpdb;
72
73 $value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', $value );
74
75 if ( is_wp_error( $value ) ) {
76 $value = '';
77 }
78
79 $value = esc_url_raw( trim( $value ) );
80 $value = str_replace( 'http://', '', $value );
81 return untrailingslashit( $value );
82 }
83
84 /**
85 * Gets the filename part of a download URL.
86 *
87 * @param string $file_url File URL.
88 * @return string
89 */
90 function wc_get_filename_from_url( $file_url ) {
91 $parts = wp_parse_url( $file_url );
92 if ( isset( $parts['path'] ) ) {
93 return basename( $parts['path'] );
94 }
95 }
96
97 /**
98 * Normalise dimensions, unify to cm then convert to wanted unit value.
99 *
100 * Usage:
101 * wc_get_dimension( 55, 'in' );
102 * wc_get_dimension( 55, 'in', 'm' );
103 *
104 * @param int|float $dimension Dimension.
105 * @param string $to_unit Unit to convert to.
106 * Options: 'in', 'm', 'cm', 'm'.
107 * @param string $from_unit Unit to convert from.
108 * Defaults to ''.
109 * Options: 'in', 'm', 'cm', 'm'.
110 * @return float
111 */
112 function wc_get_dimension( $dimension, $to_unit, $from_unit = '' ) {
113 $to_unit = strtolower( $to_unit );
114
115 if ( empty( $from_unit ) ) {
116 $from_unit = strtolower( get_option( 'woocommerce_dimension_unit' ) );
117 }
118
119 // Unify all units to cm first.
120 if ( $from_unit !== $to_unit ) {
121 switch ( $from_unit ) {
122 case 'in':
123 $dimension *= 2.54;
124 break;
125 case 'm':
126 $dimension *= 100;
127 break;
128 case 'mm':
129 $dimension *= 0.1;
130 break;
131 case 'yd':
132 $dimension *= 91.44;
133 break;
134 }
135
136 // Output desired unit.
137 switch ( $to_unit ) {
138 case 'in':
139 $dimension *= 0.3937;
140 break;
141 case 'm':
142 $dimension *= 0.01;
143 break;
144 case 'mm':
145 $dimension *= 10;
146 break;
147 case 'yd':
148 $dimension *= 0.010936133;
149 break;
150 }
151 }
152
153 return ( $dimension < 0 ) ? 0 : $dimension;
154 }
155
156 /**
157 * Normalise weights, unify to kg then convert to wanted unit value.
158 *
159 * Usage:
160 * wc_get_weight(55, 'kg');
161 * wc_get_weight(55, 'kg', 'lbs');
162 *
163 * @param int|float $weight Weight.
164 * @param string $to_unit Unit to convert to.
165 * Options: 'g', 'kg', 'lbs', 'oz'.
166 * @param string $from_unit Unit to convert from.
167 * Defaults to ''.
168 * Options: 'g', 'kg', 'lbs', 'oz'.
169 * @return float
170 */
171 function wc_get_weight( $weight, $to_unit, $from_unit = '' ) {
172 $weight = (float) $weight;
173 $to_unit = strtolower( $to_unit );
174
175 if ( empty( $from_unit ) ) {
176 $from_unit = strtolower( get_option( 'woocommerce_weight_unit' ) );
177 }
178
179 // Unify all units to kg first.
180 if ( $from_unit !== $to_unit ) {
181 switch ( $from_unit ) {
182 case 'g':
183 $weight *= 0.001;
184 break;
185 case 'lbs':
186 $weight *= 0.453592;
187 break;
188 case 'oz':
189 $weight *= 0.0283495;
190 break;
191 }
192
193 // Output desired unit.
194 switch ( $to_unit ) {
195 case 'g':
196 $weight *= 1000;
197 break;
198 case 'lbs':
199 $weight *= 2.20462;
200 break;
201 case 'oz':
202 $weight *= 35.274;
203 break;
204 }
205 }
206
207 return ( $weight < 0 ) ? 0 : $weight;
208 }
209
210 /**
211 * Trim trailing zeros off prices.
212 *
213 * @param string|float|int $price Price.
214 * @return string
215 */
216 function wc_trim_zeros( $price ) {
217 return preg_replace( '/' . preg_quote( wc_get_price_decimal_separator(), '/' ) . '0++$/', '', $price );
218 }
219
220 /**
221 * Round a tax amount.
222 *
223 * @param double $value Amount to round.
224 * @param int $precision DP to round. Defaults to wc_get_price_decimals.
225 * @return float
226 */
227 function wc_round_tax_total( $value, $precision = null ) {
228 $precision = is_null( $precision ) ? wc_get_price_decimals() : intval( $precision );
229
230 if ( version_compare( PHP_VERSION, '5.3.0', '>=' ) ) {
231 $rounded_tax = round( $value, $precision, wc_get_tax_rounding_mode() ); // phpcs:ignore PHPCompatibility.FunctionUse.NewFunctionParameters.round_modeFound
232 } elseif ( 2 === wc_get_tax_rounding_mode() ) {
233 $rounded_tax = wc_legacy_round_half_down( $value, $precision );
234 } else {
235 $rounded_tax = round( $value, $precision );
236 }
237
238 return apply_filters( 'wc_round_tax_total', $rounded_tax, $value, $precision, WC_TAX_ROUNDING_MODE );
239 }
240
241 /**
242 * Round half down in PHP 5.2.
243 *
244 * @since 3.2.6
245 * @param float $value Value to round.
246 * @param int $precision Precision to round down to.
247 * @return float
248 */
249 function wc_legacy_round_half_down( $value, $precision ) {
250 $value = wc_float_to_string( $value );
251
252 if ( false !== strstr( $value, '.' ) ) {
253 $value = explode( '.', $value );
254
255 if ( strlen( $value[1] ) > $precision && substr( $value[1], -1 ) === '5' ) {
256 $value[1] = substr( $value[1], 0, -1 ) . '4';
257 }
258
259 $value = implode( '.', $value );
260 }
261
262 return round( floatval( $value ), $precision );
263 }
264
265 /**
266 * Make a refund total negative.
267 *
268 * @param float $amount Refunded amount.
269 *
270 * @return float
271 */
272 function wc_format_refund_total( $amount ) {
273 return $amount * -1;
274 }
275
276 /**
277 * Format decimal numbers ready for DB storage.
278 *
279 * Sanitize, remove decimals, and optionally round + trim off zeros.
280 *
281 * This function does not remove thousands - this should be done before passing a value to the function.
282 *
283 * @param float|string $number Expects either a float or a string with a decimal separator only (no thousands).
284 * @param mixed $dp number Number of decimal points to use, blank to use woocommerce_price_num_decimals, or false to avoid all rounding.
285 * @param bool $trim_zeros From end of string.
286 * @return string
287 */
288 function wc_format_decimal( $number, $dp = false, $trim_zeros = false ) {
289 $locale = localeconv();
290 $decimals = array( wc_get_price_decimal_separator(), $locale['decimal_point'], $locale['mon_decimal_point'] );
291
292 // Remove locale from string.
293 if ( ! is_float( $number ) ) {
294 $number = str_replace( $decimals, '.', $number );
295
296 // Convert multiple dots to just one.
297 $number = preg_replace( '/\.(?![^.]+$)|[^0-9.-]/', '', wc_clean( $number ) );
298 }
299
300 if ( false !== $dp ) {
301 $dp = intval( '' === $dp ? wc_get_price_decimals() : $dp );
302 $number = number_format( floatval( $number ), $dp, '.', '' );
303 } elseif ( is_float( $number ) ) {
304 // DP is false - don't use number format, just return a string using whatever is given. Remove scientific notation using sprintf.
305 $number = str_replace( $decimals, '.', sprintf( '%.' . wc_get_rounding_precision() . 'f', $number ) );
306 // We already had a float, so trailing zeros are not needed.
307 $trim_zeros = true;
308 }
309
310 if ( $trim_zeros && strstr( $number, '.' ) ) {
311 $number = rtrim( rtrim( $number, '0' ), '.' );
312 }
313
314 return $number;
315 }
316
317 /**
318 * Convert a float to a string without locale formatting which PHP adds when changing floats to strings.
319 *
320 * @param float $float Float value to format.
321 * @return string
322 */
323 function wc_float_to_string( $float ) {
324 if ( ! is_float( $float ) ) {
325 return $float;
326 }
327
328 $locale = localeconv();
329 $string = strval( $float );
330 $string = str_replace( $locale['decimal_point'], '.', $string );
331
332 return $string;
333 }
334
335 /**
336 * Format a price with WC Currency Locale settings.
337 *
338 * @param string $value Price to localize.
339 * @return string
340 */
341 function wc_format_localized_price( $value ) {
342 return apply_filters( 'woocommerce_format_localized_price', str_replace( '.', wc_get_price_decimal_separator(), strval( $value ) ), $value );
343 }
344
345 /**
346 * Format a decimal with PHP Locale settings.
347 *
348 * @param string $value Decimal to localize.
349 * @return string
350 */
351 function wc_format_localized_decimal( $value ) {
352 $locale = localeconv();
353 return apply_filters( 'woocommerce_format_localized_decimal', str_replace( '.', $locale['decimal_point'], strval( $value ) ), $value );
354 }
355
356 /**
357 * Format a coupon code.
358 *
359 * @since 3.0.0
360 * @param string $value Coupon code to format.
361 * @return string
362 */
363 function wc_format_coupon_code( $value ) {
364 return apply_filters( 'woocommerce_coupon_code', $value );
365 }
366
367 /**
368 * Sanitize a coupon code.
369 *
370 * Uses sanitize_post_field since coupon codes are stored as
371 * post_titles - the sanitization and escaping must match.
372 *
373 * @since 3.6.0
374 * @param string $value Coupon code to format.
375 * @return string
376 */
377 function wc_sanitize_coupon_code( $value ) {
378 return wp_filter_kses( sanitize_post_field( 'post_title', $value, 0, 'db' ) );
379 }
380
381 /**
382 * Clean variables using sanitize_text_field. Arrays are cleaned recursively.
383 * Non-scalar values are ignored.
384 *
385 * @param string|array $var Data to sanitize.
386 * @return string|array
387 */
388 function wc_clean( $var ) {
389 if ( is_array( $var ) ) {
390 return array_map( 'wc_clean', $var );
391 } else {
392 return is_scalar( $var ) ? sanitize_text_field( $var ) : $var;
393 }
394 }
395
396 /**
397 * Function wp_check_invalid_utf8 with recursive array support.
398 *
399 * @param string|array $var Data to sanitize.
400 * @return string|array
401 */
402 function wc_check_invalid_utf8( $var ) {
403 if ( is_array( $var ) ) {
404 return array_map( 'wc_check_invalid_utf8', $var );
405 } else {
406 return wp_check_invalid_utf8( $var );
407 }
408 }
409
410 /**
411 * Run wc_clean over posted textarea but maintain line breaks.
412 *
413 * @since 3.0.0
414 * @param string $var Data to sanitize.
415 * @return string
416 */
417 function wc_sanitize_textarea( $var ) {
418 return implode( "\n", array_map( 'wc_clean', explode( "\n", $var ) ) );
419 }
420
421 /**
422 * Sanitize a string destined to be a tooltip.
423 *
424 * @since 2.3.10 Tooltips are encoded with htmlspecialchars to prevent XSS. Should not be used in conjunction with esc_attr()
425 * @param string $var Data to sanitize.
426 * @return string
427 */
428 function wc_sanitize_tooltip( $var ) {
429 return htmlspecialchars(
430 wp_kses(
431 html_entity_decode( $var ),
432 array(
433 'br' => array(),
434 'em' => array(),
435 'strong' => array(),
436 'small' => array(),
437 'span' => array(),
438 'ul' => array(),
439 'li' => array(),
440 'ol' => array(),
441 'p' => array(),
442 )
443 )
444 );
445 }
446
447 /**
448 * Merge two arrays.
449 *
450 * @param array $a1 First array to merge.
451 * @param array $a2 Second array to merge.
452 * @return array
453 */
454 function wc_array_overlay( $a1, $a2 ) {
455 foreach ( $a1 as $k => $v ) {
456 if ( ! array_key_exists( $k, $a2 ) ) {
457 continue;
458 }
459 if ( is_array( $v ) && is_array( $a2[ $k ] ) ) {
460 $a1[ $k ] = wc_array_overlay( $v, $a2[ $k ] );
461 } else {
462 $a1[ $k ] = $a2[ $k ];
463 }
464 }
465 return $a1;
466 }
467
468 /**
469 * Formats a stock amount by running it through a filter.
470 *
471 * @param int|float $amount Stock amount.
472 * @return int|float
473 */
474 function wc_stock_amount( $amount ) {
475 return apply_filters( 'woocommerce_stock_amount', $amount );
476 }
477
478 /**
479 * Get the price format depending on the currency position.
480 *
481 * @return string
482 */
483 function get_woocommerce_price_format() {
484 $currency_pos = get_option( 'woocommerce_currency_pos' );
485 $format = '%1$s%2$s';
486
487 switch ( $currency_pos ) {
488 case 'left':
489 $format = '%1$s%2$s';
490 break;
491 case 'right':
492 $format = '%2$s%1$s';
493 break;
494 case 'left_space':
495 $format = '%1$s&nbsp;%2$s';
496 break;
497 case 'right_space':
498 $format = '%2$s&nbsp;%1$s';
499 break;
500 }
501
502 return apply_filters( 'woocommerce_price_format', $format, $currency_pos );
503 }
504
505 /**
506 * Return the thousand separator for prices.
507 *
508 * @since 2.3
509 * @return string
510 */
511 function wc_get_price_thousand_separator() {
512 return stripslashes( apply_filters( 'wc_get_price_thousand_separator', get_option( 'woocommerce_price_thousand_sep' ) ) );
513 }
514
515 /**
516 * Return the decimal separator for prices.
517 *
518 * @since 2.3
519 * @return string
520 */
521 function wc_get_price_decimal_separator() {
522 $separator = apply_filters( 'wc_get_price_decimal_separator', get_option( 'woocommerce_price_decimal_sep' ) );
523 return $separator ? stripslashes( $separator ) : '.';
524 }
525
526 /**
527 * Return the number of decimals after the decimal point.
528 *
529 * @since 2.3
530 * @return int
531 */
532 function wc_get_price_decimals() {
533 return absint( apply_filters( 'wc_get_price_decimals', get_option( 'woocommerce_price_num_decimals', 2 ) ) );
534 }
535
536 /**
537 * Format the price with a currency symbol.
538 *
539 * @param float $price Raw price.
540 * @param array $args Arguments to format a price {
541 * Array of arguments.
542 * Defaults to empty array.
543 *
544 * @type bool $ex_tax_label Adds exclude tax label.
545 * Defaults to false.
546 * @type string $currency Currency code.
547 * Defaults to empty string (Use the result from get_woocommerce_currency()).
548 * @type string $decimal_separator Decimal separator.
549 * Defaults the result of wc_get_price_decimal_separator().
550 * @type string $thousand_separator Thousand separator.
551 * Defaults the result of wc_get_price_thousand_separator().
552 * @type string $decimals Number of decimals.
553 * Defaults the result of wc_get_price_decimals().
554 * @type string $price_format Price format depending on the currency position.
555 * Defaults the result of get_woocommerce_price_format().
556 * }
557 * @return string
558 */
559 function wc_price( $price, $args = array() ) {
560 $args = apply_filters(
561 'wc_price_args',
562 wp_parse_args(
563 $args,
564 array(
565 'ex_tax_label' => false,
566 'currency' => '',
567 'decimal_separator' => wc_get_price_decimal_separator(),
568 'thousand_separator' => wc_get_price_thousand_separator(),
569 'decimals' => wc_get_price_decimals(),
570 'price_format' => get_woocommerce_price_format(),
571 )
572 )
573 );
574
575 $unformatted_price = $price;
576 $negative = $price < 0;
577 $price = apply_filters( 'raw_woocommerce_price', floatval( $negative ? $price * -1 : $price ) );
578 $price = apply_filters( 'formatted_woocommerce_price', number_format( $price, $args['decimals'], $args['decimal_separator'], $args['thousand_separator'] ), $price, $args['decimals'], $args['decimal_separator'], $args['thousand_separator'] );
579
580 if ( apply_filters( 'woocommerce_price_trim_zeros', false ) && $args['decimals'] > 0 ) {
581 $price = wc_trim_zeros( $price );
582 }
583
584 $formatted_price = ( $negative ? '-' : '' ) . sprintf( $args['price_format'], '<span class="woocommerce-Price-currencySymbol">' . get_woocommerce_currency_symbol( $args['currency'] ) . '</span>', $price );
585 $return = '<span class="woocommerce-Price-amount amount"><bdi>' . $formatted_price . '</bdi></span>';
586
587 if ( $args['ex_tax_label'] && wc_tax_enabled() ) {
588 $return .= ' <small class="woocommerce-Price-taxLabel tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>';
589 }
590
591 /**
592 * Filters the string of price markup.
593 *
594 * @param string $return Price HTML markup.
595 * @param string $price Formatted price.
596 * @param array $args Pass on the args.
597 * @param float $unformatted_price Price as float to allow plugins custom formatting. Since 3.2.0.
598 */
599 return apply_filters( 'wc_price', $return, $price, $args, $unformatted_price );
600 }
601
602 /**
603 * Notation to numbers.
604 *
605 * This function transforms the php.ini notation for numbers (like '2M') to an integer.
606 *
607 * @param string $size Size value.
608 * @return int
609 */
610 function wc_let_to_num( $size ) {
611 $l = substr( $size, -1 );
612 $ret = (int) substr( $size, 0, -1 );
613 switch ( strtoupper( $l ) ) {
614 case 'P':
615 $ret *= 1024;
616 // No break.
617 case 'T':
618 $ret *= 1024;
619 // No break.
620 case 'G':
621 $ret *= 1024;
622 // No break.
623 case 'M':
624 $ret *= 1024;
625 // No break.
626 case 'K':
627 $ret *= 1024;
628 // No break.
629 }
630 return $ret;
631 }
632
633 /**
634 * WooCommerce Date Format - Allows to change date format for everything WooCommerce.
635 *
636 * @return string
637 */
638 function wc_date_format() {
639 return apply_filters( 'woocommerce_date_format', get_option( 'date_format' ) );
640 }
641
642 /**
643 * WooCommerce Time Format - Allows to change time format for everything WooCommerce.
644 *
645 * @return string
646 */
647 function wc_time_format() {
648 return apply_filters( 'woocommerce_time_format', get_option( 'time_format' ) );
649 }
650
651 /**
652 * Convert mysql datetime to PHP timestamp, forcing UTC. Wrapper for strtotime.
653 *
654 * Based on wcs_strtotime_dark_knight() from WC Subscriptions by Prospress.
655 *
656 * @since 3.0.0
657 * @param string $time_string Time string.
658 * @param int|null $from_timestamp Timestamp to convert from.
659 * @return int
660 */
661 function wc_string_to_timestamp( $time_string, $from_timestamp = null ) {
662 $original_timezone = date_default_timezone_get();
663
664 // @codingStandardsIgnoreStart
665 date_default_timezone_set( 'UTC' );
666
667 if ( null === $from_timestamp ) {
668 $next_timestamp = strtotime( $time_string );
669 } else {
670 $next_timestamp = strtotime( $time_string, $from_timestamp );
671 }
672
673 date_default_timezone_set( $original_timezone );
674 // @codingStandardsIgnoreEnd
675
676 return $next_timestamp;
677 }
678
679 /**
680 * Convert a date string to a WC_DateTime.
681 *
682 * @since 3.1.0
683 * @param string $time_string Time string.
684 * @return WC_DateTime
685 */
686 function wc_string_to_datetime( $time_string ) {
687 // Strings are defined in local WP timezone. Convert to UTC.
688 if ( 1 === preg_match( '/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(Z|((-|\+)\d{2}:\d{2}))$/', $time_string, $date_bits ) ) {
689 $offset = ! empty( $date_bits[7] ) ? iso8601_timezone_to_offset( $date_bits[7] ) : wc_timezone_offset();
690 $timestamp = gmmktime( $date_bits[4], $date_bits[5], $date_bits[6], $date_bits[2], $date_bits[3], $date_bits[1] ) - $offset;
691 } else {
692 $timestamp = wc_string_to_timestamp( get_gmt_from_date( gmdate( 'Y-m-d H:i:s', wc_string_to_timestamp( $time_string ) ) ) );
693 }
694 $datetime = new WC_DateTime( "@{$timestamp}", new DateTimeZone( 'UTC' ) );
695
696 // Set local timezone or offset.
697 if ( get_option( 'timezone_string' ) ) {
698 $datetime->setTimezone( new DateTimeZone( wc_timezone_string() ) );
699 } else {
700 $datetime->set_utc_offset( wc_timezone_offset() );
701 }
702
703 return $datetime;
704 }
705
706 /**
707 * WooCommerce Timezone - helper to retrieve the timezone string for a site until.
708 * a WP core method exists (see https://core.trac.wordpress.org/ticket/24730).
709 *
710 * Adapted from https://secure.php.net/manual/en/function.timezone-name-from-abbr.php#89155.
711 *
712 * @since 2.1
713 * @return string PHP timezone string for the site
714 */
715 function wc_timezone_string() {
716 // Added in WordPress 5.3 Ref https://developer.wordpress.org/reference/functions/wp_timezone_string/.
717 if ( function_exists( 'wp_timezone_string' ) ) {
718 return wp_timezone_string();
719 }
720
721 // If site timezone string exists, return it.
722 $timezone = get_option( 'timezone_string' );
723 if ( $timezone ) {
724 return $timezone;
725 }
726
727 // Get UTC offset, if it isn't set then return UTC.
728 $utc_offset = floatval( get_option( 'gmt_offset', 0 ) );
729 if ( ! is_numeric( $utc_offset ) || 0.0 === $utc_offset ) {
730 return 'UTC';
731 }
732
733 // Adjust UTC offset from hours to seconds.
734 $utc_offset = (int) ( $utc_offset * 3600 );
735
736 // Attempt to guess the timezone string from the UTC offset.
737 $timezone = timezone_name_from_abbr( '', $utc_offset );
738 if ( $timezone ) {
739 return $timezone;
740 }
741
742 // Last try, guess timezone string manually.
743 foreach ( timezone_abbreviations_list() as $abbr ) {
744 foreach ( $abbr as $city ) {
745 // WordPress restrict the use of date(), since it's affected by timezone settings, but in this case is just what we need to guess the correct timezone.
746 if ( (bool) date( 'I' ) === (bool) $city['dst'] && $city['timezone_id'] && intval( $city['offset'] ) === $utc_offset ) { // phpcs:ignore WordPress.DateTime.RestrictedFunctions.date_date
747 return $city['timezone_id'];
748 }
749 }
750 }
751
752 // Fallback to UTC.
753 return 'UTC';
754 }
755
756 /**
757 * Get timezone offset in seconds.
758 *
759 * @since 3.0.0
760 * @return float
761 */
762 function wc_timezone_offset() {
763 $timezone = get_option( 'timezone_string' );
764
765 if ( $timezone ) {
766 $timezone_object = new DateTimeZone( $timezone );
767 return $timezone_object->getOffset( new DateTime( 'now' ) );
768 } else {
769 return floatval( get_option( 'gmt_offset', 0 ) ) * HOUR_IN_SECONDS;
770 }
771 }
772
773 /**
774 * Callback which can flatten post meta (gets the first value if it's an array).
775 *
776 * @since 3.0.0
777 * @param array $value Value to flatten.
778 * @return mixed
779 */
780 function wc_flatten_meta_callback( $value ) {
781 return is_array( $value ) ? current( $value ) : $value;
782 }
783
784 if ( ! function_exists( 'wc_rgb_from_hex' ) ) {
785
786 /**
787 * Convert RGB to HEX.
788 *
789 * @param mixed $color Color.
790 *
791 * @return array
792 */
793 function wc_rgb_from_hex( $color ) {
794 $color = str_replace( '#', '', $color );
795 // Convert shorthand colors to full format, e.g. "FFF" -> "FFFFFF".
796 $color = preg_replace( '~^(.)(.)(.)$~', '$1$1$2$2$3$3', $color );
797
798 $rgb = array();
799 $rgb['R'] = hexdec( $color[0] . $color[1] );
800 $rgb['G'] = hexdec( $color[2] . $color[3] );
801 $rgb['B'] = hexdec( $color[4] . $color[5] );
802
803 return $rgb;
804 }
805 }
806
807 if ( ! function_exists( 'wc_hex_darker' ) ) {
808
809 /**
810 * Make HEX color darker.
811 *
812 * @param mixed $color Color.
813 * @param int $factor Darker factor.
814 * Defaults to 30.
815 * @return string
816 */
817 function wc_hex_darker( $color, $factor = 30 ) {
818 $base = wc_rgb_from_hex( $color );
819 $color = '#';
820
821 foreach ( $base as $k => $v ) {
822 $amount = $v / 100;
823 $amount = round( $amount * $factor );
824 $new_decimal = $v - $amount;
825
826 $new_hex_component = dechex( $new_decimal );
827 if ( strlen( $new_hex_component ) < 2 ) {
828 $new_hex_component = '0' . $new_hex_component;
829 }
830 $color .= $new_hex_component;
831 }
832
833 return $color;
834 }
835 }
836
837 if ( ! function_exists( 'wc_hex_lighter' ) ) {
838
839 /**
840 * Make HEX color lighter.
841 *
842 * @param mixed $color Color.
843 * @param int $factor Lighter factor.
844 * Defaults to 30.
845 * @return string
846 */
847 function wc_hex_lighter( $color, $factor = 30 ) {
848 $base = wc_rgb_from_hex( $color );
849 $color = '#';
850
851 foreach ( $base as $k => $v ) {
852 $amount = 255 - $v;
853 $amount = $amount / 100;
854 $amount = round( $amount * $factor );
855 $new_decimal = $v + $amount;
856
857 $new_hex_component = dechex( $new_decimal );
858 if ( strlen( $new_hex_component ) < 2 ) {
859 $new_hex_component = '0' . $new_hex_component;
860 }
861 $color .= $new_hex_component;
862 }
863
864 return $color;
865 }
866 }
867
868 if ( ! function_exists( 'wc_hex_is_light' ) ) {
869
870 /**
871 * Determine whether a hex color is light.
872 *
873 * @param mixed $color Color.
874 * @return bool True if a light color.
875 */
876 function wc_hex_is_light( $color ) {
877 $hex = str_replace( '#', '', $color );
878
879 $c_r = hexdec( substr( $hex, 0, 2 ) );
880 $c_g = hexdec( substr( $hex, 2, 2 ) );
881 $c_b = hexdec( substr( $hex, 4, 2 ) );
882
883 $brightness = ( ( $c_r * 299 ) + ( $c_g * 587 ) + ( $c_b * 114 ) ) / 1000;
884
885 return $brightness > 155;
886 }
887 }
888
889 if ( ! function_exists( 'wc_light_or_dark' ) ) {
890
891 /**
892 * Detect if we should use a light or dark color on a background color.
893 *
894 * @param mixed $color Color.
895 * @param string $dark Darkest reference.
896 * Defaults to '#000000'.
897 * @param string $light Lightest reference.
898 * Defaults to '#FFFFFF'.
899 * @return string
900 */
901 function wc_light_or_dark( $color, $dark = '#000000', $light = '#FFFFFF' ) {
902 return wc_hex_is_light( $color ) ? $dark : $light;
903 }
904 }
905
906 if ( ! function_exists( 'wc_format_hex' ) ) {
907
908 /**
909 * Format string as hex.
910 *
911 * @param string $hex HEX color.
912 * @return string|null
913 */
914 function wc_format_hex( $hex ) {
915 $hex = trim( str_replace( '#', '', $hex ) );
916
917 if ( strlen( $hex ) === 3 ) {
918 $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
919 }
920
921 return $hex ? '#' . $hex : null;
922 }
923 }
924
925 /**
926 * Format the postcode according to the country and length of the postcode.
927 *
928 * @param string $postcode Unformatted postcode.
929 * @param string $country Base country.
930 * @return string
931 */
932 function wc_format_postcode( $postcode, $country ) {
933 $postcode = wc_normalize_postcode( $postcode );
934
935 switch ( $country ) {
936 case 'CA':
937 case 'GB':
938 $postcode = substr_replace( $postcode, ' ', -3, 0 );
939 break;
940 case 'IE':
941 $postcode = substr_replace( $postcode, ' ', 3, 0 );
942 break;
943 case 'BR':
944 case 'PL':
945 $postcode = substr_replace( $postcode, '-', -3, 0 );
946 break;
947 case 'JP':
948 $postcode = substr_replace( $postcode, '-', 3, 0 );
949 break;
950 case 'PT':
951 $postcode = substr_replace( $postcode, '-', 4, 0 );
952 break;
953 case 'US':
954 $postcode = rtrim( substr_replace( $postcode, '-', 5, 0 ), '-' );
955 break;
956 case 'NL':
957 $postcode = substr_replace( $postcode, ' ', 4, 0 );
958 break;
959 }
960
961 return apply_filters( 'woocommerce_format_postcode', trim( $postcode ), $country );
962 }
963
964 /**
965 * Normalize postcodes.
966 *
967 * Remove spaces and convert characters to uppercase.
968 *
969 * @since 2.6.0
970 * @param string $postcode Postcode.
971 * @return string
972 */
973 function wc_normalize_postcode( $postcode ) {
974 return preg_replace( '/[\s\-]/', '', trim( wc_strtoupper( $postcode ) ) );
975 }
976
977 /**
978 * Format phone numbers.
979 *
980 * @param string $phone Phone number.
981 * @return string
982 */
983 function wc_format_phone_number( $phone ) {
984 if ( ! WC_Validation::is_phone( $phone ) ) {
985 return '';
986 }
987 return preg_replace( '/[^0-9\+\-\(\)\s]/', '-', preg_replace( '/[\x00-\x1F\x7F-\xFF]/', '', $phone ) );
988 }
989
990 /**
991 * Sanitize phone number.
992 * Allows only numbers and "+" (plus sign).
993 *
994 * @since 3.6.0
995 * @param string $phone Phone number.
996 * @return string
997 */
998 function wc_sanitize_phone_number( $phone ) {
999 return preg_replace( '/[^\d+]/', '', $phone );
1000 }
1001
1002 /**
1003 * Wrapper for mb_strtoupper which see's if supported first.
1004 *
1005 * @since 3.1.0
1006 * @param string $string String to format.
1007 * @return string
1008 */
1009 function wc_strtoupper( $string ) {
1010 return function_exists( 'mb_strtoupper' ) ? mb_strtoupper( $string ) : strtoupper( $string );
1011 }
1012
1013 /**
1014 * Make a string lowercase.
1015 * Try to use mb_strtolower() when available.
1016 *
1017 * @since 2.3
1018 * @param string $string String to format.
1019 * @return string
1020 */
1021 function wc_strtolower( $string ) {
1022 return function_exists( 'mb_strtolower' ) ? mb_strtolower( $string ) : strtolower( $string );
1023 }
1024
1025 /**
1026 * Trim a string and append a suffix.
1027 *
1028 * @param string $string String to trim.
1029 * @param integer $chars Amount of characters.
1030 * Defaults to 200.
1031 * @param string $suffix Suffix.
1032 * Defaults to '...'.
1033 * @return string
1034 */
1035 function wc_trim_string( $string, $chars = 200, $suffix = '...' ) {
1036 if ( strlen( $string ) > $chars ) {
1037 if ( function_exists( 'mb_substr' ) ) {
1038 $string = mb_substr( $string, 0, ( $chars - mb_strlen( $suffix ) ) ) . $suffix;
1039 } else {
1040 $string = substr( $string, 0, ( $chars - strlen( $suffix ) ) ) . $suffix;
1041 }
1042 }
1043 return $string;
1044 }
1045
1046 /**
1047 * Format content to display shortcodes.
1048 *
1049 * @since 2.3.0
1050 * @param string $raw_string Raw string.
1051 * @return string
1052 */
1053 function wc_format_content( $raw_string ) {
1054 return apply_filters( 'woocommerce_format_content', apply_filters( 'woocommerce_short_description', $raw_string ), $raw_string );
1055 }
1056
1057 /**
1058 * Format product short description.
1059 * Adds support for Jetpack Markdown.
1060 *
1061 * @codeCoverageIgnore
1062 * @since 2.4.0
1063 * @param string $content Product short description.
1064 * @return string
1065 */
1066 function wc_format_product_short_description( $content ) {
1067 // Add support for Jetpack Markdown.
1068 if ( class_exists( 'WPCom_Markdown' ) ) {
1069 $markdown = WPCom_Markdown::get_instance();
1070
1071 return wpautop(
1072 $markdown->transform(
1073 $content,
1074 array(
1075 'unslash' => false,
1076 )
1077 )
1078 );
1079 }
1080
1081 return $content;
1082 }
1083
1084 /**
1085 * Formats curency symbols when saved in settings.
1086 *
1087 * @codeCoverageIgnore
1088 * @param string $value Option value.
1089 * @param array $option Option name.
1090 * @param string $raw_value Raw value.
1091 * @return string
1092 */
1093 function wc_format_option_price_separators( $value, $option, $raw_value ) {
1094 return wp_kses_post( $raw_value );
1095 }
1096 add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_price_decimal_sep', 'wc_format_option_price_separators', 10, 3 );
1097 add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_price_thousand_sep', 'wc_format_option_price_separators', 10, 3 );
1098
1099 /**
1100 * Formats decimals when saved in settings.
1101 *
1102 * @codeCoverageIgnore
1103 * @param string $value Option value.
1104 * @param array $option Option name.
1105 * @param string $raw_value Raw value.
1106 * @return string
1107 */
1108 function wc_format_option_price_num_decimals( $value, $option, $raw_value ) {
1109 return is_null( $raw_value ) ? 2 : absint( $raw_value );
1110 }
1111 add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_price_num_decimals', 'wc_format_option_price_num_decimals', 10, 3 );
1112
1113 /**
1114 * Formats hold stock option and sets cron event up.
1115 *
1116 * @codeCoverageIgnore
1117 * @param string $value Option value.
1118 * @param array $option Option name.
1119 * @param string $raw_value Raw value.
1120 * @return string
1121 */
1122 function wc_format_option_hold_stock_minutes( $value, $option, $raw_value ) {
1123 $value = ! empty( $raw_value ) ? absint( $raw_value ) : ''; // Allow > 0 or set to ''.
1124
1125 wp_clear_scheduled_hook( 'woocommerce_cancel_unpaid_orders' );
1126
1127 if ( '' !== $value ) {
1128 wp_schedule_single_event( time() + ( absint( $value ) * 60 ), 'woocommerce_cancel_unpaid_orders' );
1129 }
1130
1131 return $value;
1132 }
1133 add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_hold_stock_minutes', 'wc_format_option_hold_stock_minutes', 10, 3 );
1134
1135 /**
1136 * Sanitize terms from an attribute text based.
1137 *
1138 * @since 2.4.5
1139 * @param string $term Term value.
1140 * @return string
1141 */
1142 function wc_sanitize_term_text_based( $term ) {
1143 return trim( wp_strip_all_tags( wp_unslash( $term ) ) );
1144 }
1145
1146 if ( ! function_exists( 'wc_make_numeric_postcode' ) ) {
1147 /**
1148 * Make numeric postcode.
1149 *
1150 * Converts letters to numbers so we can do a simple range check on postcodes.
1151 * E.g. PE30 becomes 16050300 (P = 16, E = 05, 3 = 03, 0 = 00)
1152 *
1153 * @since 2.6.0
1154 * @param string $postcode Regular postcode.
1155 * @return string
1156 */
1157 function wc_make_numeric_postcode( $postcode ) {
1158 $postcode = str_replace( array( ' ', '-' ), '', $postcode );
1159 $postcode_length = strlen( $postcode );
1160 $letters_to_numbers = array_merge( array( 0 ), range( 'A', 'Z' ) );
1161 $letters_to_numbers = array_flip( $letters_to_numbers );
1162 $numeric_postcode = '';
1163
1164 for ( $i = 0; $i < $postcode_length; $i ++ ) {
1165 if ( is_numeric( $postcode[ $i ] ) ) {
1166 $numeric_postcode .= str_pad( $postcode[ $i ], 2, '0', STR_PAD_LEFT );
1167 } elseif ( isset( $letters_to_numbers[ $postcode[ $i ] ] ) ) {
1168 $numeric_postcode .= str_pad( $letters_to_numbers[ $postcode[ $i ] ], 2, '0', STR_PAD_LEFT );
1169 } else {
1170 $numeric_postcode .= '00';
1171 }
1172 }
1173
1174 return $numeric_postcode;
1175 }
1176 }
1177
1178 /**
1179 * Format the stock amount ready for display based on settings.
1180 *
1181 * @since 3.0.0
1182 * @param WC_Product $product Product object for which the stock you need to format.
1183 * @return string
1184 */
1185 function wc_format_stock_for_display( $product ) {
1186 $display = __( 'In stock', 'woocommerce' );
1187 $stock_amount = $product->get_stock_quantity();
1188
1189 switch ( get_option( 'woocommerce_stock_format' ) ) {
1190 case 'low_amount':
1191 if ( $stock_amount <= get_option( 'woocommerce_notify_low_stock_amount' ) ) {
1192 /* translators: %s: stock amount */
1193 $display = sprintf( __( 'Only %s left in stock', 'woocommerce' ), wc_format_stock_quantity_for_display( $stock_amount, $product ) );
1194 }
1195 break;
1196 case '':
1197 /* translators: %s: stock amount */
1198 $display = sprintf( __( '%s in stock', 'woocommerce' ), wc_format_stock_quantity_for_display( $stock_amount, $product ) );
1199 break;
1200 }
1201
1202 if ( $product->backorders_allowed() && $product->backorders_require_notification() ) {
1203 $display .= ' ' . __( '(can be backordered)', 'woocommerce' );
1204 }
1205
1206 return $display;
1207 }
1208
1209 /**
1210 * Format the stock quantity ready for display.
1211 *
1212 * @since 3.0.0
1213 * @param int $stock_quantity Stock quantity.
1214 * @param WC_Product $product Product instance so that we can pass through the filters.
1215 * @return string
1216 */
1217 function wc_format_stock_quantity_for_display( $stock_quantity, $product ) {
1218 return apply_filters( 'woocommerce_format_stock_quantity', $stock_quantity, $product );
1219 }
1220
1221 /**
1222 * Format a sale price for display.
1223 *
1224 * @since 3.0.0
1225 * @param string $regular_price Regular price.
1226 * @param string $sale_price Sale price.
1227 * @return string
1228 */
1229 function wc_format_sale_price( $regular_price, $sale_price ) {
1230 $price = '<del>' . ( is_numeric( $regular_price ) ? wc_price( $regular_price ) : $regular_price ) . '</del> <ins>' . ( is_numeric( $sale_price ) ? wc_price( $sale_price ) : $sale_price ) . '</ins>';
1231 return apply_filters( 'woocommerce_format_sale_price', $price, $regular_price, $sale_price );
1232 }
1233
1234 /**
1235 * Format a price range for display.
1236 *
1237 * @param string $from Price from.
1238 * @param string $to Price to.
1239 * @return string
1240 */
1241 function wc_format_price_range( $from, $to ) {
1242 /* translators: 1: price from 2: price to */
1243 $price = sprintf( _x( '%1$s &ndash; %2$s', 'Price range: from-to', 'woocommerce' ), is_numeric( $from ) ? wc_price( $from ) : $from, is_numeric( $to ) ? wc_price( $to ) : $to );
1244 return apply_filters( 'woocommerce_format_price_range', $price, $from, $to );
1245 }
1246
1247 /**
1248 * Format a weight for display.
1249 *
1250 * @since 3.0.0
1251 * @param float $weight Weight.
1252 * @return string
1253 */
1254 function wc_format_weight( $weight ) {
1255 $weight_string = wc_format_localized_decimal( $weight );
1256
1257 if ( ! empty( $weight_string ) ) {
1258 $weight_string .= ' ' . get_option( 'woocommerce_weight_unit' );
1259 } else {
1260 $weight_string = __( 'N/A', 'woocommerce' );
1261 }
1262
1263 return apply_filters( 'woocommerce_format_weight', $weight_string, $weight );
1264 }
1265
1266 /**
1267 * Format dimensions for display.
1268 *
1269 * @since 3.0.0
1270 * @param array $dimensions Array of dimensions.
1271 * @return string
1272 */
1273 function wc_format_dimensions( $dimensions ) {
1274 $dimension_string = implode( ' &times; ', array_filter( array_map( 'wc_format_localized_decimal', $dimensions ) ) );
1275
1276 if ( ! empty( $dimension_string ) ) {
1277 $dimension_string .= ' ' . get_option( 'woocommerce_dimension_unit' );
1278 } else {
1279 $dimension_string = __( 'N/A', 'woocommerce' );
1280 }
1281
1282 return apply_filters( 'woocommerce_format_dimensions', $dimension_string, $dimensions );
1283 }
1284
1285 /**
1286 * Format a date for output.
1287 *
1288 * @since 3.0.0
1289 * @param WC_DateTime $date Instance of WC_DateTime.
1290 * @param string $format Data format.
1291 * Defaults to the wc_date_format function if not set.
1292 * @return string
1293 */
1294 function wc_format_datetime( $date, $format = '' ) {
1295 if ( ! $format ) {
1296 $format = wc_date_format();
1297 }
1298 if ( ! is_a( $date, 'WC_DateTime' ) ) {
1299 return '';
1300 }
1301 return $date->date_i18n( $format );
1302 }
1303
1304 /**
1305 * Process oEmbeds.
1306 *
1307 * @since 3.1.0
1308 * @param string $content Content.
1309 * @return string
1310 */
1311 function wc_do_oembeds( $content ) {
1312 global $wp_embed;
1313
1314 $content = $wp_embed->autoembed( $content );
1315
1316 return $content;
1317 }
1318
1319 /**
1320 * Get part of a string before :.
1321 *
1322 * Used for example in shipping methods ids where they take the format
1323 * method_id:instance_id
1324 *
1325 * @since 3.2.0
1326 * @param string $string String to extract.
1327 * @return string
1328 */
1329 function wc_get_string_before_colon( $string ) {
1330 return trim( current( explode( ':', (string) $string ) ) );
1331 }
1332
1333 /**
1334 * Array merge and sum function.
1335 *
1336 * Source: https://gist.github.com/Nickology/f700e319cbafab5eaedc
1337 *
1338 * @since 3.2.0
1339 * @return array
1340 */
1341 function wc_array_merge_recursive_numeric() {
1342 $arrays = func_get_args();
1343
1344 // If there's only one array, it's already merged.
1345 if ( 1 === count( $arrays ) ) {
1346 return $arrays[0];
1347 }
1348
1349 // Remove any items in $arrays that are NOT arrays.
1350 foreach ( $arrays as $key => $array ) {
1351 if ( ! is_array( $array ) ) {
1352 unset( $arrays[ $key ] );
1353 }
1354 }
1355
1356 // We start by setting the first array as our final array.
1357 // We will merge all other arrays with this one.
1358 $final = array_shift( $arrays );
1359
1360 foreach ( $arrays as $b ) {
1361 foreach ( $final as $key => $value ) {
1362 // If $key does not exist in $b, then it is unique and can be safely merged.
1363 if ( ! isset( $b[ $key ] ) ) {
1364 $final[ $key ] = $value;
1365 } else {
1366 // If $key is present in $b, then we need to merge and sum numeric values in both.
1367 if ( is_numeric( $value ) && is_numeric( $b[ $key ] ) ) {
1368 // If both values for these keys are numeric, we sum them.
1369 $final[ $key ] = $value + $b[ $key ];
1370 } elseif ( is_array( $value ) && is_array( $b[ $key ] ) ) {
1371 // If both values are arrays, we recursively call ourself.
1372 $final[ $key ] = wc_array_merge_recursive_numeric( $value, $b[ $key ] );
1373 } else {
1374 // If both keys exist but differ in type, then we cannot merge them.
1375 // In this scenario, we will $b's value for $key is used.
1376 $final[ $key ] = $b[ $key ];
1377 }
1378 }
1379 }
1380
1381 // Finally, we need to merge any keys that exist only in $b.
1382 foreach ( $b as $key => $value ) {
1383 if ( ! isset( $final[ $key ] ) ) {
1384 $final[ $key ] = $value;
1385 }
1386 }
1387 }
1388
1389 return $final;
1390 }
1391
1392 /**
1393 * Implode and escape HTML attributes for output.
1394 *
1395 * @since 3.3.0
1396 * @param array $raw_attributes Attribute name value pairs.
1397 * @return string
1398 */
1399 function wc_implode_html_attributes( $raw_attributes ) {
1400 $attributes = array();
1401 foreach ( $raw_attributes as $name => $value ) {
1402 $attributes[] = esc_attr( $name ) . '="' . esc_attr( $value ) . '"';
1403 }
1404 return implode( ' ', $attributes );
1405 }
1406
1407 /**
1408 * Escape JSON for use on HTML or attribute text nodes.
1409 *
1410 * @since 3.5.5
1411 * @param string $json JSON to escape.
1412 * @param bool $html True if escaping for HTML text node, false for attributes. Determines how quotes are handled.
1413 * @return string Escaped JSON.
1414 */
1415 function wc_esc_json( $json, $html = false ) {
1416 return _wp_specialchars(
1417 $json,
1418 $html ? ENT_NOQUOTES : ENT_QUOTES, // Escape quotes in attribute nodes only.
1419 'UTF-8', // json_encode() outputs UTF-8 (really just ASCII), not the blog's charset.
1420 true // Double escape entities: `&amp;` -> `&amp;amp;`.
1421 );
1422 }
1423
1424 /**
1425 * Parse a relative date option from the settings API into a standard format.
1426 *
1427 * @since 3.4.0
1428 * @param mixed $raw_value Value stored in DB.
1429 * @return array Nicely formatted array with number and unit values.
1430 */
1431 function wc_parse_relative_date_option( $raw_value ) {
1432 $periods = array(
1433 'days' => __( 'Day(s)', 'woocommerce' ),
1434 'weeks' => __( 'Week(s)', 'woocommerce' ),
1435 'months' => __( 'Month(s)', 'woocommerce' ),
1436 'years' => __( 'Year(s)', 'woocommerce' ),
1437 );
1438
1439 $value = wp_parse_args(
1440 (array) $raw_value,
1441 array(
1442 'number' => '',
1443 'unit' => 'days',
1444 )
1445 );
1446
1447 $value['number'] = ! empty( $value['number'] ) ? absint( $value['number'] ) : '';
1448
1449 if ( ! in_array( $value['unit'], array_keys( $periods ), true ) ) {
1450 $value['unit'] = 'days';
1451 }
1452
1453 return $value;
1454 }
1455
1456 /**
1457 * Format the endpoint slug, strip out anything not allowed in a url.
1458 *
1459 * @since 3.5.0
1460 * @param string $raw_value The raw value.
1461 * @return string
1462 */
1463 function wc_sanitize_endpoint_slug( $raw_value ) {
1464 return sanitize_title( $raw_value );
1465 }
1466 add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_checkout_pay_endpoint', 'wc_sanitize_endpoint_slug', 10, 1 );
1467 add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_checkout_order_received_endpoint', 'wc_sanitize_endpoint_slug', 10, 1 );
1468 add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_myaccount_add_payment_method_endpoint', 'wc_sanitize_endpoint_slug', 10, 1 );
1469 add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_myaccount_delete_payment_method_endpoint', 'wc_sanitize_endpoint_slug', 10, 1 );
1470 add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_myaccount_set_default_payment_method_endpoint', 'wc_sanitize_endpoint_slug', 10, 1 );
1471 add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_myaccount_orders_endpoint', 'wc_sanitize_endpoint_slug', 10, 1 );
1472 add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_myaccount_view_order_endpoint', 'wc_sanitize_endpoint_slug', 10, 1 );
1473 add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_myaccount_downloads_endpoint', 'wc_sanitize_endpoint_slug', 10, 1 );
1474 add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_myaccount_edit_account_endpoint', 'wc_sanitize_endpoint_slug', 10, 1 );
1475 add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_myaccount_edit_address_endpoint', 'wc_sanitize_endpoint_slug', 10, 1 );
1476 add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_myaccount_payment_methods_endpoint', 'wc_sanitize_endpoint_slug', 10, 1 );
1477 add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_myaccount_lost_password_endpoint', 'wc_sanitize_endpoint_slug', 10, 1 );
1478 add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_logout_endpoint', 'wc_sanitize_endpoint_slug', 10, 1 );
1479