/includes/wc-formatting-functions.php

  1. <?php 
  2. /** 
  3. * WooCommerce Formatting 
  4. * 
  5. * Functions for formatting data. 
  6. * 
  7. * @author WooThemes 
  8. * @category Core 
  9. * @package WooCommerce/Functions 
  10. * @version 2.1.0 
  11. */ 
  12.  
  13. if ( ! defined( 'ABSPATH' ) ) { 
  14. exit; // Exit if accessed directly 
  15.  
  16. /** 
  17. * Converts a string (e.g. yes or no) to a bool. 
  18. * @since 3.0.0 
  19. * @param string $string 
  20. * @return bool 
  21. */ 
  22. function wc_string_to_bool( $string ) { 
  23. return is_bool( $string ) ? $string : ( 'yes' === $string || 1 === $string || 'true' === $string || '1' === $string ); 
  24.  
  25. /** 
  26. * Converts a bool to a string. 
  27. * @since 3.0.0 
  28. * @param bool $bool 
  29. * @return string yes or no 
  30. */ 
  31. function wc_bool_to_string( $bool ) { 
  32. if ( ! is_bool( $bool ) ) { 
  33. $bool = wc_string_to_bool( $bool ); 
  34. return true === $bool ? 'yes' : 'no'; 
  35.  
  36. /** 
  37. * Explode a string into an array by $delimiter and remove empty values. 
  38. * @since 3.0.0 
  39. * @param string $string 
  40. * @param string $delimiter 
  41. * @return array 
  42. */ 
  43. function wc_string_to_array( $string, $delimiter = ', ' ) { 
  44. return is_array( $string ) ? $string : array_filter( explode( $delimiter, $string ) ); 
  45.  
  46. /** 
  47. * Sanitize taxonomy names. Slug format (no spaces, lowercase). 
  48. * 
  49. * urldecode is used to reverse munging of UTF8 characters. 
  50. * 
  51. * @param mixed $taxonomy 
  52. * @return string 
  53. */ 
  54. function wc_sanitize_taxonomy_name( $taxonomy ) { 
  55. return apply_filters( 'sanitize_taxonomy_name', urldecode( sanitize_title( urldecode( $taxonomy ) ) ), $taxonomy ); 
  56.  
  57. /** 
  58. * Sanitize permalink values before insertion into DB. 
  59. * 
  60. * Cannot use wc_clean because it sometimes strips % chars and breaks the user's setting. 
  61. * 
  62. * @since 2.6.0 
  63. * @param string $value 
  64. * @return string 
  65. */ 
  66. function wc_sanitize_permalink( $value ) { 
  67. global $wpdb; 
  68.  
  69. $value = $wpdb->strip_invalid_text_for_column( $wpdb->options, 'option_value', $value ); 
  70.  
  71. if ( is_wp_error( $value ) ) { 
  72. $value = ''; 
  73.  
  74. $value = esc_url_raw( $value ); 
  75. $value = str_replace( 'http://', '', $value ); 
  76. return untrailingslashit( $value ); 
  77.  
  78. /** 
  79. * Gets the filename part of a download URL. 
  80. * 
  81. * @param string $file_url 
  82. * @return string 
  83. */ 
  84. function wc_get_filename_from_url( $file_url ) { 
  85. $parts = parse_url( $file_url ); 
  86. if ( isset( $parts['path'] ) ) { 
  87. return basename( $parts['path'] ); 
  88.  
  89. /** 
  90. * Normalise dimensions, unify to cm then convert to wanted unit value. 
  91. * 
  92. * Usage: 
  93. * wc_get_dimension(55, 'in'); 
  94. * wc_get_dimension(55, 'in', 'm'); 
  95. * 
  96. * @param int|float $dimension 
  97. * @param string $to_unit 'in', 'm', 'cm', 'm' 
  98. * @param string $from_unit (optional) 'in', 'm', 'cm', 'm' 
  99. * @return float 
  100. */ 
  101. function wc_get_dimension( $dimension, $to_unit, $from_unit = '' ) { 
  102. $to_unit = strtolower( $to_unit ); 
  103.  
  104. if ( empty( $from_unit ) ) { 
  105. $from_unit = strtolower( get_option( 'woocommerce_dimension_unit' ) ); 
  106.  
  107. // Unify all units to cm first. 
  108. if ( $from_unit !== $to_unit ) { 
  109. switch ( $from_unit ) { 
  110. case 'in' : 
  111. $dimension *= 2.54; 
  112. break; 
  113. case 'm' : 
  114. $dimension *= 100; 
  115. break; 
  116. case 'mm' : 
  117. $dimension *= 0.1; 
  118. break; 
  119. case 'yd' : 
  120. $dimension *= 91.44; 
  121. break; 
  122.  
  123. // Output desired unit. 
  124. switch ( $to_unit ) { 
  125. case 'in' : 
  126. $dimension *= 0.3937; 
  127. break; 
  128. case 'm' : 
  129. $dimension *= 0.01; 
  130. break; 
  131. case 'mm' : 
  132. $dimension *= 10; 
  133. break; 
  134. case 'yd' : 
  135. $dimension *= 0.010936133; 
  136. break; 
  137.  
  138. return ( $dimension < 0 ) ? 0 : $dimension; 
  139.  
  140. /** 
  141. * Normalise weights, unify to kg then convert to wanted unit value. 
  142. * 
  143. * Usage: 
  144. * wc_get_weight(55, 'kg'); 
  145. * wc_get_weight(55, 'kg', 'lbs'); 
  146. * 
  147. * @param int|float $weight 
  148. * @param string $to_unit 'g', 'kg', 'lbs', 'oz' 
  149. * @param string $from_unit (optional) 'g', 'kg', 'lbs', 'oz' 
  150. * @return float 
  151. */ 
  152. function wc_get_weight( $weight, $to_unit, $from_unit = '' ) { 
  153. $weight = (float) $weight; 
  154. $to_unit = strtolower( $to_unit ); 
  155.  
  156. if ( empty( $from_unit ) ) { 
  157. $from_unit = strtolower( get_option( 'woocommerce_weight_unit' ) ); 
  158.  
  159. // Unify all units to kg first. 
  160. if ( $from_unit !== $to_unit ) { 
  161. switch ( $from_unit ) { 
  162. case 'g' : 
  163. $weight *= 0.001; 
  164. break; 
  165. case 'lbs' : 
  166. $weight *= 0.453592; 
  167. break; 
  168. case 'oz' : 
  169. $weight *= 0.0283495; 
  170. break; 
  171.  
  172. // Output desired unit. 
  173. switch ( $to_unit ) { 
  174. case 'g' : 
  175. $weight *= 1000; 
  176. break; 
  177. case 'lbs' : 
  178. $weight *= 2.20462; 
  179. break; 
  180. case 'oz' : 
  181. $weight *= 35.274; 
  182. break; 
  183.  
  184. return ( $weight < 0 ) ? 0 : $weight; 
  185.  
  186. /** 
  187. * Trim trailing zeros off prices. 
  188. * 
  189. * @param mixed $price 
  190. * @return string 
  191. */ 
  192. function wc_trim_zeros( $price ) { 
  193. return preg_replace( '/' . preg_quote( wc_get_price_decimal_separator(), '/' ) . '0++$/', '', $price ); 
  194.  
  195. /** 
  196. * Round a tax amount. 
  197. * 
  198. * @param mixed $tax 
  199. * @return double 
  200. */ 
  201. function wc_round_tax_total( $tax ) { 
  202. $dp = wc_get_price_decimals(); 
  203.  
  204. // @codeCoverageIgnoreStart 
  205. if ( version_compare( phpversion(), '5.3', '<' ) ) { 
  206. $rounded_tax = round( $tax, $dp ); 
  207. } else { 
  208. // @codeCoverageIgnoreEnd 
  209. $rounded_tax = round( $tax, $dp, WC_TAX_ROUNDING_MODE ); 
  210. return apply_filters( 'wc_round_tax_total', $rounded_tax, $tax, $dp, WC_TAX_ROUNDING_MODE ); 
  211.  
  212. /** 
  213. * Make a refund total negative. 
  214. * @return float 
  215. */ 
  216. function wc_format_refund_total( $amount ) { 
  217. return $amount * -1; 
  218.  
  219. /** 
  220. * Format decimal numbers ready for DB storage. 
  221. * 
  222. * Sanitize, remove locale formatting, and optionally round + trim off zeros. 
  223. * 
  224. * @param float|string $number Expects either a float or a string with a decimal separator only (no thousands) 
  225. * @param mixed $dp number of decimal points to use, blank to use woocommerce_price_num_decimals, or false to avoid all rounding. 
  226. * @param bool $trim_zeros from end of string 
  227. * @return string 
  228. */ 
  229. function wc_format_decimal( $number, $dp = false, $trim_zeros = false ) { 
  230. $locale = localeconv(); 
  231. $decimals = array( wc_get_price_decimal_separator(), $locale['decimal_point'], $locale['mon_decimal_point'] ); 
  232.  
  233. // Remove locale from string. 
  234. if ( ! is_float( $number ) ) { 
  235. $number = wc_clean( str_replace( $decimals, '.', $number ) ); 
  236. $number = preg_replace( '/[^0-9\., -]/', '', $number ); 
  237.  
  238. if ( false !== $dp ) { 
  239. $dp = intval( '' == $dp ? wc_get_price_decimals() : $dp ); 
  240. $number = number_format( floatval( $number ), $dp, '.', '' ); 
  241.  
  242. // DP is false - don't use number format, just return a string in our format 
  243. } elseif ( is_float( $number ) ) { 
  244. $number = wc_clean( str_replace( $decimals, '.', strval( $number ) ) ); 
  245.  
  246. if ( $trim_zeros && strstr( $number, '.' ) ) { 
  247. $number = rtrim( rtrim( $number, '0' ), '.' ); 
  248.  
  249. return $number; 
  250.  
  251. /** 
  252. * Convert a float to a string without locale formatting which PHP adds when changing floats to strings. 
  253. * @param float $float 
  254. * @return string 
  255. */ 
  256. function wc_float_to_string( $float ) { 
  257. if ( ! is_float( $float ) ) { 
  258. return $float; 
  259.  
  260. $locale = localeconv(); 
  261. $string = strval( $float ); 
  262. $string = str_replace( $locale['decimal_point'], '.', $string ); 
  263.  
  264. return $string; 
  265.  
  266. /** 
  267. * Format a price with WC Currency Locale settings. 
  268. * @param string $value 
  269. * @return string 
  270. */ 
  271. function wc_format_localized_price( $value ) { 
  272. return str_replace( '.', wc_get_price_decimal_separator(), strval( $value ) ); 
  273.  
  274. /** 
  275. * Format a decimal with PHP Locale settings. 
  276. * @param string $value 
  277. * @return string 
  278. */ 
  279. function wc_format_localized_decimal( $value ) { 
  280. $locale = localeconv(); 
  281. return str_replace( '.', $locale['decimal_point'], strval( $value ) ); 
  282.  
  283. /** 
  284. * Format a coupon code. 
  285. * 
  286. * @since 3.0.0 
  287. * @param string $value 
  288. * @return string 
  289. */ 
  290. function wc_format_coupon_code( $value ) { 
  291. return apply_filters( 'woocommerce_coupon_code', $value ); 
  292.  
  293. /** 
  294. * Clean variables using sanitize_text_field. Arrays are cleaned recursively. 
  295. * Non-scalar values are ignored. 
  296. * @param string|array $var 
  297. * @return string|array 
  298. */ 
  299. function wc_clean( $var ) { 
  300. if ( is_array( $var ) ) { 
  301. return array_map( 'wc_clean', $var ); 
  302. } else { 
  303. return is_scalar( $var ) ? sanitize_text_field( $var ) : $var; 
  304.  
  305. /** 
  306. * Run wc_clean over posted textarea but maintain line breaks. 
  307. * @since 3.0.0 
  308. * @param string $var 
  309. * @return string 
  310. */ 
  311. function wc_sanitize_textarea( $var ) { 
  312. return implode( "\n", array_map( 'wc_clean', explode( "\n", $var ) ) ); 
  313.  
  314. /** 
  315. * Sanitize a string destined to be a tooltip. 
  316. * 
  317. * @since 2.3.10 Tooltips are encoded with htmlspecialchars to prevent XSS. Should not be used in conjunction with esc_attr() 
  318. * @param string $var 
  319. * @return string 
  320. */ 
  321. function wc_sanitize_tooltip( $var ) { 
  322. return htmlspecialchars( wp_kses( html_entity_decode( $var ), array( 
  323. 'br' => array(),  
  324. 'em' => array(),  
  325. 'strong' => array(),  
  326. 'small' => array(),  
  327. 'span' => array(),  
  328. 'ul' => array(),  
  329. 'li' => array(),  
  330. 'ol' => array(),  
  331. 'p' => array(),  
  332. ) ) ); 
  333.  
  334. /** 
  335. * Merge two arrays. 
  336. * 
  337. * @param array $a1 
  338. * @param array $a2 
  339. * @return array 
  340. */ 
  341. function wc_array_overlay( $a1, $a2 ) { 
  342. foreach ( $a1 as $k => $v ) { 
  343. if ( ! array_key_exists( $k, $a2 ) ) { 
  344. continue; 
  345. if ( is_array( $v ) && is_array( $a2[ $k ] ) ) { 
  346. $a1[ $k ] = wc_array_overlay( $v, $a2[ $k ] ); 
  347. } else { 
  348. $a1[ $k ] = $a2[ $k ]; 
  349. return $a1; 
  350.  
  351. /** 
  352. * Formats a stock amount by running it through a filter. 
  353. * @param int|float $amount 
  354. * @return int|float 
  355. */ 
  356. function wc_stock_amount( $amount ) { 
  357. return apply_filters( 'woocommerce_stock_amount', $amount ); 
  358.  
  359. /** 
  360. * Get the price format depending on the currency position. 
  361. * 
  362. * @return string 
  363. */ 
  364. function get_woocommerce_price_format() { 
  365. $currency_pos = get_option( 'woocommerce_currency_pos' ); 
  366. $format = '%1$s%2$s'; 
  367.  
  368. switch ( $currency_pos ) { 
  369. case 'left' : 
  370. $format = '%1$s%2$s'; 
  371. break; 
  372. case 'right' : 
  373. $format = '%2$s%1$s'; 
  374. break; 
  375. case 'left_space' : 
  376. $format = '%1$s %2$s'; 
  377. break; 
  378. case 'right_space' : 
  379. $format = '%2$s %1$s'; 
  380. break; 
  381.  
  382. return apply_filters( 'woocommerce_price_format', $format, $currency_pos ); 
  383.  
  384. /** 
  385. * Return the thousand separator for prices. 
  386. * @since 2.3 
  387. * @return string 
  388. */ 
  389. function wc_get_price_thousand_separator() { 
  390. $separator = apply_filters( 'wc_get_price_thousand_separator', get_option( 'woocommerce_price_thousand_sep' ) ); 
  391. return stripslashes( $separator ); 
  392.  
  393. /** 
  394. * Return the decimal separator for prices. 
  395. * @since 2.3 
  396. * @return string 
  397. */ 
  398. function wc_get_price_decimal_separator() { 
  399. $separator = apply_filters( 'wc_get_price_decimal_separator', get_option( 'woocommerce_price_decimal_sep' ) ); 
  400. return $separator ? stripslashes( $separator ) : '.'; 
  401.  
  402. /** 
  403. * Return the number of decimals after the decimal point. 
  404. * @since 2.3 
  405. * @return int 
  406. */ 
  407. function wc_get_price_decimals() { 
  408. $decimals = apply_filters( 'wc_get_price_decimals', get_option( 'woocommerce_price_num_decimals', 2 ) ); 
  409. return absint( $decimals ); 
  410.  
  411. /** 
  412. * Format the price with a currency symbol. 
  413. * 
  414. * @param float $price 
  415. * @param array $args (default: array()) 
  416. * @return string 
  417. */ 
  418. function wc_price( $price, $args = array() ) { 
  419. extract( apply_filters( 'wc_price_args', wp_parse_args( $args, array( 
  420. 'ex_tax_label' => false,  
  421. 'currency' => '',  
  422. 'decimal_separator' => wc_get_price_decimal_separator(),  
  423. 'thousand_separator' => wc_get_price_thousand_separator(),  
  424. 'decimals' => wc_get_price_decimals(),  
  425. 'price_format' => get_woocommerce_price_format(),  
  426. ) ) ) ); 
  427.  
  428. $negative = $price < 0; 
  429. $price = apply_filters( 'raw_woocommerce_price', floatval( $negative ? $price * -1 : $price ) ); 
  430. $price = apply_filters( 'formatted_woocommerce_price', number_format( $price, $decimals, $decimal_separator, $thousand_separator ), $price, $decimals, $decimal_separator, $thousand_separator ); 
  431.  
  432. if ( apply_filters( 'woocommerce_price_trim_zeros', false ) && $decimals > 0 ) { 
  433. $price = wc_trim_zeros( $price ); 
  434.  
  435. $formatted_price = ( $negative ? '-' : '' ) . sprintf( $price_format, '<span class="woocommerce-Price-currencySymbol">' . get_woocommerce_currency_symbol( $currency ) . '</span>', $price ); 
  436. $return = '<span class="woocommerce-Price-amount amount">' . $formatted_price . '</span>'; 
  437.  
  438. if ( $ex_tax_label && wc_tax_enabled() ) { 
  439. $return .= ' <small class="woocommerce-Price-taxLabel tax_label">' . WC()->countries->ex_tax_or_vat() . '</small>'; 
  440.  
  441. return apply_filters( 'wc_price', $return, $price, $args ); 
  442.  
  443. /** 
  444. * let_to_num function. 
  445. * 
  446. * This function transforms the php.ini notation for numbers (like '2M') to an integer. 
  447. * 
  448. * @param $size 
  449. * @return int 
  450. */ 
  451. function wc_let_to_num( $size ) { 
  452. $l = substr( $size, -1 ); 
  453. $ret = substr( $size, 0, -1 ); 
  454. switch ( strtoupper( $l ) ) { 
  455. case 'P': 
  456. $ret *= 1024; 
  457. case 'T': 
  458. $ret *= 1024; 
  459. case 'G': 
  460. $ret *= 1024; 
  461. case 'M': 
  462. $ret *= 1024; 
  463. case 'K': 
  464. $ret *= 1024; 
  465. return $ret; 
  466.  
  467. /** 
  468. * WooCommerce Date Format - Allows to change date format for everything WooCommerce. 
  469. * 
  470. * @return string 
  471. */ 
  472. function wc_date_format() { 
  473. return apply_filters( 'woocommerce_date_format', get_option( 'date_format' ) ); 
  474.  
  475. /** 
  476. * WooCommerce Time Format - Allows to change time format for everything WooCommerce. 
  477. * 
  478. * @return string 
  479. */ 
  480. function wc_time_format() { 
  481. return apply_filters( 'woocommerce_time_format', get_option( 'time_format' ) ); 
  482.  
  483. /** 
  484. * Convert mysql datetime to PHP timestamp, forcing UTC. Wrapper for strtotime. 
  485. * 
  486. * Based on wcs_strtotime_dark_knight() from WC Subscriptions by Prospress. 
  487. * 
  488. * @since 3.0.0 
  489. * @return int 
  490. */ 
  491. function wc_string_to_timestamp( $time_string, $from_timestamp = null ) { 
  492. $original_timezone = date_default_timezone_get(); 
  493.  
  494. // @codingStandardsIgnoreStart 
  495. date_default_timezone_set( 'UTC' ); 
  496.  
  497. if ( null === $from_timestamp ) { 
  498. $next_timestamp = strtotime( $time_string ); 
  499. } else { 
  500. $next_timestamp = strtotime( $time_string, $from_timestamp ); 
  501.  
  502. date_default_timezone_set( $original_timezone ); 
  503. // @codingStandardsIgnoreEnd 
  504.  
  505. return $next_timestamp; 
  506.  
  507. /** 
  508. * WooCommerce Timezone - helper to retrieve the timezone string for a site until. 
  509. * a WP core method exists (see https://core.trac.wordpress.org/ticket/24730). 
  510. * 
  511. * Adapted from https://secure.php.net/manual/en/function.timezone-name-from-abbr.php#89155. 
  512. * 
  513. * @since 2.1 
  514. * @return string PHP timezone string for the site 
  515. */ 
  516. function wc_timezone_string() { 
  517.  
  518. // if site timezone string exists, return it 
  519. if ( $timezone = get_option( 'timezone_string' ) ) { 
  520. return $timezone; 
  521.  
  522. // get UTC offset, if it isn't set then return UTC 
  523. if ( 0 === ( $utc_offset = intval( get_option( 'gmt_offset', 0 ) ) ) ) { 
  524. return 'UTC'; 
  525.  
  526. // adjust UTC offset from hours to seconds 
  527. $utc_offset *= 3600; 
  528.  
  529. // attempt to guess the timezone string from the UTC offset 
  530. if ( $timezone = timezone_name_from_abbr( '', $utc_offset ) ) { 
  531. return $timezone; 
  532.  
  533. // last try, guess timezone string manually 
  534. foreach ( timezone_abbreviations_list() as $abbr ) { 
  535. foreach ( $abbr as $city ) { 
  536. if ( (bool) date( 'I' ) === (bool) $city['dst'] && $city['timezone_id'] && intval( $city['offset'] ) === $utc_offset ) { 
  537. return $city['timezone_id']; 
  538.  
  539. // fallback to UTC 
  540. return 'UTC'; 
  541.  
  542. /** 
  543. * Get timezone offset in seconds. 
  544. * 
  545. * @since 3.0.0 
  546. * @return float 
  547. */ 
  548. function wc_timezone_offset() { 
  549. if ( $timezone = get_option( 'timezone_string' ) ) { 
  550. $timezone_object = new DateTimeZone( $timezone ); 
  551. return $timezone_object->getOffset( new DateTime( 'now' ) ); 
  552. } else { 
  553. return floatval( get_option( 'gmt_offset', 0 ) ) * HOUR_IN_SECONDS; 
  554.  
  555. /** 
  556. * Callback which can flatten post meta (gets the first value if it's an array). 
  557. * 
  558. * @since 3.0.0 
  559. * @param array $value 
  560. * @return mixed 
  561. */ 
  562. function wc_flatten_meta_callback( $value ) { 
  563. return is_array( $value ) ? current( $value ) : $value; 
  564.  
  565. if ( ! function_exists( 'wc_rgb_from_hex' ) ) { 
  566.  
  567. /** 
  568. * Hex darker/lighter/contrast functions for colors. 
  569. * 
  570. * @param mixed $color 
  571. * @return string 
  572. */ 
  573. function wc_rgb_from_hex( $color ) { 
  574. $color = str_replace( '#', '', $color ); 
  575. // Convert shorthand colors to full format, e.g. "FFF" -> "FFFFFF" 
  576. $color = preg_replace( '~^(.)(.)(.)$~', '$1$1$2$2$3$3', $color ); 
  577.  
  578. $rgb = array(); 
  579. $rgb['R'] = hexdec( $color{0} . $color{1} ); 
  580. $rgb['G'] = hexdec( $color{2} . $color{3} ); 
  581. $rgb['B'] = hexdec( $color{4} . $color{5} ); 
  582.  
  583. return $rgb; 
  584.  
  585. if ( ! function_exists( 'wc_hex_darker' ) ) { 
  586.  
  587. /** 
  588. * Hex darker/lighter/contrast functions for colors. 
  589. * 
  590. * @param mixed $color 
  591. * @param int $factor (default: 30) 
  592. * @return string 
  593. */ 
  594. function wc_hex_darker( $color, $factor = 30 ) { 
  595. $base = wc_rgb_from_hex( $color ); 
  596. $color = '#'; 
  597.  
  598. foreach ( $base as $k => $v ) { 
  599. $amount = $v / 100; 
  600. $amount = round( $amount * $factor ); 
  601. $new_decimal = $v - $amount; 
  602.  
  603. $new_hex_component = dechex( $new_decimal ); 
  604. if ( strlen( $new_hex_component ) < 2 ) { 
  605. $new_hex_component = "0" . $new_hex_component; 
  606. $color .= $new_hex_component; 
  607.  
  608. return $color; 
  609.  
  610. if ( ! function_exists( 'wc_hex_lighter' ) ) { 
  611.  
  612. /** 
  613. * Hex darker/lighter/contrast functions for colors. 
  614. * 
  615. * @param mixed $color 
  616. * @param int $factor (default: 30) 
  617. * @return string 
  618. */ 
  619. function wc_hex_lighter( $color, $factor = 30 ) { 
  620. $base = wc_rgb_from_hex( $color ); 
  621. $color = '#'; 
  622.  
  623. foreach ( $base as $k => $v ) { 
  624. $amount = 255 - $v; 
  625. $amount = $amount / 100; 
  626. $amount = round( $amount * $factor ); 
  627. $new_decimal = $v + $amount; 
  628.  
  629. $new_hex_component = dechex( $new_decimal ); 
  630. if ( strlen( $new_hex_component ) < 2 ) { 
  631. $new_hex_component = "0" . $new_hex_component; 
  632. $color .= $new_hex_component; 
  633.  
  634. return $color; 
  635.  
  636. if ( ! function_exists( 'wc_light_or_dark' ) ) { 
  637.  
  638. /** 
  639. * Detect if we should use a light or dark color on a background color. 
  640. * 
  641. * @param mixed $color 
  642. * @param string $dark (default: '#000000') 
  643. * @param string $light (default: '#FFFFFF') 
  644. * @return string 
  645. */ 
  646. function wc_light_or_dark( $color, $dark = '#000000', $light = '#FFFFFF' ) { 
  647.  
  648. $hex = str_replace( '#', '', $color ); 
  649.  
  650. $c_r = hexdec( substr( $hex, 0, 2 ) ); 
  651. $c_g = hexdec( substr( $hex, 2, 2 ) ); 
  652. $c_b = hexdec( substr( $hex, 4, 2 ) ); 
  653.  
  654. $brightness = ( ( $c_r * 299 ) + ( $c_g * 587 ) + ( $c_b * 114 ) ) / 1000; 
  655.  
  656. return $brightness > 155 ? $dark : $light; 
  657.  
  658. if ( ! function_exists( 'wc_format_hex' ) ) { 
  659.  
  660. /** 
  661. * Format string as hex. 
  662. * 
  663. * @param string $hex 
  664. * @return string 
  665. */ 
  666. function wc_format_hex( $hex ) { 
  667.  
  668. $hex = trim( str_replace( '#', '', $hex ) ); 
  669.  
  670. if ( strlen( $hex ) == 3 ) { 
  671. $hex = $hex[0] . $hex[0] . $hex[1] . $hex[1] . $hex[2] . $hex[2]; 
  672.  
  673. return $hex ? '#' . $hex : null; 
  674.  
  675. /** 
  676. * Format the postcode according to the country and length of the postcode. 
  677. * 
  678. * @param string $postcode 
  679. * @param string $country 
  680. * @return string Formatted postcode. 
  681. */ 
  682. function wc_format_postcode( $postcode, $country ) { 
  683. $postcode = wc_normalize_postcode( $postcode ); 
  684.  
  685. switch ( $country ) { 
  686. case 'CA' : 
  687. case 'GB' : 
  688. $postcode = trim( substr_replace( $postcode, ' ', -3, 0 ) ); 
  689. break; 
  690. case 'BR' : 
  691. case 'PL' : 
  692. $postcode = substr_replace( $postcode, '-', -3, 0 ); 
  693. break; 
  694. case 'JP' : 
  695. $postcode = substr_replace( $postcode, '-', 3, 0 ); 
  696. break; 
  697. case 'PT' : 
  698. $postcode = substr_replace( $postcode, '-', 4, 0 ); 
  699. break; 
  700. case 'US' : 
  701. $postcode = rtrim( substr_replace( $postcode, '-', 5, 0 ), '-' ); 
  702. break; 
  703.  
  704. return apply_filters( 'woocommerce_format_postcode', $postcode, $country ); 
  705.  
  706. /** 
  707. * Normalize postcodes. 
  708. * 
  709. * Remove spaces and convert characters to uppercase. 
  710. * 
  711. * @since 2.6.0 
  712. * @param string $postcode 
  713. * @return string Sanitized postcode. 
  714. */ 
  715. function wc_normalize_postcode( $postcode ) { 
  716. $postcode = function_exists( 'mb_strtoupper' ) ? mb_strtoupper( $postcode ) : strtoupper( $postcode ); 
  717. return preg_replace( '/[\s\-]/', '', trim( $postcode ) ); 
  718.  
  719. /** 
  720. * format_phone function. 
  721. * 
  722. * @param mixed $tel 
  723. * @return string 
  724. */ 
  725. function wc_format_phone_number( $tel ) { 
  726. return str_replace( '.', '-', $tel ); 
  727.  
  728. /** 
  729. * Make a string lowercase. 
  730. * Try to use mb_strtolower() when available. 
  731. * 
  732. * @since 2.3 
  733. * @param string $string 
  734. * @return string 
  735. */ 
  736. function wc_strtolower( $string ) { 
  737. return function_exists( 'mb_strtolower' ) ? mb_strtolower( $string ) : strtolower( $string ); 
  738.  
  739. /** 
  740. * Trim a string and append a suffix. 
  741. * @param string $string 
  742. * @param integer $chars 
  743. * @param string $suffix 
  744. * @return string 
  745. */ 
  746. function wc_trim_string( $string, $chars = 200, $suffix = '...' ) { 
  747. if ( strlen( $string ) > $chars ) { 
  748. if ( function_exists( 'mb_substr' ) ) { 
  749. $string = mb_substr( $string, 0, ( $chars - mb_strlen( $suffix ) ) ) . $suffix; 
  750. } else { 
  751. $string = substr( $string, 0, ( $chars - strlen( $suffix ) ) ) . $suffix; 
  752. return $string; 
  753.  
  754. /** 
  755. * Format content to display shortcodes. 
  756. * 
  757. * @since 2.3.0 
  758. * @param string $raw_string 
  759. * @return string 
  760. */ 
  761. function wc_format_content( $raw_string ) { 
  762. return apply_filters( 'woocommerce_format_content', apply_filters( 'woocommerce_short_description', $raw_string ), $raw_string ); 
  763.  
  764. /** 
  765. * Format product short description. 
  766. * Adds support for Jetpack Markdown. 
  767. * 
  768. * @since 2.4.0 
  769. * @param string $content 
  770. * @return string 
  771. */ 
  772. function wc_format_product_short_description( $content ) { 
  773. // Add support for Jetpack Markdown 
  774. if ( class_exists( 'WPCom_Markdown' ) ) { 
  775. $markdown = WPCom_Markdown::get_instance(); 
  776.  
  777. return wpautop( $markdown->transform( $content, array( 'unslash' => false ) ) ); 
  778.  
  779. return $content; 
  780.  
  781. /** 
  782. * Formats curency symbols when saved in settings. 
  783. * @param string $value 
  784. * @param array $option 
  785. * @param string $raw_value 
  786. * @return string 
  787. */ 
  788. function wc_format_option_price_separators( $value, $option, $raw_value ) { 
  789. return wp_kses_post( $raw_value ); 
  790. add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_price_decimal_sep', 'wc_format_option_price_separators', 10, 3 ); 
  791. add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_price_thousand_sep', 'wc_format_option_price_separators', 10, 3 ); 
  792.  
  793. /** 
  794. * Formats decimals when saved in settings. 
  795. * @param string $value 
  796. * @param array $option 
  797. * @param string $raw_value 
  798. * @return string 
  799. */ 
  800. function wc_format_option_price_num_decimals( $value, $option, $raw_value ) { 
  801. return is_null( $raw_value ) ? 2 : absint( $raw_value ); 
  802. add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_price_num_decimals', 'wc_format_option_price_num_decimals', 10, 3 ); 
  803.  
  804. /** 
  805. * Formats hold stock option and sets cron event up. 
  806. * @param string $value 
  807. * @param array $option 
  808. * @param string $raw_value 
  809. * @return string 
  810. */ 
  811. function wc_format_option_hold_stock_minutes( $value, $option, $raw_value ) { 
  812. $value = ! empty( $raw_value ) ? absint( $raw_value ) : ''; // Allow > 0 or set to '' 
  813.  
  814. wp_clear_scheduled_hook( 'woocommerce_cancel_unpaid_orders' ); 
  815.  
  816. if ( '' !== $value ) { 
  817. wp_schedule_single_event( time() + ( absint( $value ) * 60 ), 'woocommerce_cancel_unpaid_orders' ); 
  818.  
  819. return $value; 
  820. add_filter( 'woocommerce_admin_settings_sanitize_option_woocommerce_hold_stock_minutes', 'wc_format_option_hold_stock_minutes', 10, 3 ); 
  821.  
  822. /** 
  823. * Sanitize terms from an attribute text based. 
  824. * 
  825. * @since 2.4.5 
  826. * @param string $term 
  827. * @return string 
  828. */ 
  829. function wc_sanitize_term_text_based( $term ) { 
  830. return trim( wp_unslash( strip_tags( $term ) ) ); 
  831.  
  832. if ( ! function_exists( 'wc_make_numeric_postcode' ) ) { 
  833. /** 
  834. * Make numeric postcode. 
  835. * 
  836. * Converts letters to numbers so we can do a simple range check on postcodes. 
  837. * E.g. PE30 becomes 16050300 (P = 16, E = 05, 3 = 03, 0 = 00) 
  838. * 
  839. * @since 2.6.0 
  840. * @param string $postcode Regular postcode 
  841. * @return string 
  842. */ 
  843. function wc_make_numeric_postcode( $postcode ) { 
  844. $postcode = str_replace( array( ' ', '-' ), '', $postcode ); 
  845. $postcode_length = strlen( $postcode ); 
  846. $letters_to_numbers = array_merge( array( 0 ), range( 'A', 'Z' ) ); 
  847. $letters_to_numbers = array_flip( $letters_to_numbers ); 
  848. $numeric_postcode = ''; 
  849.  
  850. for ( $i = 0; $i < $postcode_length; $i ++ ) { 
  851. if ( is_numeric( $postcode[ $i ] ) ) { 
  852. $numeric_postcode .= str_pad( $postcode[ $i ], 2, '0', STR_PAD_LEFT ); 
  853. } elseif ( isset( $letters_to_numbers[ $postcode[ $i ] ] ) ) { 
  854. $numeric_postcode .= str_pad( $letters_to_numbers[ $postcode[ $i ] ], 2, '0', STR_PAD_LEFT ); 
  855. } else { 
  856. $numeric_postcode .= '00'; 
  857.  
  858. return $numeric_postcode; 
  859.  
  860. /** 
  861. * Format the stock amount ready for display based on settings. 
  862. * 
  863. * @since 3.0.0 
  864. * @param WC_Product $product Product object for which the stock you need to format. 
  865. * @return string 
  866. */ 
  867. function wc_format_stock_for_display( $product ) { 
  868. $display = __( 'In stock', 'woocommerce' ); 
  869. $stock_amount = $product->get_stock_quantity(); 
  870.  
  871. switch ( get_option( 'woocommerce_stock_format' ) ) { 
  872. case 'low_amount' : 
  873. if ( $stock_amount <= get_option( 'woocommerce_notify_low_stock_amount' ) ) { 
  874. $display = sprintf( __( 'Only %s left in stock', 'woocommerce' ), wc_format_stock_quantity_for_display( $stock_amount, $product ) ); 
  875. break; 
  876. case '' : 
  877. $display = sprintf( __( '%s in stock', 'woocommerce' ), wc_format_stock_quantity_for_display( $stock_amount, $product ) ); 
  878. break; 
  879.  
  880. if ( $product->backorders_allowed() && $product->backorders_require_notification() ) { 
  881. $display .= ' ' . __( '(can be backordered)', 'woocommerce' ); 
  882.  
  883. return $display; 
  884.  
  885. /** 
  886. * Format the stock quantity ready for display. 
  887. * 
  888. * @since 3.0.0 
  889. * @param int $stock_quantity 
  890. * @param WC_Product $product so that we can pass through the filters. 
  891. * @return string 
  892. */ 
  893. function wc_format_stock_quantity_for_display( $stock_quantity, $product ) { 
  894. return apply_filters( 'woocommerce_format_stock_quantity', $stock_quantity, $product ); 
  895.  
  896. /** 
  897. * Format a sale price for display. 
  898. * @since 3.0.0 
  899. * @param string $regular_price 
  900. * @param string $sale_price 
  901. * @return string 
  902. */ 
  903. function wc_format_sale_price( $regular_price, $sale_price ) { 
  904. $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>'; 
  905. return apply_filters( 'woocommerce_format_sale_price', $price, $regular_price, $sale_price ); 
  906.  
  907. /** 
  908. * Format a price range for display. 
  909. * @param string $from 
  910. * @param string $to 
  911. * @return string 
  912. */ 
  913. function wc_format_price_range( $from, $to ) { 
  914. $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 ); 
  915. return apply_filters( 'woocommerce_format_price_range', $price, $from, $to ); 
  916.  
  917. /** 
  918. * Format a weight for display. 
  919. * 
  920. * @since 3.0.0 
  921. * @param float $weight Weight. 
  922. * @return string 
  923. */ 
  924. function wc_format_weight( $weight ) { 
  925. $weight_string = wc_format_localized_decimal( $weight ); 
  926.  
  927. if ( ! empty( $weight_string ) ) { 
  928. $weight_string .= ' ' . get_option( 'woocommerce_weight_unit' ); 
  929. } else { 
  930. $weight_string = __( 'N/A', 'woocommerce' ); 
  931.  
  932. return apply_filters( 'woocommerce_format_weight', $weight_string, $weight ); 
  933.  
  934. /** 
  935. * Format dimensions for display. 
  936. * 
  937. * @since 3.0.0 
  938. * @param array $dimensions Array of dimensions. 
  939. * @return string 
  940. */ 
  941. function wc_format_dimensions( $dimensions ) { 
  942. $dimension_string = implode( ' x ', array_filter( array_map( 'wc_format_localized_decimal', $dimensions ) ) ); 
  943.  
  944. if ( ! empty( $dimension_string ) ) { 
  945. $dimension_string .= ' ' . get_option( 'woocommerce_dimension_unit' ); 
  946. } else { 
  947. $dimension_string = __( 'N/A', 'woocommerce' ); 
  948.  
  949. return apply_filters( 'woocommerce_format_dimensions', $dimension_string, $dimensions ); 
  950.  
  951. /** 
  952. * Format a date for output. 
  953. * 
  954. * @since 3.0.0 
  955. * @param WC_DateTime $date 
  956. * @param string $format Defaults to the wc_date_format function if not set. 
  957. * @return string 
  958. */ 
  959. function wc_format_datetime( $date, $format = '' ) { 
  960. if ( ! $format ) { 
  961. $format = wc_date_format(); 
  962. if ( ! is_a( $date, 'WC_DateTime' ) ) { 
  963. return ''; 
  964. return $date->date_i18n( $format ); 
.