/includes/wc-product-functions.php

  1. <?php 
  2. if ( ! defined( 'ABSPATH' ) ) { 
  3. exit; 
  4.  
  5. /** 
  6. * WooCommerce Product Functions 
  7. * 
  8. * Functions for product specific things. 
  9. * 
  10. * @author WooThemes 
  11. * @category Core 
  12. * @package WooCommerce/Functions 
  13. * @version 3.0.0 
  14. */ 
  15.  
  16. /** 
  17. * Products wrapper for get_posts. 
  18. * 
  19. * This function should be used for product retrieval so that we have a data agnostic 
  20. * way to get a list of products. 
  21. * 
  22. * Args: 
  23. * status array|string List of statuses to find. Default: any. Options: any, draft, pending, private and publish. 
  24. * type array|string Product type, e.g. Default: all. Options: all, simple, external, variable, variation, grouped. 
  25. * parent int post/product parent 
  26. * sku string Limit result set to products with specific SKU. 
  27. * category array Limit result set to products assigned to specific categories by slug 
  28. * e.g. array('hoodie', 'cap', 't-shirt'). 
  29. * tag array Limit result set to products assigned to specific tags (by slug) 
  30. * e.g. array('funky', 'retro', 'designer') 
  31. * shipping_class array Limit results set to products in specific shipping classes (by slug) 
  32. * e.g. array('standard', 'next-day') 
  33. * limit int Maximum of products to retrieve. 
  34. * offset int Offset of products to retrieve. 
  35. * page int Page of products to retrieve. Ignored when using the 'offset' arg. 
  36. * exclude array Product IDs to exclude from the query. 
  37. * orderby string Order by date, title, id, modified, rand etc 
  38. * order string ASC or DESC 
  39. * return string Type of data to return. Allowed values: 
  40. * ids array of Product ids 
  41. * objects array of product objects (default) 
  42. * paginate bool If true, the return value will be an array with values: 
  43. * 'products' => array of data (return value above),  
  44. * 'total' => total number of products matching the query 
  45. * 'max_num_pages' => max number of pages found 
  46. * 
  47. * @since 3.0.0 
  48. * @param array $args Array of args (above) 
  49. * @return array|stdClass Number of pages and an array of product objects if 
  50. * paginate is true, or just an array of values. 
  51. */ 
  52. function wc_get_products( $args ) { 
  53. $args = wp_parse_args( $args, array( 
  54. 'status' => array( 'draft', 'pending', 'private', 'publish' ),  
  55. 'type' => array_merge( array_keys( wc_get_product_types() ) ),  
  56. 'parent' => null,  
  57. 'sku' => '',  
  58. 'category' => array(),  
  59. 'tag' => array(),  
  60. 'limit' => get_option( 'posts_per_page' ),  
  61. 'offset' => null,  
  62. 'page' => 1,  
  63. 'include' => array(),  
  64. 'exclude' => array(),  
  65. 'orderby' => 'date',  
  66. 'order' => 'DESC',  
  67. 'return' => 'objects',  
  68. 'paginate' => false,  
  69. 'shipping_class' => array(),  
  70. ) ); 
  71.  
  72. // Handle some BW compatibility arg names where wp_query args differ in naming. 
  73. $map_legacy = array( 
  74. 'numberposts' => 'limit',  
  75. 'post_status' => 'status',  
  76. 'post_parent' => 'parent',  
  77. 'posts_per_page' => 'limit',  
  78. 'paged' => 'page',  
  79. ); 
  80.  
  81. foreach ( $map_legacy as $from => $to ) { 
  82. if ( isset( $args[ $from ] ) ) { 
  83. $args[ $to ] = $args[ $from ]; 
  84.  
  85. return WC_Data_Store::load( 'product' )->get_products( $args ); 
  86.  
  87. /** 
  88. * Main function for returning products, uses the WC_Product_Factory class. 
  89. * 
  90. * @since 2.2.0 
  91. * 
  92. * @param mixed $the_product Post object or post ID of the product. 
  93. * @param array $deprecated Previously used to pass arguments to the factory, e.g. to force a type. 
  94. * @return WC_Product|null 
  95. */ 
  96. function wc_get_product( $the_product = false, $deprecated = array() ) { 
  97. if ( ! did_action( 'woocommerce_init' ) ) { 
  98. wc_doing_it_wrong( __FUNCTION__, __( 'wc_get_product should not be called before the woocommerce_init action.', 'woocommerce' ), '2.5' ); 
  99. return false; 
  100. if ( ! empty( $deprecated ) ) { 
  101. wc_deprecated_argument( 'args', '3.0', 'Passing args to wc_get_product is deprecated. If you need to force a type, construct the product class directly.' ); 
  102. return WC()->product_factory->get_product( $the_product, $deprecated ); 
  103.  
  104. /** 
  105. * Returns whether or not SKUS are enabled. 
  106. * @return bool 
  107. */ 
  108. function wc_product_sku_enabled() { 
  109. return apply_filters( 'wc_product_sku_enabled', true ); 
  110.  
  111. /** 
  112. * Returns whether or not product weights are enabled. 
  113. * @return bool 
  114. */ 
  115. function wc_product_weight_enabled() { 
  116. return apply_filters( 'wc_product_weight_enabled', true ); 
  117.  
  118. /** 
  119. * Returns whether or not product dimensions (HxWxD) are enabled. 
  120. * @return bool 
  121. */ 
  122. function wc_product_dimensions_enabled() { 
  123. return apply_filters( 'wc_product_dimensions_enabled', true ); 
  124.  
  125. /** 
  126. * Clear all transients cache for product data. 
  127. * 
  128. * @param int $post_id (default: 0) 
  129. */ 
  130. function wc_delete_product_transients( $post_id = 0 ) { 
  131. // Core transients 
  132. $transients_to_clear = array( 
  133. 'wc_products_onsale',  
  134. 'wc_featured_products',  
  135. 'wc_outofstock_count',  
  136. 'wc_low_stock_count',  
  137. ); 
  138.  
  139. // Transient names that include an ID 
  140. $post_transient_names = array( 
  141. 'wc_product_children_',  
  142. 'wc_var_prices_',  
  143. 'wc_related_',  
  144. 'wc_child_has_weight_',  
  145. 'wc_child_has_dimensions_',  
  146. ); 
  147.  
  148. if ( $post_id > 0 ) { 
  149. foreach ( $post_transient_names as $transient ) { 
  150. $transients_to_clear[] = $transient . $post_id; 
  151.  
  152. // Does this product have a parent? 
  153. $product = wc_get_product( $post_id ); 
  154.  
  155. if ( $product && $product->get_parent_id() > 0 ) { 
  156. wc_delete_product_transients( $product->get_parent_id() ); 
  157.  
  158. // Delete transients 
  159. foreach ( $transients_to_clear as $transient ) { 
  160. delete_transient( $transient ); 
  161.  
  162. // Increments the transient version to invalidate cache 
  163. WC_Cache_Helper::get_transient_version( 'product', true ); 
  164.  
  165. do_action( 'woocommerce_delete_product_transients', $post_id ); 
  166.  
  167. /** 
  168. * Function that returns an array containing the IDs of the products that are on sale. 
  169. * 
  170. * @since 2.0 
  171. * @return array 
  172. */ 
  173. function wc_get_product_ids_on_sale() { 
  174. // Load from cache 
  175. $product_ids_on_sale = get_transient( 'wc_products_onsale' ); 
  176.  
  177. // Valid cache found 
  178. if ( false !== $product_ids_on_sale ) { 
  179. return $product_ids_on_sale; 
  180.  
  181. $data_store = WC_Data_Store::load( 'product' ); 
  182. $on_sale_products = $data_store->get_on_sale_products(); 
  183. $product_ids_on_sale = wp_parse_id_list( array_merge( wp_list_pluck( $on_sale_products, 'id' ), array_diff( wp_list_pluck( $on_sale_products, 'parent_id' ), array( 0 ) ) ) ); 
  184.  
  185. set_transient( 'wc_products_onsale', $product_ids_on_sale, DAY_IN_SECONDS * 30 ); 
  186.  
  187. return $product_ids_on_sale; 
  188.  
  189. /** 
  190. * Function that returns an array containing the IDs of the featured products. 
  191. * 
  192. * @since 2.1 
  193. * @return array 
  194. */ 
  195. function wc_get_featured_product_ids() { 
  196. // Load from cache 
  197. $featured_product_ids = get_transient( 'wc_featured_products' ); 
  198.  
  199. // Valid cache found 
  200. if ( false !== $featured_product_ids ) 
  201. return $featured_product_ids; 
  202.  
  203. $data_store = WC_Data_Store::load( 'product' ); 
  204. $featured = $data_store->get_featured_product_ids(); 
  205. $product_ids = array_keys( $featured ); 
  206. $parent_ids = array_values( array_filter( $featured ) ); 
  207. $featured_product_ids = array_unique( array_merge( $product_ids, $parent_ids ) ); 
  208.  
  209. set_transient( 'wc_featured_products', $featured_product_ids, DAY_IN_SECONDS * 30 ); 
  210.  
  211. return $featured_product_ids; 
  212.  
  213. /** 
  214. * Filter to allow product_cat in the permalinks for products. 
  215. * 
  216. * @param string $permalink The existing permalink URL. 
  217. * @param WP_Post $post 
  218. * @return string 
  219. */ 
  220. function wc_product_post_type_link( $permalink, $post ) { 
  221. // Abort if post is not a product. 
  222. if ( 'product' !== $post->post_type ) { 
  223. return $permalink; 
  224.  
  225. // Abort early if the placeholder rewrite tag isn't in the generated URL. 
  226. if ( false === strpos( $permalink, '%' ) ) { 
  227. return $permalink; 
  228.  
  229. // Get the custom taxonomy terms in use by this post. 
  230. $terms = get_the_terms( $post->ID, 'product_cat' ); 
  231.  
  232. if ( ! empty( $terms ) ) { 
  233. if ( function_exists( 'wp_list_sort' ) ) { 
  234. $terms = wp_list_sort( $terms, 'term_id', 'ASC' ); 
  235. } else { 
  236. usort( $terms, '_usort_terms_by_ID' ); 
  237. $category_object = apply_filters( 'wc_product_post_type_link_product_cat', $terms[0], $terms, $post ); 
  238. $category_object = get_term( $category_object, 'product_cat' ); 
  239. $product_cat = $category_object->slug; 
  240.  
  241. if ( $category_object->parent ) { 
  242. $ancestors = get_ancestors( $category_object->term_id, 'product_cat' ); 
  243. foreach ( $ancestors as $ancestor ) { 
  244. $ancestor_object = get_term( $ancestor, 'product_cat' ); 
  245. $product_cat = $ancestor_object->slug . '/' . $product_cat; 
  246. } else { 
  247. // If no terms are assigned to this post, use a string instead (can't leave the placeholder there) 
  248. $product_cat = _x( 'uncategorized', 'slug', 'woocommerce' ); 
  249.  
  250. $find = array( 
  251. '%year%',  
  252. '%monthnum%',  
  253. '%day%',  
  254. '%hour%',  
  255. '%minute%',  
  256. '%second%',  
  257. '%post_id%',  
  258. '%category%',  
  259. '%product_cat%',  
  260. ); 
  261.  
  262. $replace = array( 
  263. date_i18n( 'Y', strtotime( $post->post_date ) ),  
  264. date_i18n( 'm', strtotime( $post->post_date ) ),  
  265. date_i18n( 'd', strtotime( $post->post_date ) ),  
  266. date_i18n( 'H', strtotime( $post->post_date ) ),  
  267. date_i18n( 'i', strtotime( $post->post_date ) ),  
  268. date_i18n( 's', strtotime( $post->post_date ) ),  
  269. $post->ID,  
  270. $product_cat,  
  271. $product_cat,  
  272. ); 
  273.  
  274. $permalink = str_replace( $find, $replace, $permalink ); 
  275.  
  276. return $permalink; 
  277. add_filter( 'post_type_link', 'wc_product_post_type_link', 10, 2 ); 
  278.  
  279.  
  280. /** 
  281. * Get the placeholder image URL for products etc. 
  282. * 
  283. * @access public 
  284. * @return string 
  285. */ 
  286. function wc_placeholder_img_src() { 
  287. return apply_filters( 'woocommerce_placeholder_img_src', WC()->plugin_url() . '/assets/images/placeholder.png' ); 
  288.  
  289. /** 
  290. * Get the placeholder image. 
  291. * 
  292. * @access public 
  293. * @return string 
  294. */ 
  295. function wc_placeholder_img( $size = 'shop_thumbnail' ) { 
  296. $dimensions = wc_get_image_size( $size ); 
  297.  
  298. return apply_filters( 'woocommerce_placeholder_img', '<img src="' . wc_placeholder_img_src() . '" alt="' . esc_attr__( 'Placeholder', 'woocommerce' ) . '" width="' . esc_attr( $dimensions['width'] ) . '" class="woocommerce-placeholder wp-post-image" height="' . esc_attr( $dimensions['height'] ) . '" />', $size, $dimensions ); 
  299.  
  300. /** 
  301. * Variation Formatting. 
  302. * 
  303. * Gets a formatted version of variation data or item meta. 
  304. * 
  305. * @param array|WC_Product_Variation $variation 
  306. * @param bool $flat (default: false) 
  307. * @param bool $include_names include attribute names/labels 
  308. * @return string 
  309. */ 
  310. function wc_get_formatted_variation( $variation, $flat = false, $include_names = true ) { 
  311. $return = ''; 
  312.  
  313. if ( is_a( $variation, 'WC_Product_Variation' ) ) { 
  314. $variation_attributes = $variation->get_attributes(); 
  315. $product = $variation; 
  316. } else { 
  317. $product = false; 
  318. // Remove attribute_ prefix from names. 
  319. $variation_attributes = array(); 
  320. if ( is_array( $variation ) ) { 
  321. foreach ( $variation as $key => $value ) { 
  322. $variation_attributes[ str_replace( 'attribute_', '', $key ) ] = $value; 
  323.  
  324. $list_type = $include_names ? 'dl' : 'ul'; 
  325.  
  326. if ( is_array( $variation_attributes ) ) { 
  327.  
  328. if ( ! $flat ) { 
  329. $return = '<' . $list_type . ' class="variation">'; 
  330.  
  331. $variation_list = array(); 
  332.  
  333. foreach ( $variation_attributes as $name => $value ) { 
  334. if ( ! $value ) { 
  335. continue; 
  336.  
  337. // If this is a term slug, get the term's nice name 
  338. if ( taxonomy_exists( $name ) ) { 
  339. $term = get_term_by( 'slug', $value, $name ); 
  340. if ( ! is_wp_error( $term ) && ! empty( $term->name ) ) { 
  341. $value = $term->name; 
  342. } else { 
  343. $value = ucwords( str_replace( '-', ' ', $value ) ); 
  344.  
  345. if ( $include_names ) { 
  346. if ( $flat ) { 
  347. $variation_list[] = wc_attribute_label( $name, $product ) . ': ' . rawurldecode( $value ); 
  348. } else { 
  349. $variation_list[] = '<dt>' . wc_attribute_label( $name, $product ) . ':</dt><dd>' . rawurldecode( $value ) . '</dd>'; 
  350. } else { 
  351. if ( $flat ) { 
  352. $variation_list[] = rawurldecode( $value ); 
  353. } else { 
  354. $variation_list[] = '<li>' . rawurldecode( $value ) . '</li>'; 
  355.  
  356. if ( $flat ) { 
  357. $return .= implode( ', ', $variation_list ); 
  358. } else { 
  359. $return .= implode( '', $variation_list ); 
  360.  
  361. if ( ! $flat ) { 
  362. $return .= '</' . $list_type . '>'; 
  363. return $return; 
  364.  
  365. /** 
  366. * Function which handles the start and end of scheduled sales via cron. 
  367. */ 
  368. function wc_scheduled_sales() { 
  369. $data_store = WC_Data_Store::load( 'product' ); 
  370.  
  371. // Sales which are due to start 
  372. $product_ids = $data_store->get_starting_sales(); 
  373. if ( $product_ids ) { 
  374. foreach ( $product_ids as $product_id ) { 
  375. $product = wc_get_product( $product_id ); 
  376. $sale_price = $product->get_sale_price(); 
  377.  
  378. if ( $sale_price ) { 
  379. $product->set_price( $sale_price ); 
  380. $product->set_date_on_sale_from( '' ); 
  381. } else { 
  382. $product->set_date_on_sale_to( '' ); 
  383. $product->set_date_on_sale_from( '' ); 
  384.  
  385. $product->save(); 
  386.  
  387. delete_transient( 'wc_products_onsale' ); 
  388.  
  389. // Sales which are due to end 
  390. $product_ids = $data_store->get_ending_sales(); 
  391. if ( $product_ids ) { 
  392. foreach ( $product_ids as $product_id ) { 
  393. $product = wc_get_product( $product_id ); 
  394. $regular_price = $product->get_regular_price(); 
  395. $product->set_price( $regular_price ); 
  396. $product->set_sale_price( '' ); 
  397. $product->set_date_on_sale_to( '' ); 
  398. $product->set_date_on_sale_from( '' ); 
  399. $product->save(); 
  400.  
  401. WC_Cache_Helper::get_transient_version( 'product', true ); 
  402. delete_transient( 'wc_products_onsale' ); 
  403. add_action( 'woocommerce_scheduled_sales', 'wc_scheduled_sales' ); 
  404.  
  405. /** 
  406. * Get attachment image attributes. 
  407. * 
  408. * @access public 
  409. * @param array $attr 
  410. * @return array 
  411. */ 
  412. function wc_get_attachment_image_attributes( $attr ) { 
  413. if ( strstr( $attr['src'][0], 'woocommerce_uploads/' ) ) { 
  414. $attr['src'][0] = wc_placeholder_img_src(); 
  415. return $attr; 
  416. add_filter( 'wp_get_attachment_image_attributes', 'wc_get_attachment_image_attributes' ); 
  417.  
  418.  
  419. /** 
  420. * Prepare attachment for JavaScript. 
  421. * 
  422. * @access public 
  423. * @param array $response 
  424. * @return array 
  425. */ 
  426. function wc_prepare_attachment_for_js( $response ) { 
  427.  
  428. if ( isset( $response['url'] ) && strstr( $response['url'], 'woocommerce_uploads/' ) ) { 
  429. $response['full']['url'] = wc_placeholder_img_src(); 
  430. if ( isset( $response['sizes'] ) ) { 
  431. foreach ( $response['sizes'] as $size => $value ) { 
  432. $response['sizes'][ $size ]['url'] = wc_placeholder_img_src(); 
  433.  
  434. return $response; 
  435. add_filter( 'wp_prepare_attachment_for_js', 'wc_prepare_attachment_for_js' ); 
  436.  
  437. /** 
  438. * Track product views. 
  439. */ 
  440. function wc_track_product_view() { 
  441. if ( ! is_singular( 'product' ) || ! is_active_widget( false, false, 'woocommerce_recently_viewed_products', true ) ) { 
  442. return; 
  443.  
  444. global $post; 
  445.  
  446. if ( empty( $_COOKIE['woocommerce_recently_viewed'] ) ) 
  447. $viewed_products = array(); 
  448. else 
  449. $viewed_products = (array) explode( '|', $_COOKIE['woocommerce_recently_viewed'] ); 
  450.  
  451. if ( ! in_array( $post->ID, $viewed_products ) ) { 
  452. $viewed_products[] = $post->ID; 
  453.  
  454. if ( sizeof( $viewed_products ) > 15 ) { 
  455. array_shift( $viewed_products ); 
  456.  
  457. // Store for session only 
  458. wc_setcookie( 'woocommerce_recently_viewed', implode( '|', $viewed_products ) ); 
  459.  
  460. add_action( 'template_redirect', 'wc_track_product_view', 20 ); 
  461.  
  462. /** 
  463. * Get product types. 
  464. * 
  465. * @since 2.2 
  466. * @return array 
  467. */ 
  468. function wc_get_product_types() { 
  469. return (array) apply_filters( 'product_type_selector', array( 
  470. 'simple' => __( 'Simple product', 'woocommerce' ),  
  471. 'grouped' => __( 'Grouped product', 'woocommerce' ),  
  472. 'external' => __( 'External/Affiliate product', 'woocommerce' ),  
  473. 'variable' => __( 'Variable product', 'woocommerce' ),  
  474. ) ); 
  475.  
  476. /** 
  477. * Check if product sku is unique. 
  478. * 
  479. * @since 2.2 
  480. * @param int $product_id 
  481. * @param string $sku 
  482. * @return bool 
  483. */ 
  484. function wc_product_has_unique_sku( $product_id, $sku ) { 
  485. $data_store = WC_Data_Store::load( 'product' ); 
  486. $sku_found = $data_store->is_existing_sku( $product_id, $sku ); 
  487.  
  488. if ( apply_filters( 'wc_product_has_unique_sku', $sku_found, $product_id, $sku ) ) { 
  489. return false; 
  490. } else { 
  491. return true; 
  492.  
  493. /** 
  494. * Force a unique SKU. 
  495. * 
  496. * @since 3.0.0 
  497. * @param integer $product_id 
  498. */ 
  499. function wc_product_force_unique_sku( $product_id ) { 
  500. $product = wc_get_product( $product_id ); 
  501.  
  502. if ( $product && ( $current_sku = $product->get_sku( 'edit' ) ) ) { 
  503. try { 
  504. $new_sku = wc_product_generate_unique_sku( $product_id, $current_sku ); 
  505.  
  506. if ( $current_sku !== $new_sku ) { 
  507. $product->set_sku( $new_sku ); 
  508. $product->save(); 
  509. } catch ( Exception $e ) {} 
  510.  
  511. /** 
  512. * Recursively appends a suffix until a unique SKU is found. 
  513. * 
  514. * @since 3.0.0 
  515. * @param integer $product_id 
  516. * @param string $sku 
  517. * @param integer $index 
  518. * @return string 
  519. */ 
  520. function wc_product_generate_unique_sku( $product_id, $sku, $index = 0 ) { 
  521. $generated_sku = 0 < $index ? $sku . '-' . $index : $sku; 
  522.  
  523. if ( ! wc_product_has_unique_sku( $product_id, $generated_sku ) ) { 
  524. $generated_sku = wc_product_generate_unique_sku( $product_id, $sku, ( $index + 1 ) ); 
  525.  
  526. return $generated_sku; 
  527.  
  528. /** 
  529. * Get product ID by SKU. 
  530. * 
  531. * @since 2.3.0 
  532. * @param string $sku 
  533. * @return int 
  534. */ 
  535. function wc_get_product_id_by_sku( $sku ) { 
  536. global $wpdb; 
  537.  
  538. $data_store = WC_Data_Store::load( 'product' ); 
  539. $product_id = $data_store->get_product_id_by_sku( $sku ); 
  540.  
  541. return ( $product_id ) ? intval( $product_id ) : 0; 
  542.  
  543. /** 
  544. * Get attibutes/data for an individual variation from the database and maintain it's integrity. 
  545. * @param revisit? 
  546. * @since 2.4.0 
  547. * @param int $variation_id 
  548. * @return array 
  549. */ 
  550. function wc_get_product_variation_attributes( $variation_id ) { 
  551. // Build variation data from meta 
  552. $all_meta = get_post_meta( $variation_id ); 
  553. $parent_id = wp_get_post_parent_id( $variation_id ); 
  554. $parent_attributes = array_filter( (array) get_post_meta( $parent_id, '_product_attributes', true ) ); 
  555. $found_parent_attributes = array(); 
  556. $variation_attributes = array(); 
  557.  
  558. // Compare to parent variable product attributes and ensure they match 
  559. foreach ( $parent_attributes as $attribute_name => $options ) { 
  560. if ( ! empty( $options['is_variation'] ) ) { 
  561. $attribute = 'attribute_' . sanitize_title( $attribute_name ); 
  562. $found_parent_attributes[] = $attribute; 
  563. if ( ! array_key_exists( $attribute, $variation_attributes ) ) { 
  564. $variation_attributes[ $attribute ] = ''; // Add it - 'any' will be asumed 
  565.  
  566. // Get the variation attributes from meta 
  567. foreach ( $all_meta as $name => $value ) { 
  568. // Only look at valid attribute meta, and also compare variation level attributes and remove any which do not exist at parent level 
  569. if ( 0 !== strpos( $name, 'attribute_' ) || ! in_array( $name, $found_parent_attributes ) ) { 
  570. unset( $variation_attributes[ $name ] ); 
  571. continue; 
  572. /** 
  573. * Pre 2.4 handling where 'slugs' were saved instead of the full text attribute. 
  574. * Attempt to get full version of the text attribute from the parent. 
  575. */ 
  576. if ( sanitize_title( $value[0] ) === $value[0] && version_compare( get_post_meta( $parent_id, '_product_version', true ), '2.4.0', '<' ) ) { 
  577. foreach ( $parent_attributes as $attribute ) { 
  578. if ( 'attribute_' . sanitize_title( $attribute['name'] ) !== $name ) { 
  579. continue; 
  580. $text_attributes = wc_get_text_attributes( $attribute['value'] ); 
  581.  
  582. foreach ( $text_attributes as $text_attribute ) { 
  583. if ( sanitize_title( $text_attribute ) === $value[0] ) { 
  584. $value[0] = $text_attribute; 
  585. break; 
  586.  
  587. $variation_attributes[ $name ] = $value[0]; 
  588.  
  589. return $variation_attributes; 
  590.  
  591. /** 
  592. * Get all product cats for a product by ID, including hierarchy 
  593. * @since 2.5.0 
  594. * @param int $product_id 
  595. * @return array 
  596. */ 
  597. function wc_get_product_cat_ids( $product_id ) { 
  598. $product_cats = wc_get_product_term_ids( $product_id, 'product_cat' ); 
  599.  
  600. foreach ( $product_cats as $product_cat ) { 
  601. $product_cats = array_merge( $product_cats, get_ancestors( $product_cat, 'product_cat' ) ); 
  602.  
  603. return $product_cats; 
  604.  
  605. /** 
  606. * Gets data about an attachment, such as alt text and captions. 
  607. * @since 2.6.0 
  608. * @param object|bool $product 
  609. * @return array 
  610. */ 
  611. function wc_get_product_attachment_props( $attachment_id = null, $product = false ) { 
  612. $props = array( 
  613. 'title' => '',  
  614. 'caption' => '',  
  615. 'url' => '',  
  616. 'alt' => '',  
  617. 'src' => '',  
  618. 'srcset' => false,  
  619. 'sizes' => false,  
  620. ); 
  621. if ( $attachment = get_post( $attachment_id ) ) { 
  622. $props['title'] = trim( strip_tags( $attachment->post_title ) ); 
  623. $props['caption'] = trim( strip_tags( $attachment->post_excerpt ) ); 
  624. $props['url'] = wp_get_attachment_url( $attachment_id ); 
  625. $props['alt'] = trim( strip_tags( get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) ) ); 
  626.  
  627. // Large version. 
  628. $src = wp_get_attachment_image_src( $attachment_id, 'full' ); 
  629. $props['full_src'] = $src[0]; 
  630. $props['full_src_w'] = $src[1]; 
  631. $props['full_src_h'] = $src[2]; 
  632.  
  633. // Thumbnail version. 
  634. $src = wp_get_attachment_image_src( $attachment_id, 'shop_thumbnail' ); 
  635. $props['thumb_src'] = $src[0]; 
  636. $props['thumb_src_w'] = $src[1]; 
  637. $props['thumb_src_h'] = $src[2]; 
  638.  
  639. // Image source. 
  640. $src = wp_get_attachment_image_src( $attachment_id, 'shop_single' ); 
  641. $props['src'] = $src[0]; 
  642. $props['src_w'] = $src[1]; 
  643. $props['src_h'] = $src[2]; 
  644. $props['srcset'] = function_exists( 'wp_get_attachment_image_srcset' ) ? wp_get_attachment_image_srcset( $attachment_id, 'shop_single' ) : false; 
  645. $props['sizes'] = function_exists( 'wp_get_attachment_image_sizes' ) ? wp_get_attachment_image_sizes( $attachment_id, 'shop_single' ) : false; 
  646.  
  647. // Alt text fallbacks 
  648. $props['alt'] = empty( $props['alt'] ) ? $props['caption'] : $props['alt']; 
  649. $props['alt'] = empty( $props['alt'] ) ? trim( strip_tags( $attachment->post_title ) ) : $props['alt']; 
  650. $props['alt'] = empty( $props['alt'] ) && $product ? trim( strip_tags( get_the_title( $product->ID ) ) ) : $props['alt']; 
  651. return $props; 
  652.  
  653. /** 
  654. * Get product visibility options. 
  655. * 
  656. * @since 3.0.0 
  657. * @return array 
  658. */ 
  659. function wc_get_product_visibility_options() { 
  660. return apply_filters( 'woocommerce_product_visibility_options', array( 
  661. 'visible' => __( 'Visible', 'woocommerce' ),  
  662. 'catalog' => __( 'Catalog', 'woocommerce' ),  
  663. 'search' => __( 'Search', 'woocommerce' ),  
  664. 'hidden' => __( 'Hidden', 'woocommerce' ),  
  665. ) ); 
  666.  
  667. /** 
  668. * Get min/max price meta query args. 
  669. * 
  670. * @since 3.0.0 
  671. * @param array $args Min price and max price arguments. 
  672. * @return array 
  673. */ 
  674. function wc_get_min_max_price_meta_query( $args ) { 
  675. $min = isset( $args['min_price'] ) ? floatval( $args['min_price'] ) : 0; 
  676. $max = isset( $args['max_price'] ) ? floatval( $args['max_price'] ) : 9999999999; 
  677.  
  678. /** 
  679. * Adjust if the store taxes are not displayed how they are stored. 
  680. * Max is left alone because the filter was already increased. 
  681. * Kicks in when prices excluding tax are displayed including tax. 
  682. */ 
  683. if ( wc_tax_enabled() && 'incl' === get_option( 'woocommerce_tax_display_shop' ) && ! wc_prices_include_tax() ) { 
  684. $tax_classes = array_merge( array( '' ), WC_Tax::get_tax_classes() ); 
  685. $class_min = $min; 
  686.  
  687. foreach ( $tax_classes as $tax_class ) { 
  688. if ( $tax_rates = WC_Tax::get_rates( $tax_class ) ) { 
  689. $class_min = $min - WC_Tax::get_tax_total( WC_Tax::calc_exclusive_tax( $min, $tax_rates ) ); 
  690.  
  691. $min = $class_min; 
  692.  
  693. return array( 
  694. 'key' => '_price',  
  695. 'value' => array( $min, $max ),  
  696. 'compare' => 'BETWEEN',  
  697. 'type' => 'NUMERIC',  
  698. ); 
  699.  
  700. /** 
  701. * Get product tax class options. 
  702. * 
  703. * @since 3.0.0 
  704. * @return array 
  705. */ 
  706. function wc_get_product_tax_class_options() { 
  707. $tax_classes = WC_Tax::get_tax_classes(); 
  708. $tax_class_options = array(); 
  709. $tax_class_options[''] = __( 'Standard', 'woocommerce' ); 
  710.  
  711. if ( ! empty( $tax_classes ) ) { 
  712. foreach ( $tax_classes as $class ) { 
  713. $tax_class_options[ sanitize_title( $class ) ] = $class; 
  714. return $tax_class_options; 
  715.  
  716. /** 
  717. * Get stock status options. 
  718. * 
  719. * @since 3.0.0 
  720. * @return array 
  721. */ 
  722. function wc_get_product_stock_status_options() { 
  723. return array( 
  724. 'instock' => __( 'In stock', 'woocommerce' ),  
  725. 'outofstock' => __( 'Out of stock', 'woocommerce' ),  
  726. ); 
  727.  
  728. /** 
  729. * Get backorder options. 
  730. * 
  731. * @since 3.0.0 
  732. * @return array 
  733. */ 
  734. function wc_get_product_backorder_options() { 
  735. return array( 
  736. 'no' => __( 'Do not allow', 'woocommerce' ),  
  737. 'notify' => __( 'Allow, but notify customer', 'woocommerce' ),  
  738. 'yes' => __( 'Allow', 'woocommerce' ),  
  739. ); 
  740.  
  741. /** 
  742. * Get related products based on product category and tags. 
  743. * 
  744. * @since 3.0.0 
  745. * @param int $product_id Product ID. 
  746. * @param int $limit Limit of results. 
  747. * @param array $exclude_ids Exclude IDs from the results. 
  748. * @return array 
  749. */ 
  750. function wc_get_related_products( $product_id, $limit = 5, $exclude_ids = array() ) { 
  751. global $wpdb; 
  752.  
  753. $product_id = absint( $product_id ); 
  754. $exclude_ids = array_merge( array( 0, $product_id ), $exclude_ids ); 
  755. $transient_name = 'wc_related_' . $product_id; 
  756. $related_posts = get_transient( $transient_name ); 
  757. $limit = $limit > 0 ? $limit : 5; 
  758.  
  759. // We want to query related posts if they are not cached, or we don't have enough. 
  760. if ( false === $related_posts || count( $related_posts ) < $limit ) { 
  761. $cats_array = apply_filters( 'woocommerce_product_related_posts_relate_by_category', true, $product_id ) ? apply_filters( 'woocommerce_get_related_product_cat_terms', wc_get_product_term_ids( $product_id, 'product_cat' ), $product_id ) : array(); 
  762. $tags_array = apply_filters( 'woocommerce_product_related_posts_relate_by_tag', true, $product_id ) ? apply_filters( 'woocommerce_get_related_product_tag_terms', wc_get_product_term_ids( $product_id, 'product_tag' ), $product_id ) : array(); 
  763.  
  764. // Don't bother if none are set, unless woocommerce_product_related_posts_force_display is set to true in which case all products are related. 
  765. if ( empty( $cats_array ) && empty( $tags_array ) && ! apply_filters( 'woocommerce_product_related_posts_force_display', false, $product_id ) ) { 
  766. $related_posts = array(); 
  767. } else { 
  768. $data_store = WC_Data_Store::load( 'product' ); 
  769. $related_posts = $data_store->get_related_products( $cats_array, $tags_array, $exclude_ids, $limit + 10, $product_id ); 
  770.  
  771. set_transient( $transient_name, $related_posts, DAY_IN_SECONDS ); 
  772.  
  773. shuffle( $related_posts ); 
  774.  
  775. return array_slice( $related_posts, 0, $limit ); 
  776.  
  777. /** 
  778. * Retrieves product term ids for a taxonomy. 
  779. * 
  780. * @since 3.0.0 
  781. * @param int $product_id Product ID. 
  782. * @param string $taxonomy Taxonomy slug. 
  783. * @return array 
  784. */ 
  785. function wc_get_product_term_ids( $product_id, $taxonomy ) { 
  786. $terms = get_the_terms( $product_id, $taxonomy ); 
  787. return ! empty( $terms ) ? wp_list_pluck( $terms, 'term_id' ) : array(); 
  788.  
  789. /** 
  790. * For a given product, and optionally price/qty, work out the price with tax included, based on store settings. 
  791. * @since 3.0.0 
  792. * @param WC_Product $product 
  793. * @param array $args 
  794. * @return float 
  795. */ 
  796. function wc_get_price_including_tax( $product, $args = array() ) { 
  797. $args = wp_parse_args( $args, array( 
  798. 'qty' => '',  
  799. 'price' => '',  
  800. ) ); 
  801.  
  802. $price = '' !== $args['price'] ? max( 0.0, (float) $args['price'] ) : $product->get_price(); 
  803. $qty = '' !== $args['qty'] ? max( 0.0, (float) $args['qty'] ) : 1; 
  804.  
  805. if ( '' === $price ) { 
  806. return ''; 
  807. } elseif ( empty( $qty ) ) { 
  808. return 0.0; 
  809.  
  810. $line_price = $price * $qty; 
  811. $return_price = $line_price; 
  812.  
  813. if ( $product->is_taxable() ) { 
  814. if ( ! wc_prices_include_tax() ) { 
  815. $tax_rates = WC_Tax::get_rates( $product->get_tax_class() ); 
  816. $taxes = WC_Tax::calc_tax( $line_price, $tax_rates, false ); 
  817. $tax_amount = WC_Tax::get_tax_total( $taxes ); 
  818. $return_price = round( $line_price + $tax_amount, wc_get_price_decimals() ); 
  819. } else { 
  820. $tax_rates = WC_Tax::get_rates( $product->get_tax_class() ); 
  821. $base_tax_rates = WC_Tax::get_base_tax_rates( $product->get_tax_class( 'unfiltered' ) ); 
  822.  
  823. /** 
  824. * If the customer is excempt from VAT, remove the taxes here. 
  825. * Either remove the base or the user taxes depending on woocommerce_adjust_non_base_location_prices setting. 
  826. */ 
  827. if ( ! empty( WC()->customer ) && WC()->customer->get_is_vat_exempt() ) { 
  828. $remove_taxes = apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ? WC_Tax::calc_tax( $line_price, $base_tax_rates, true ) : WC_Tax::calc_tax( $line_price, $tax_rates, true ); 
  829. $remove_tax = array_sum( $remove_taxes ); 
  830. $return_price = round( $line_price - $remove_tax, wc_get_price_decimals() ); 
  831.  
  832. /** 
  833. * The woocommerce_adjust_non_base_location_prices filter can stop base taxes being taken off when dealing with out of base locations. 
  834. * e.g. If a product costs 10 including tax, all users will pay 10 regardless of location and taxes. 
  835. * This feature is experimental @since 2.4.7 and may change in the future. Use at your risk. 
  836. */ 
  837. } elseif ( $tax_rates !== $base_tax_rates && apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) { 
  838. $base_taxes = WC_Tax::calc_tax( $line_price, $base_tax_rates, true ); 
  839. $modded_taxes = WC_Tax::calc_tax( $line_price - array_sum( $base_taxes ), $tax_rates, false ); 
  840. $return_price = round( $line_price - array_sum( $base_taxes ) + array_sum( $modded_taxes ), wc_get_price_decimals() ); 
  841. return apply_filters( 'woocommerce_get_price_including_tax', $return_price, $qty, $product ); 
  842.  
  843. /** 
  844. * For a given product, and optionally price/qty, work out the price with tax excluded, based on store settings. 
  845. * @since 3.0.0 
  846. * @param WC_Product $product 
  847. * @param array $args 
  848. * @return float 
  849. */ 
  850. function wc_get_price_excluding_tax( $product, $args = array() ) { 
  851. $args = wp_parse_args( $args, array( 
  852. 'qty' => '',  
  853. 'price' => '',  
  854. ) ); 
  855.  
  856. $price = '' !== $args['price'] ? max( 0.0, (float) $args['price'] ) : $product->get_price(); 
  857. $qty = '' !== $args['qty'] ? max( 0.0, (float) $args['qty'] ) : 1; 
  858.  
  859. if ( '' === $price ) { 
  860. return ''; 
  861. } elseif ( empty( $qty ) ) { 
  862. return 0.0; 
  863.  
  864. if ( $product->is_taxable() && wc_prices_include_tax() ) { 
  865. $tax_rates = WC_Tax::get_base_tax_rates( $product->get_tax_class( 'unfiltered' ) ); 
  866. $taxes = WC_Tax::calc_tax( $price * $qty, $tax_rates, true ); 
  867. $price = WC_Tax::round( $price * $qty - array_sum( $taxes ) ); 
  868. } else { 
  869. $price = $price * $qty; 
  870.  
  871. return apply_filters( 'woocommerce_get_price_excluding_tax', $price, $qty, $product ); 
  872.  
  873. /** 
  874. * Returns the price including or excluding tax, based on the 'woocommerce_tax_display_shop' setting. 
  875. * @since 3.0.0 
  876. * @param WC_Product $product 
  877. * @param array $args 
  878. * @return float 
  879. */ 
  880. function wc_get_price_to_display( $product, $args = array() ) { 
  881. $args = wp_parse_args( $args, array( 
  882. 'qty' => 1,  
  883. 'price' => $product->get_price(),  
  884. ) ); 
  885.  
  886. $price = $args['price']; 
  887. $qty = $args['qty']; 
  888.  
  889. return 'incl' === get_option( 'woocommerce_tax_display_shop' ) ? wc_get_price_including_tax( $product, array( 'qty' => $qty, 'price' => $price ) ) : wc_get_price_excluding_tax( $product, array( 'qty' => $qty, 'price' => $price ) ); 
  890.  
  891. /** 
  892. * Returns the product categories in a list. 
  893. * 
  894. * @param int $product_id 
  895. * @param string $sep (default: ', '). 
  896. * @param string $before (default: ''). 
  897. * @param string $after (default: ''). 
  898. * @return string 
  899. */ 
  900. function wc_get_product_category_list( $product_id, $sep = ', ', $before = '', $after = '' ) { 
  901. return get_the_term_list( $product_id, 'product_cat', $before, $sep, $after ); 
  902.  
  903. /** 
  904. * Returns the product tags in a list. 
  905. * 
  906. * @param int $product_id 
  907. * @param string $sep (default: ', '). 
  908. * @param string $before (default: ''). 
  909. * @param string $after (default: ''). 
  910. * @return string 
  911. */ 
  912. function wc_get_product_tag_list( $product_id, $sep = ', ', $before = '', $after = '' ) { 
  913. return get_the_term_list( $product_id, 'product_tag', $before, $sep, $after ); 
  914.  
  915. /** 
  916. * Callback for array filter to get visible only. 
  917. * @since 3.0.0 
  918. * @param WC_Product $product 
  919. * @return bool 
  920. */ 
  921. function wc_products_array_filter_visible( $product ) { 
  922. return $product && is_a( $product, 'WC_Product' ) && $product->is_visible(); 
  923.  
  924. /** 
  925. * Callback for array filter to get products the user can edit only. 
  926. * 
  927. * @since 3.0.0 
  928. * @param WC_Product $product 
  929. * @return bool 
  930. */ 
  931. function wc_products_array_filter_editable( $product ) { 
  932. return $product && is_a( $product, 'WC_Product' ) && current_user_can( 'edit_product', $product->get_id() ); 
  933.  
  934. /** 
  935. * Sort an array of products by a value. 
  936. * @since 3.0.0 
  937. * @param array $products 
  938. * @param string $orderby 
  939. * @return array 
  940. */ 
  941. function wc_products_array_orderby( $products, $orderby = 'date', $order = 'desc' ) { 
  942. $orderby = strtolower( $orderby ); 
  943. $order = strtolower( $order ); 
  944. switch ( $orderby ) { 
  945. case 'title' : 
  946. case 'id' : 
  947. case 'date' : 
  948. case 'modified' : 
  949. case 'menu_order' : 
  950. case 'price' : 
  951. usort( $products, 'wc_products_array_orderby_' . $orderby ); 
  952. break; 
  953. default : 
  954. shuffle( $products ); 
  955. break; 
  956. if ( 'desc' === $order ) { 
  957. $products = array_reverse( $products ); 
  958. return $products; 
  959.  
  960. /** 
  961. * Sort by title. 
  962. * @since 3.0.0 
  963. * @param WC_Product object $a 
  964. * @param WC_Product object $b 
  965. * @return int 
  966. */ 
  967. function wc_products_array_orderby_title( $a, $b ) { 
  968. return strcasecmp( $a->get_name(), $b->get_name() ); 
  969.  
  970. /** 
  971. * Sort by id. 
  972. * @since 3.0.0 
  973. * @param WC_Product object $a 
  974. * @param WC_Product object $b 
  975. * @return int 
  976. */ 
  977. function wc_products_array_orderby_id( $a, $b ) { 
  978. if ( $a->get_id() === $b->get_id() ) { 
  979. return 0; 
  980. return ( $a->get_id() < $b->get_id() ) ? -1 : 1; 
  981.  
  982. /** 
  983. * Sort by date. 
  984. * @since 3.0.0 
  985. * @param WC_Product object $a 
  986. * @param WC_Product object $b 
  987. * @return int 
  988. */ 
  989. function wc_products_array_orderby_date( $a, $b ) { 
  990. if ( $a->get_date_created() === $b->get_date_created() ) { 
  991. return 0; 
  992. return ( $a->get_date_created() < $b->get_date_created() ) ? -1 : 1; 
  993.  
  994. /** 
  995. * Sort by modified. 
  996. * @since 3.0.0 
  997. * @param WC_Product object $a 
  998. * @param WC_Product object $b 
  999. * @return int 
  1000. */ 
  1001. function wc_products_array_orderby_modified( $a, $b ) { 
  1002. if ( $a->get_date_modified() === $b->get_date_modified() ) { 
  1003. return 0; 
  1004. return ( $a->get_date_modified() < $b->get_date_modified() ) ? -1 : 1; 
  1005.  
  1006. /** 
  1007. * Sort by menu order. 
  1008. * @since 3.0.0 
  1009. * @param WC_Product object $a 
  1010. * @param WC_Product object $b 
  1011. * @return int 
  1012. */ 
  1013. function wc_products_array_orderby_menu_order( $a, $b ) { 
  1014. if ( $a->get_menu_order() === $b->get_menu_order() ) { 
  1015. return 0; 
  1016. return ( $a->get_menu_order() < $b->get_menu_order() ) ? -1 : 1; 
  1017.  
  1018. /** 
  1019. * Sort by price low to high. 
  1020. * @since 3.0.0 
  1021. * @param WC_Product object $a 
  1022. * @param WC_Product object $b 
  1023. * @return int 
  1024. */ 
  1025. function wc_products_array_orderby_price( $a, $b ) { 
  1026. if ( $a->get_price() === $b->get_price() ) { 
  1027. return 0; 
  1028. return ( $a->get_price() < $b->get_price() ) ? -1 : 1; 
  1029.  
  1030. /** 
  1031. * Queue a product for syncing at the end of the request. 
  1032. * 
  1033. * @param int $product_id 
  1034. */ 
  1035. function wc_deferred_product_sync( $product_id ) { 
  1036. global $wc_deferred_product_sync; 
  1037.  
  1038. if ( empty( $wc_deferred_product_sync ) ) { 
  1039. $wc_deferred_product_sync = array(); 
  1040.  
  1041. $wc_deferred_product_sync[] = $product_id; 
.