wc-core-functions.php 65 KB

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