30.
* @return string
*/
function wc_hex_darker( $color, $factor = 30 ) {
$base = wc_rgb_from_hex( $color );
$color = '#';
foreach ( $base as $k => $v ) {
$amount = $v / 100;
$amount = NumberUtil::round( $amount * $factor );
$new_decimal = $v - $amount;
$new_hex_component = dechex( $new_decimal );
if ( strlen( $new_hex_component ) < 2 ) {
$new_hex_component = '0' . $new_hex_component;
}
$color .= $new_hex_component;
}
return $color;
}
}
if ( ! function_exists( 'wc_hex_lighter' ) ) {
/**
* Make HEX color lighter.
*
* @param mixed $color Color.
* @param int $factor Lighter factor.
* Defaults to 30.
* @return string
*/
function wc_hex_lighter( $color, $factor = 30 ) {
$base = wc_rgb_from_hex( $color );
$color = '#';
foreach ( $base as $k => $v ) {
$amount = 255 - $v;
$amount = $amount / 100;
$amount = NumberUtil::round( $amount * $factor );
$new_decimal = $v + $amount;
$new_hex_component = dechex( $new_decimal );
if ( strlen( $new_hex_component ) < 2 ) {
$new_hex_component = '0' . $new_hex_component;
}
$color .= $new_hex_component;
}
return $color;
}
}
if ( ! function_exists( 'wc_hex_is_light' ) ) {
/**
* Determine whether a hex color is light.
*
* @param mixed $color Color.
* @return bool True if a light color.
*/
function wc_hex_is_light( $color ) {
$hex = str_replace( '#', '', $color ?? '' );
$c_r = hexdec( substr( $hex, 0, 2 ) );
$c_g = hexdec( substr( $hex, 2, 2 ) );
$c_b = hexdec( substr( $hex, 4, 2 ) );
$brightness = ( ( $c_r * 299 ) + ( $c_g * 587 ) + ( $c_b * 114 ) ) / 1000;
return $brightness > 155;
}
}
if ( ! function_exists( 'wc_light_or_dark' ) ) {
/**
* Detect if we should use a light or dark color on a background color.
*
* @param mixed $color Color.
* @param string $dark Darkest reference.
* Defaults to '#000000'.
* @param string $light Lightest reference.
* Defaults to '#FFFFFF'.
* @return string
*/
function wc_light_or_dark( $color, $dark = '#000000', $light = '#FFFFFF' ) {
return wc_hex_is_light( $color ) ? $dark : $light;
}
}
if ( ! function_exists( 'wc_format_hex' ) ) {
/**
* Format string as hex.
*
* @param string $hex HEX color.
* @return string|null
*/
function wc_format_hex( $hex ) {
$hex = trim( str_replace( '#', '', $hex ?? '' ) );
if ( strlen( $hex ) === 3 ) {
$hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2];
}
return $hex ? '#' . $hex : null;
}
}
/**
* Format the postcode according to the country and length of the postcode.
*
* @param string $postcode Unformatted postcode.
* @param string $country Base country.
* @return string
*/
function wc_format_postcode( $postcode, $country ) {
$postcode = wc_normalize_postcode( $postcode ?? '' );
switch ( $country ) {
case 'SE':
$postcode = substr_replace( $postcode, ' ', -2, 0 );
break;
case 'CA':
case 'GB':
$postcode = substr_replace( $postcode, ' ', -3, 0 );
break;
case 'IE':
$postcode = substr_replace( $postcode, ' ', 3, 0 );
break;
case 'BR':
case 'PL':
$postcode = substr_replace( $postcode, '-', -3, 0 );
break;
case 'JP':
$postcode = substr_replace( $postcode, '-', 3, 0 );
break;
case 'PT':
$postcode = substr_replace( $postcode, '-', 4, 0 );
break;
case 'PR':
case 'US':
$postcode = rtrim( substr_replace( $postcode, '-', 5, 0 ), '-' );
break;
case 'NL':
$postcode = substr_replace( $postcode, ' ', 4, 0 );
break;
case 'LV':
$postcode = preg_replace( '/^(LV)?-?(\d+)$/', 'LV-${2}', $postcode );
break;
case 'CZ':
case 'SK':
$postcode = preg_replace( "/^({$country})-?(\d+)$/", '${1}-${2}', $postcode );
$postcode = substr_replace( $postcode, ' ', -2, 0 );
break;
case 'DK':
$postcode = preg_replace( '/^(DK)(.+)$/', '${1}-${2}', $postcode );
break;
}
return apply_filters( 'woocommerce_format_postcode', trim( $postcode ), $country );
}
/**
* Normalize postcodes.
*
* Remove spaces and convert characters to uppercase.
*
* @since 2.6.0
* @param string $postcode Postcode.
* @return string
*/
function wc_normalize_postcode( $postcode ) {
return preg_replace( '/[\s\-]/', '', trim( wc_strtoupper( $postcode ?? '' ) ) );
}
/**
* Format phone numbers.
*
* @param string $phone Phone number.
* @return string
*/
function wc_format_phone_number( $phone ) {
$phone = $phone ?? '';
if ( ! WC_Validation::is_phone( $phone ) ) {
return '';
}
return preg_replace( '/[^0-9\+\-\(\)\s]/', '-', preg_replace( '/[\x00-\x1F\x7F-\xFF]/', '', $phone ) );
}
/**
* Sanitize phone number.
* Allows only numbers and "+" (plus sign).
*
* @since 3.6.0
* @param string $phone Phone number.
* @return string
*/
function wc_sanitize_phone_number( $phone ) {
return preg_replace( '/[^\d+]/', '', $phone ?? '' );
}
/**
* Wrapper for mb_strtoupper which see's if supported first.
*
* @since 3.1.0
* @param string $string String to format.
* @return string
*/
function wc_strtoupper( $string ) {
$string = $string ?? '';
return function_exists( 'mb_strtoupper' ) ? mb_strtoupper( $string ) : strtoupper( $string );
}
/**
* Make a string lowercase.
* Try to use mb_strtolower() when available.
*
* @since 2.3
* @param string $string String to format.
* @return string
*/
function wc_strtolower( $string ) {
$string = $string ?? '';
return function_exists( 'mb_strtolower' ) ? mb_strtolower( $string ) : strtolower( $string );
}
/**
* Trim a string and append a suffix.
*
* @param string $string String to trim.
* @param integer $chars Amount of characters.
* Defaults to 200.
* @param string $suffix Suffix.
* Defaults to '...'.
* @return string
*/
function wc_trim_string( $string, $chars = 200, $suffix = '...' ) {
$string = $string ?? '';
if ( strlen( $string ) > $chars ) {
if ( function_exists( 'mb_substr' ) ) {
$string = mb_substr( $string, 0, ( $chars - mb_strlen( $suffix ) ) ) . $suffix;
} else {
$string = substr( $string, 0, ( $chars - strlen( $suffix ) ) ) . $suffix;
}
}
return $string;
}
/**
* Format content to display shortcodes.
*
* @since 2.3.0
* @param string $raw_string Raw string.
* @return string
*/
function wc_format_content( $raw_string ) {
$raw_string = $raw_string ?? '';
return apply_filters( 'woocommerce_format_content', apply_filters( 'woocommerce_short_description', $raw_string ), $raw_string );
}
/**
* Format product short description.
* Adds support for Jetpack Markdown.
*
* @codeCoverageIgnore
* @since 2.4.0
* @param string $content Product short description.
* @return string
*/
function wc_format_product_short_description( $content ) {
// Add support for Jetpack Markdown.
if ( class_exists( 'WPCom_Markdown' ) ) {
$markdown = WPCom_Markdown::get_instance();
return wpautop(
$markdown->transform(
$content,
array(
'unslash' => false,
)
)
);
}
return $content;
}
/**
* Formats curency symbols when saved in settings.
*
* @codeCoverageIgnore
* @param string $value Option value.
* @param array $option Option name.
* @param string $raw_value Raw value.
* @return string
*/
function wc_format_option_price_separators( $value, $option, $raw_value ) {
return wp_kses_post( $raw_value ?? '' );
}
add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_price_decimal_sep', 'wc_format_option_price_separators', 10, 3 );
add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_price_thousand_sep', 'wc_format_option_price_separators', 10, 3 );
/**
* Formats decimals when saved in settings.
*
* @codeCoverageIgnore
* @param string $value Option value.
* @param array $option Option name.
* @param string $raw_value Raw value.
* @return string
*/
function wc_format_option_price_num_decimals( $value, $option, $raw_value ) {
return is_null( $raw_value ) ? 2 : absint( $raw_value );
}
add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_price_num_decimals', 'wc_format_option_price_num_decimals', 10, 3 );
/**
* Formats hold stock option and sets cron event up.
*
* @codeCoverageIgnore
* @param string $value Option value.
* @param array $option Option name.
* @param string $raw_value Raw value.
* @return string
*/
function wc_format_option_hold_stock_minutes( $value, $option, $raw_value ) {
$value = ! empty( $raw_value ) ? absint( $raw_value ) : ''; // Allow > 0 or set to ''.
wp_clear_scheduled_hook( 'woocommerce_cancel_unpaid_orders' );
if ( '' !== $value ) {
$cancel_unpaid_interval = apply_filters( 'woocommerce_cancel_unpaid_orders_interval_minutes', absint( $value ) );
wp_schedule_single_event( time() + ( absint( $cancel_unpaid_interval ) * 60 ), 'woocommerce_cancel_unpaid_orders' );
}
return $value;
}
add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_hold_stock_minutes', 'wc_format_option_hold_stock_minutes', 10, 3 );
/**
* Sanitize terms from an attribute text based.
*
* @since 2.4.5
* @param string $term Term value.
* @return string
*/
function wc_sanitize_term_text_based( $term ) {
return trim( wp_strip_all_tags( wp_unslash( $term ?? '' ) ) );
}
if ( ! function_exists( 'wc_make_numeric_postcode' ) ) {
/**
* Make numeric postcode.
*
* Converts letters to numbers so we can do a simple range check on postcodes.
* E.g. PE30 becomes 16050300 (P = 16, E = 05, 3 = 03, 0 = 00)
*
* @since 2.6.0
* @param string $postcode Regular postcode.
* @return string
*/
function wc_make_numeric_postcode( $postcode ) {
$postcode = str_replace( array( ' ', '-' ), '', $postcode ?? '' );
$postcode_length = strlen( $postcode );
$letters_to_numbers = array_merge( array( 0 ), range( 'A', 'Z' ) );
$letters_to_numbers = array_flip( $letters_to_numbers );
$numeric_postcode = '';
for ( $i = 0; $i < $postcode_length; $i ++ ) {
if ( is_numeric( $postcode[ $i ] ) ) {
$numeric_postcode .= str_pad( $postcode[ $i ], 2, '0', STR_PAD_LEFT );
} elseif ( isset( $letters_to_numbers[ $postcode[ $i ] ] ) ) {
$numeric_postcode .= str_pad( $letters_to_numbers[ $postcode[ $i ] ], 2, '0', STR_PAD_LEFT );
} else {
$numeric_postcode .= '00';
}
}
return $numeric_postcode;
}
}
/**
* Format the stock amount ready for display based on settings.
*
* @since 3.0.0
* @param WC_Product $product Product object for which the stock you need to format.
* @return string
*/
function wc_format_stock_for_display( $product ) {
$display = __( 'In stock', 'woocommerce' );
$stock_amount = $product->get_stock_quantity();
switch ( get_option( 'woocommerce_stock_format' ) ) {
case 'low_amount':
if ( $stock_amount <= wc_get_low_stock_amount( $product ) ) {
/* translators: %s: stock amount */
$display = sprintf( __( 'Only %s left in stock', 'woocommerce' ), wc_format_stock_quantity_for_display( $stock_amount, $product ) );
}
break;
case '':
/* translators: %s: stock amount */
$display = sprintf( __( '%s in stock', 'woocommerce' ), wc_format_stock_quantity_for_display( $stock_amount, $product ) );
break;
}
if ( $product->backorders_allowed() && $product->backorders_require_notification() ) {
$display .= ' ' . __( '(can be backordered)', 'woocommerce' );
}
return $display;
}
/**
* Format the stock quantity ready for display.
*
* @since 3.0.0
* @param int $stock_quantity Stock quantity.
* @param WC_Product $product Product instance so that we can pass through the filters.
* @return string
*/
function wc_format_stock_quantity_for_display( $stock_quantity, $product ) {
return apply_filters( 'woocommerce_format_stock_quantity', $stock_quantity, $product );
}
/**
* Format a sale price for display.
*
* @since 3.0.0
* @param string $regular_price Regular price.
* @param string $sale_price Sale price.
* @return string
*/
function wc_format_sale_price( $regular_price, $sale_price ) {
// Format the prices.
$formatted_regular_price = is_numeric( $regular_price ) ? wc_price( $regular_price ) : $regular_price;
$formatted_sale_price = is_numeric( $sale_price ) ? wc_price( $sale_price ) : $sale_price;
// Strikethrough pricing.
$price = '' . $formatted_regular_price . ' ';
// For accessibility (a11y) we'll also display that information to screen readers.
$price .= '';
// translators: %s is a product's regular price.
$price .= esc_html( sprintf( __( 'Original price was: %s.', 'woocommerce' ), wp_strip_all_tags( $formatted_regular_price ) ) );
$price .= '';
// Add the sale price.
$price .= '' . $formatted_sale_price . '';
// For accessibility (a11y) we'll also display that information to screen readers.
$price .= '';
// translators: %s is a product's current (sale) price.
$price .= esc_html( sprintf( __( 'Current price is: %s.', 'woocommerce' ), wp_strip_all_tags( $formatted_sale_price ) ) );
$price .= '';
return apply_filters( 'woocommerce_format_sale_price', $price, $regular_price, $sale_price );
}
/**
* Format a price range for display.
*
* @param string $from Price from.
* @param string $to Price to.
* @return string
*/
function wc_format_price_range( $from, $to ) {
/* translators: 1: price from 2: price to */
$price = sprintf( _x( '%1$s – %2$s', 'Price range: from-to', 'woocommerce' ), is_numeric( $from ) ? wc_price( $from ) : $from, is_numeric( $to ) ? wc_price( $to ) : $to );
return apply_filters( 'woocommerce_format_price_range', $price, $from, $to );
}
/**
* Format a weight for display.
*
* @since 3.0.0
* @param float $weight Weight.
* @return string
*/
function wc_format_weight( $weight ) {
$weight_string = wc_format_localized_decimal( $weight );
if ( ! empty( $weight_string ) ) {
$weight_label = I18nUtil::get_weight_unit_label( get_option( 'woocommerce_weight_unit' ) );
$weight_string = sprintf(
// translators: 1. A formatted number; 2. A label for a weight unit of measure. E.g. 2.72 kg.
_x( '%1$s %2$s', 'formatted weight', 'woocommerce' ),
$weight_string,
$weight_label
);
} else {
$weight_string = __( 'N/A', 'woocommerce' );
}
return apply_filters( 'woocommerce_format_weight', $weight_string, $weight );
}
/**
* Format dimensions for display.
*
* @since 3.0.0
* @param array $dimensions Array of dimensions.
* @return string
*/
function wc_format_dimensions( $dimensions ) {
$dimension_string = implode( ' × ', array_filter( array_map( 'wc_format_localized_decimal', $dimensions ) ) );
if ( ! empty( $dimension_string ) ) {
$dimension_label = I18nUtil::get_dimensions_unit_label( get_option( 'woocommerce_dimension_unit' ) );
$dimension_string = sprintf(
// translators: 1. A formatted number; 2. A label for a dimensions unit of measure. E.g. 3.14 cm.
_x( '%1$s %2$s', 'formatted dimensions', 'woocommerce' ),
$dimension_string,
$dimension_label
);
} else {
$dimension_string = __( 'N/A', 'woocommerce' );
}
return apply_filters( 'woocommerce_format_dimensions', $dimension_string, $dimensions );
}
/**
* Format a date for output.
*
* @since 3.0.0
* @param WC_DateTime $date Instance of WC_DateTime.
* @param string $format Data format.
* Defaults to the wc_date_format function if not set.
* @return string
*/
function wc_format_datetime( $date, $format = '' ) {
if ( ! $format ) {
$format = wc_date_format();
}
if ( ! is_a( $date, 'WC_DateTime' ) ) {
return '';
}
return $date->date_i18n( $format );
}
/**
* Process oEmbeds.
*
* @since 3.1.0
* @param string $content Content.
* @return string
*/
function wc_do_oembeds( $content ) {
global $wp_embed;
$content = $wp_embed->autoembed( $content ?? '' );
return $content;
}
/**
* Get part of a string before :.
*
* Used for example in shipping methods ids where they take the format
* method_id:instance_id
*
* @since 3.2.0
* @param string $string String to extract.
* @return string
*/
function wc_get_string_before_colon( $string ) {
return trim( current( explode( ':', (string) $string ) ) );
}
/**
* Array merge and sum function.
*
* Source: https://gist.github.com/Nickology/f700e319cbafab5eaedc
*
* @since 3.2.0
* @return array
*/
function wc_array_merge_recursive_numeric() {
$arrays = func_get_args();
// If there's only one array, it's already merged.
if ( 1 === count( $arrays ) ) {
return $arrays[0];
}
// Remove any items in $arrays that are NOT arrays.
foreach ( $arrays as $key => $array ) {
if ( ! is_array( $array ) ) {
unset( $arrays[ $key ] );
}
}
// We start by setting the first array as our final array.
// We will merge all other arrays with this one.
$final = array_shift( $arrays );
foreach ( $arrays as $b ) {
foreach ( $final as $key => $value ) {
// If $key does not exist in $b, then it is unique and can be safely merged.
if ( ! isset( $b[ $key ] ) ) {
$final[ $key ] = $value;
} else {
// If $key is present in $b, then we need to merge and sum numeric values in both.
if ( is_numeric( $value ) && is_numeric( $b[ $key ] ) ) {
// If both values for these keys are numeric, we sum them.
$final[ $key ] = $value + $b[ $key ];
} elseif ( is_array( $value ) && is_array( $b[ $key ] ) ) {
// If both values are arrays, we recursively call ourself.
$final[ $key ] = wc_array_merge_recursive_numeric( $value, $b[ $key ] );
} else {
// If both keys exist but differ in type, then we cannot merge them.
// In this scenario, we will $b's value for $key is used.
$final[ $key ] = $b[ $key ];
}
}
}
// Finally, we need to merge any keys that exist only in $b.
foreach ( $b as $key => $value ) {
if ( ! isset( $final[ $key ] ) ) {
$final[ $key ] = $value;
}
}
}
return $final;
}
/**
* Implode and escape HTML attributes for output.
*
* @since 3.3.0
* @param array $raw_attributes Attribute name value pairs.
* @return string
*/
function wc_implode_html_attributes( $raw_attributes ) {
$attributes = array();
foreach ( $raw_attributes as $name => $value ) {
$attributes[] = esc_attr( $name ) . '="' . esc_attr( $value ) . '"';
}
return implode( ' ', $attributes );
}
/**
* Escape JSON for use on HTML or attribute text nodes.
*
* @since 3.5.5
* @param string $json JSON to escape.
* @param bool $html True if escaping for HTML text node, false for attributes. Determines how quotes are handled.
* @return string Escaped JSON.
*/
function wc_esc_json( $json, $html = false ) {
return _wp_specialchars(
$json,
$html ? ENT_NOQUOTES : ENT_QUOTES, // Escape quotes in attribute nodes only.
'UTF-8', // json_encode() outputs UTF-8 (really just ASCII), not the blog's charset.
true // Double escape entities: `&` -> `&`.
);
}
/**
* Parse a relative date option from the settings API into a standard format.
*
* @since 3.4.0
* @param mixed $raw_value Value stored in DB.
* @return array Nicely formatted array with number and unit values.
*/
function wc_parse_relative_date_option( $raw_value ) {
$periods = array(
'days' => __( 'Day(s)', 'woocommerce' ),
'weeks' => __( 'Week(s)', 'woocommerce' ),
'months' => __( 'Month(s)', 'woocommerce' ),
'years' => __( 'Year(s)', 'woocommerce' ),
);
$value = wp_parse_args(
(array) $raw_value,
array(
'number' => '',
'unit' => 'days',
)
);
$value['number'] = ! empty( $value['number'] ) ? absint( $value['number'] ) : '';
if ( ! in_array( $value['unit'], array_keys( $periods ), true ) ) {
$value['unit'] = 'days';
}
return $value;
}
/**
* Format the endpoint slug, strip out anything not allowed in a url.
*
* @since 3.5.0
* @param string $raw_value The raw value.
* @return string
*/
function wc_sanitize_endpoint_slug( $raw_value ) {
return sanitize_title( $raw_value ?? '' );
}
add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_checkout_pay_endpoint', 'wc_sanitize_endpoint_slug', 10, 1 );
add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_checkout_order_received_endpoint', 'wc_sanitize_endpoint_slug', 10, 1 );
add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_myaccount_add_payment_method_endpoint', 'wc_sanitize_endpoint_slug', 10, 1 );
add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_myaccount_delete_payment_method_endpoint', 'wc_sanitize_endpoint_slug', 10, 1 );
add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_myaccount_set_default_payment_method_endpoint', 'wc_sanitize_endpoint_slug', 10, 1 );
add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_myaccount_orders_endpoint', 'wc_sanitize_endpoint_slug', 10, 1 );
add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_myaccount_view_order_endpoint', 'wc_sanitize_endpoint_slug', 10, 1 );
add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_myaccount_downloads_endpoint', 'wc_sanitize_endpoint_slug', 10, 1 );
add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_myaccount_edit_account_endpoint', 'wc_sanitize_endpoint_slug', 10, 1 );
add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_myaccount_edit_address_endpoint', 'wc_sanitize_endpoint_slug', 10, 1 );
add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_myaccount_payment_methods_endpoint', 'wc_sanitize_endpoint_slug', 10, 1 );
add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_myaccount_lost_password_endpoint', 'wc_sanitize_endpoint_slug', 10, 1 );
add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_logout_endpoint', 'wc_sanitize_endpoint_slug', 10, 1 );