/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. $variation_attributes = $variation; 
  318. $product = false; 
  319.  
  320. $list_type = $include_names ? 'dl' : 'ul'; 
  321.  
  322. if ( is_array( $variation_attributes ) ) { 
  323.  
  324. if ( ! $flat ) { 
  325. $return = '<' . $list_type . ' class="variation">'; 
  326.  
  327. $variation_list = array(); 
  328.  
  329. foreach ( $variation_attributes as $name => $value ) { 
  330. if ( ! $value ) { 
  331. continue; 
  332.  
  333. // If this is a term slug, get the term's nice name 
  334. if ( taxonomy_exists( $name ) ) { 
  335. $term = get_term_by( 'slug', $value, $name ); 
  336. if ( ! is_wp_error( $term ) && ! empty( $term->name ) ) { 
  337. $value = $term->name; 
  338. } else { 
  339. $value = ucwords( str_replace( '-', ' ', $value ) ); 
  340.  
  341. if ( $include_names ) { 
  342. if ( $flat ) { 
  343. $variation_list[] = wc_attribute_label( $name, $product ) . ': ' . rawurldecode( $value ); 
  344. } else { 
  345. $variation_list[] = '<dt>' . wc_attribute_label( $name, $product ) . ':</dt><dd>' . rawurldecode( $value ) . '</dd>'; 
  346. } else { 
  347. if ( $flat ) { 
  348. $variation_list[] = rawurldecode( $value ); 
  349. } else { 
  350. $variation_list[] = '<li>' . rawurldecode( $value ) . '</li>'; 
  351.  
  352. if ( $flat ) { 
  353. $return .= implode( ', ', $variation_list ); 
  354. } else { 
  355. $return .= implode( '', $variation_list ); 
  356.  
  357. if ( ! $flat ) { 
  358. $return .= '</' . $list_type . '>'; 
  359. return $return; 
  360.  
  361. /** 
  362. * Function which handles the start and end of scheduled sales via cron. 
  363. */ 
  364. function wc_scheduled_sales() { 
  365. $data_store = WC_Data_Store::load( 'product' ); 
  366.  
  367. // Sales which are due to start 
  368. $product_ids = $data_store->get_starting_sales(); 
  369. if ( $product_ids ) { 
  370. foreach ( $product_ids as $product_id ) { 
  371. $product = wc_get_product( $product_id ); 
  372. $sale_price = $product->get_sale_price(); 
  373.  
  374. if ( $sale_price ) { 
  375. $product->set_price( $sale_price ); 
  376. } else { 
  377. $product->set_date_on_sale_to( '' ); 
  378. $product->set_date_on_sale_from( '' ); 
  379.  
  380. $product->save(); 
  381.  
  382. delete_transient( 'wc_products_onsale' ); 
  383.  
  384. // Sales which are due to end 
  385. $product_ids = $data_store->get_ending_sales(); 
  386. if ( $product_ids ) { 
  387. foreach ( $product_ids as $product_id ) { 
  388. $product = wc_get_product( $product_id ); 
  389. $regular_price = $product->get_regular_price(); 
  390. $product->set_price( $regular_price ); 
  391. $product->set_sale_price( '' ); 
  392. $product->set_date_on_sale_to( '' ); 
  393. $product->set_date_on_sale_from( '' ); 
  394. $product->save(); 
  395.  
  396. WC_Cache_Helper::get_transient_version( 'product', true ); 
  397. delete_transient( 'wc_products_onsale' ); 
  398. add_action( 'woocommerce_scheduled_sales', 'wc_scheduled_sales' ); 
  399.  
  400. /** 
  401. * Get attachment image attributes. 
  402. * 
  403. * @access public 
  404. * @param array $attr 
  405. * @return array 
  406. */ 
  407. function wc_get_attachment_image_attributes( $attr ) { 
  408. if ( strstr( $attr['src'][0], 'woocommerce_uploads/' ) ) { 
  409. $attr['src'][0] = wc_placeholder_img_src(); 
  410. return $attr; 
  411. add_filter( 'wp_get_attachment_image_attributes', 'wc_get_attachment_image_attributes' ); 
  412.  
  413.  
  414. /** 
  415. * Prepare attachment for JavaScript. 
  416. * 
  417. * @access public 
  418. * @param array $response 
  419. * @return array 
  420. */ 
  421. function wc_prepare_attachment_for_js( $response ) { 
  422.  
  423. if ( isset( $response['url'] ) && strstr( $response['url'], 'woocommerce_uploads/' ) ) { 
  424. $response['full']['url'] = wc_placeholder_img_src(); 
  425. if ( isset( $response['sizes'] ) ) { 
  426. foreach ( $response['sizes'] as $size => $value ) { 
  427. $response['sizes'][ $size ]['url'] = wc_placeholder_img_src(); 
  428.  
  429. return $response; 
  430. add_filter( 'wp_prepare_attachment_for_js', 'wc_prepare_attachment_for_js' ); 
  431.  
  432. /** 
  433. * Track product views. 
  434. */ 
  435. function wc_track_product_view() { 
  436. if ( ! is_singular( 'product' ) || ! is_active_widget( false, false, 'woocommerce_recently_viewed_products', true ) ) { 
  437. return; 
  438.  
  439. global $post; 
  440.  
  441. if ( empty( $_COOKIE['woocommerce_recently_viewed'] ) ) 
  442. $viewed_products = array(); 
  443. else 
  444. $viewed_products = (array) explode( '|', $_COOKIE['woocommerce_recently_viewed'] ); 
  445.  
  446. if ( ! in_array( $post->ID, $viewed_products ) ) { 
  447. $viewed_products[] = $post->ID; 
  448.  
  449. if ( sizeof( $viewed_products ) > 15 ) { 
  450. array_shift( $viewed_products ); 
  451.  
  452. // Store for session only 
  453. wc_setcookie( 'woocommerce_recently_viewed', implode( '|', $viewed_products ) ); 
  454.  
  455. add_action( 'template_redirect', 'wc_track_product_view', 20 ); 
  456.  
  457. /** 
  458. * Get product types. 
  459. * 
  460. * @since 2.2 
  461. * @return array 
  462. */ 
  463. function wc_get_product_types() { 
  464. return (array) apply_filters( 'product_type_selector', array( 
  465. 'simple' => __( 'Simple product', 'woocommerce' ),  
  466. 'grouped' => __( 'Grouped product', 'woocommerce' ),  
  467. 'external' => __( 'External/Affiliate product', 'woocommerce' ),  
  468. 'variable' => __( 'Variable product', 'woocommerce' ),  
  469. ) ); 
  470.  
  471. /** 
  472. * Check if product sku is unique. 
  473. * 
  474. * @since 2.2 
  475. * @param int $product_id 
  476. * @param string $sku 
  477. * @return bool 
  478. */ 
  479. function wc_product_has_unique_sku( $product_id, $sku ) { 
  480. $data_store = WC_Data_Store::load( 'product' ); 
  481. $sku_found = $data_store->is_existing_sku( $product_id, $sku ); 
  482.  
  483. if ( apply_filters( 'wc_product_has_unique_sku', $sku_found, $product_id, $sku ) ) { 
  484. return false; 
  485. } else { 
  486. return true; 
  487.  
  488. /** 
  489. * Force a unique SKU. 
  490. * 
  491. * @since 3.0.0 
  492. * @param integer $product_id 
  493. */ 
  494. function wc_product_force_unique_sku( $product_id ) { 
  495. $product = wc_get_product( $product_id ); 
  496.  
  497. if ( $product && ( $current_sku = $product->get_sku( 'edit' ) ) ) { 
  498. try { 
  499. $new_sku = wc_product_generate_unique_sku( $product_id, $current_sku ); 
  500.  
  501. if ( $current_sku !== $new_sku ) { 
  502. $product->set_sku( $new_sku ); 
  503. $product->save(); 
  504. } catch ( Exception $e ) {} 
  505.  
  506. /** 
  507. * Recursively appends a suffix until a unique SKU is found. 
  508. * 
  509. * @since 3.0.0 
  510. * @param integer $product_id 
  511. * @param string $sku 
  512. * @param integer $index 
  513. * @return string 
  514. */ 
  515. function wc_product_generate_unique_sku( $product_id, $sku, $index = 0 ) { 
  516. $generated_sku = 0 < $index ? $sku . '-' . $index : $sku; 
  517.  
  518. if ( ! wc_product_has_unique_sku( $product_id, $generated_sku ) ) { 
  519. $generated_sku = wc_product_generate_unique_sku( $product_id, $sku, ( $index + 1 ) ); 
  520.  
  521. return $generated_sku; 
  522.  
  523. /** 
  524. * Get product ID by SKU. 
  525. * 
  526. * @since 2.3.0 
  527. * @param string $sku 
  528. * @return int 
  529. */ 
  530. function wc_get_product_id_by_sku( $sku ) { 
  531. global $wpdb; 
  532.  
  533. $data_store = WC_Data_Store::load( 'product' ); 
  534. $product_id = $data_store->get_product_id_by_sku( $sku ); 
  535.  
  536. return ( $product_id ) ? intval( $product_id ) : 0; 
  537.  
  538. /** 
  539. * Get attibutes/data for an individual variation from the database and maintain it's integrity. 
  540. * @param revisit? 
  541. * @since 2.4.0 
  542. * @param int $variation_id 
  543. * @return array 
  544. */ 
  545. function wc_get_product_variation_attributes( $variation_id ) { 
  546. // Build variation data from meta 
  547. $all_meta = get_post_meta( $variation_id ); 
  548. $parent_id = wp_get_post_parent_id( $variation_id ); 
  549. $parent_attributes = array_filter( (array) get_post_meta( $parent_id, '_product_attributes', true ) ); 
  550. $found_parent_attributes = array(); 
  551. $variation_attributes = array(); 
  552.  
  553. // Compare to parent variable product attributes and ensure they match 
  554. foreach ( $parent_attributes as $attribute_name => $options ) { 
  555. if ( ! empty( $options['is_variation'] ) ) { 
  556. $attribute = 'attribute_' . sanitize_title( $attribute_name ); 
  557. $found_parent_attributes[] = $attribute; 
  558. if ( ! array_key_exists( $attribute, $variation_attributes ) ) { 
  559. $variation_attributes[ $attribute ] = ''; // Add it - 'any' will be asumed 
  560.  
  561. // Get the variation attributes from meta 
  562. foreach ( $all_meta as $name => $value ) { 
  563. // Only look at valid attribute meta, and also compare variation level attributes and remove any which do not exist at parent level 
  564. if ( 0 !== strpos( $name, 'attribute_' ) || ! in_array( $name, $found_parent_attributes ) ) { 
  565. unset( $variation_attributes[ $name ] ); 
  566. continue; 
  567. /** 
  568. * Pre 2.4 handling where 'slugs' were saved instead of the full text attribute. 
  569. * Attempt to get full version of the text attribute from the parent. 
  570. */ 
  571. if ( sanitize_title( $value[0] ) === $value[0] && version_compare( get_post_meta( $parent_id, '_product_version', true ), '2.4.0', '<' ) ) { 
  572. foreach ( $parent_attributes as $attribute ) { 
  573. if ( 'attribute_' . sanitize_title( $attribute['name'] ) !== $name ) { 
  574. continue; 
  575. $text_attributes = wc_get_text_attributes( $attribute['value'] ); 
  576.  
  577. foreach ( $text_attributes as $text_attribute ) { 
  578. if ( sanitize_title( $text_attribute ) === $value[0] ) { 
  579. $value[0] = $text_attribute; 
  580. break; 
  581.  
  582. $variation_attributes[ $name ] = $value[0]; 
  583.  
  584. return $variation_attributes; 
  585.  
  586. /** 
  587. * Get all product cats for a product by ID, including hierarchy 
  588. * @since 2.5.0 
  589. * @param int $product_id 
  590. * @return array 
  591. */ 
  592. function wc_get_product_cat_ids( $product_id ) { 
  593. $product_cats = wc_get_product_term_ids( $product_id, 'product_cat' ); 
  594.  
  595. foreach ( $product_cats as $product_cat ) { 
  596. $product_cats = array_merge( $product_cats, get_ancestors( $product_cat, 'product_cat' ) ); 
  597.  
  598. return $product_cats; 
  599.  
  600. /** 
  601. * Gets data about an attachment, such as alt text and captions. 
  602. * @since 2.6.0 
  603. * @param object|bool $product 
  604. * @return array 
  605. */ 
  606. function wc_get_product_attachment_props( $attachment_id = null, $product = false ) { 
  607. $props = array( 
  608. 'title' => '',  
  609. 'caption' => '',  
  610. 'url' => '',  
  611. 'alt' => '',  
  612. 'src' => '',  
  613. 'srcset' => false,  
  614. 'sizes' => false,  
  615. ); 
  616. if ( $attachment = get_post( $attachment_id ) ) { 
  617. $props['title'] = trim( strip_tags( $attachment->post_title ) ); 
  618. $props['caption'] = trim( strip_tags( $attachment->post_excerpt ) ); 
  619. $props['url'] = wp_get_attachment_url( $attachment_id ); 
  620. $props['alt'] = trim( strip_tags( get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ) ) ); 
  621.  
  622. // Large version. 
  623. $src = wp_get_attachment_image_src( $attachment_id, 'large' ); 
  624. $props['full_src'] = $src[0]; 
  625. $props['full_src_w'] = $src[1]; 
  626. $props['full_src_h'] = $src[2]; 
  627.  
  628. // Thumbnail version. 
  629. $src = wp_get_attachment_image_src( $attachment_id, 'shop_thumbnail' ); 
  630. $props['thumb_src'] = $src[0]; 
  631. $props['thumb_src_w'] = $src[1]; 
  632. $props['thumb_src_h'] = $src[2]; 
  633.  
  634. // Image source. 
  635. $src = wp_get_attachment_image_src( $attachment_id, 'shop_single' ); 
  636. $props['src'] = $src[0]; 
  637. $props['src_w'] = $src[1]; 
  638. $props['src_h'] = $src[2]; 
  639. $props['srcset'] = function_exists( 'wp_get_attachment_image_srcset' ) ? wp_get_attachment_image_srcset( $attachment_id, 'shop_single' ) : false; 
  640. $props['sizes'] = function_exists( 'wp_get_attachment_image_sizes' ) ? wp_get_attachment_image_sizes( $attachment_id, 'shop_single' ) : false; 
  641.  
  642. // Alt text fallbacks 
  643. $props['alt'] = empty( $props['alt'] ) ? $props['caption'] : $props['alt']; 
  644. $props['alt'] = empty( $props['alt'] ) ? trim( strip_tags( $attachment->post_title ) ) : $props['alt']; 
  645. $props['alt'] = empty( $props['alt'] ) && $product ? trim( strip_tags( get_the_title( $product->ID ) ) ) : $props['alt']; 
  646. return $props; 
  647.  
  648. /** 
  649. * Get product visibility options. 
  650. * 
  651. * @since 3.0.0 
  652. * @return array 
  653. */ 
  654. function wc_get_product_visibility_options() { 
  655. return apply_filters( 'woocommerce_product_visibility_options', array( 
  656. 'visible' => __( 'Visible', 'woocommerce' ),  
  657. 'catalog' => __( 'Catalog', 'woocommerce' ),  
  658. 'search' => __( 'Search', 'woocommerce' ),  
  659. 'hidden' => __( 'Hidden', 'woocommerce' ),  
  660. ) ); 
  661.  
  662. /** 
  663. * Get min/max price meta query args. 
  664. * 
  665. * @since 3.0.0 
  666. * @param array $args Min price and max price arguments. 
  667. * @return array 
  668. */ 
  669. function wc_get_min_max_price_meta_query( $args ) { 
  670. $min = isset( $args['min_price'] ) ? floatval( $args['min_price'] ) : 0; 
  671. $max = isset( $args['max_price'] ) ? floatval( $args['max_price'] ) : 9999999999; 
  672.  
  673. /** 
  674. * Adjust if the store taxes are not displayed how they are stored. 
  675. * Max is left alone because the filter was already increased. 
  676. * Kicks in when prices excluding tax are displayed including tax. 
  677. */ 
  678. if ( wc_tax_enabled() && 'incl' === get_option( 'woocommerce_tax_display_shop' ) && ! wc_prices_include_tax() ) { 
  679. $tax_classes = array_merge( array( '' ), WC_Tax::get_tax_classes() ); 
  680. $class_min = $min; 
  681.  
  682. foreach ( $tax_classes as $tax_class ) { 
  683. if ( $tax_rates = WC_Tax::get_rates( $tax_class ) ) { 
  684. $class_min = $min - WC_Tax::get_tax_total( WC_Tax::calc_exclusive_tax( $min, $tax_rates ) ); 
  685.  
  686. $min = $class_min; 
  687.  
  688. return array( 
  689. 'key' => '_price',  
  690. 'value' => array( $min, $max ),  
  691. 'compare' => 'BETWEEN',  
  692. 'type' => 'DECIMAL',  
  693. ); 
  694.  
  695. /** 
  696. * Get product tax class options. 
  697. * 
  698. * @since 3.0.0 
  699. * @return array 
  700. */ 
  701. function wc_get_product_tax_class_options() { 
  702. $tax_classes = WC_Tax::get_tax_classes(); 
  703. $tax_class_options = array(); 
  704. $tax_class_options[''] = __( 'Standard', 'woocommerce' ); 
  705.  
  706. if ( ! empty( $tax_classes ) ) { 
  707. foreach ( $tax_classes as $class ) { 
  708. $tax_class_options[ sanitize_title( $class ) ] = $class; 
  709. return $tax_class_options; 
  710.  
  711. /** 
  712. * Get stock status options. 
  713. * 
  714. * @since 3.0.0 
  715. * @return array 
  716. */ 
  717. function wc_get_product_stock_status_options() { 
  718. return array( 
  719. 'instock' => __( 'In stock', 'woocommerce' ),  
  720. 'outofstock' => __( 'Out of stock', 'woocommerce' ),  
  721. ); 
  722.  
  723. /** 
  724. * Get backorder options. 
  725. * 
  726. * @since 3.0.0 
  727. * @return array 
  728. */ 
  729. function wc_get_product_backorder_options() { 
  730. return array( 
  731. 'no' => __( 'Do not allow', 'woocommerce' ),  
  732. 'notify' => __( 'Allow, but notify customer', 'woocommerce' ),  
  733. 'yes' => __( 'Allow', 'woocommerce' ),  
  734. ); 
  735.  
  736. /** 
  737. * Get related products based on product category and tags. 
  738. * 
  739. * @since 3.0.0 
  740. * @param int $product_id Product ID. 
  741. * @param int $limit Limit of results. 
  742. * @param array $exclude_ids Exclude IDs from the results. 
  743. * @return array 
  744. */ 
  745. function wc_get_related_products( $product_id, $limit = 5, $exclude_ids = array() ) { 
  746. global $wpdb; 
  747.  
  748. $product_id = absint( $product_id ); 
  749. $exclude_ids = array_merge( array( 0, $product_id ), $exclude_ids ); 
  750. $transient_name = 'wc_related_' . $product_id; 
  751. $related_posts = get_transient( $transient_name ); 
  752. $limit = $limit > 0 ? $limit : 5; 
  753.  
  754. // We want to query related posts if they are not cached, or we don't have enough. 
  755. if ( false === $related_posts || count( $related_posts ) < $limit ) { 
  756. $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(); 
  757. $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(); 
  758.  
  759. // 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. 
  760. if ( empty( $cats_array ) && empty( $tags_array ) && ! apply_filters( 'woocommerce_product_related_posts_force_display', false, $product_id ) ) { 
  761. $related_posts = array(); 
  762. } else { 
  763. $data_store = WC_Data_Store::load( 'product' ); 
  764. $related_posts = $data_store->get_related_products( $cats_array, $tags_array, $exclude_ids, $limit + 10, $product_id ); 
  765.  
  766. set_transient( $transient_name, $related_posts, DAY_IN_SECONDS ); 
  767.  
  768. shuffle( $related_posts ); 
  769.  
  770. return array_slice( $related_posts, 0, $limit ); 
  771.  
  772. /** 
  773. * Retrieves product term ids for a taxonomy. 
  774. * 
  775. * @since 3.0.0 
  776. * @param int $product_id Product ID. 
  777. * @param string $taxonomy Taxonomy slug. 
  778. * @return array 
  779. */ 
  780. function wc_get_product_term_ids( $product_id, $taxonomy ) { 
  781. $terms = get_the_terms( $product_id, $taxonomy ); 
  782. return ! empty( $terms ) ? wp_list_pluck( $terms, 'term_id' ) : array(); 
  783.  
  784. /** 
  785. * For a given product, and optionally price/qty, work out the price with tax included, based on store settings. 
  786. * @since 3.0.0 
  787. * @param WC_Product $product 
  788. * @param array $args 
  789. * @return float 
  790. */ 
  791. function wc_get_price_including_tax( $product, $args = array() ) { 
  792. $args = wp_parse_args( $args, array( 
  793. 'qty' => '',  
  794. 'price' => '',  
  795. ) ); 
  796.  
  797. $price = '' !== $args['price'] ? max( 0.0, (float) $args['price'] ) : $product->get_price(); 
  798. $qty = '' !== $args['qty'] ? max( 0.0, (float) $args['qty'] ) : 1; 
  799.  
  800. if ( '' === $price ) { 
  801. return ''; 
  802. } elseif ( empty( $qty ) ) { 
  803. return 0.0; 
  804.  
  805. $line_price = $price * $qty; 
  806. $return_price = $line_price; 
  807.  
  808. if ( $product->is_taxable() ) { 
  809. if ( ! wc_prices_include_tax() ) { 
  810. $tax_rates = WC_Tax::get_rates( $product->get_tax_class() ); 
  811. $taxes = WC_Tax::calc_tax( $line_price, $tax_rates, false ); 
  812. $tax_amount = WC_Tax::get_tax_total( $taxes ); 
  813. $return_price = round( $line_price + $tax_amount, wc_get_price_decimals() ); 
  814. } else { 
  815. $tax_rates = WC_Tax::get_rates( $product->get_tax_class() ); 
  816. $base_tax_rates = WC_Tax::get_base_tax_rates( $product->get_tax_class( 'unfiltered' ) ); 
  817.  
  818. /** 
  819. * If the customer is excempt from VAT, remove the taxes here. 
  820. * Either remove the base or the user taxes depending on woocommerce_adjust_non_base_location_prices setting. 
  821. */ 
  822. if ( ! empty( WC()->customer ) && WC()->customer->get_is_vat_exempt() ) { 
  823. $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 ); 
  824. $remove_tax = array_sum( $remove_taxes ); 
  825. $return_price = round( $line_price - $remove_tax, wc_get_price_decimals() ); 
  826.  
  827. /** 
  828. * The woocommerce_adjust_non_base_location_prices filter can stop base taxes being taken off when dealing with out of base locations. 
  829. * e.g. If a product costs 10 including tax, all users will pay 10 regardless of location and taxes. 
  830. * This feature is experimental @since 2.4.7 and may change in the future. Use at your risk. 
  831. */ 
  832. } elseif ( $tax_rates !== $base_tax_rates && apply_filters( 'woocommerce_adjust_non_base_location_prices', true ) ) { 
  833. $base_taxes = WC_Tax::calc_tax( $line_price, $base_tax_rates, true ); 
  834. $modded_taxes = WC_Tax::calc_tax( $line_price - array_sum( $base_taxes ), $tax_rates, false ); 
  835. $return_price = round( $line_price - array_sum( $base_taxes ) + array_sum( $modded_taxes ), wc_get_price_decimals() ); 
  836. return apply_filters( 'woocommerce_get_price_including_tax', $return_price, $qty, $product ); 
  837.  
  838. /** 
  839. * For a given product, and optionally price/qty, work out the price with tax excluded, based on store settings. 
  840. * @since 3.0.0 
  841. * @param WC_Product $product 
  842. * @param array $args 
  843. * @return float 
  844. */ 
  845. function wc_get_price_excluding_tax( $product, $args = array() ) { 
  846. $args = wp_parse_args( $args, array( 
  847. 'qty' => '',  
  848. 'price' => '',  
  849. ) ); 
  850.  
  851. $price = '' !== $args['price'] ? max( 0.0, (float) $args['price'] ) : $product->get_price(); 
  852. $qty = '' !== $args['qty'] ? max( 0.0, (float) $args['qty'] ) : 1; 
  853.  
  854. if ( '' === $price ) { 
  855. return ''; 
  856. } elseif ( empty( $qty ) ) { 
  857. return 0.0; 
  858.  
  859. if ( $product->is_taxable() && wc_prices_include_tax() ) { 
  860. $tax_rates = WC_Tax::get_base_tax_rates( $product->get_tax_class( 'unfiltered' ) ); 
  861. $taxes = WC_Tax::calc_tax( $price * $qty, $tax_rates, true ); 
  862. $price = WC_Tax::round( $price * $qty - array_sum( $taxes ) ); 
  863. } else { 
  864. $price = $price * $qty; 
  865.  
  866. return apply_filters( 'woocommerce_get_price_excluding_tax', $price, $qty, $product ); 
  867.  
  868. /** 
  869. * Returns the price including or excluding tax, based on the 'woocommerce_tax_display_shop' setting. 
  870. * @since 3.0.0 
  871. * @param WC_Product $product 
  872. * @param array $args 
  873. * @return float 
  874. */ 
  875. function wc_get_price_to_display( $product, $args = array() ) { 
  876. $args = wp_parse_args( $args, array( 
  877. 'qty' => 1,  
  878. 'price' => $product->get_price(),  
  879. ) ); 
  880.  
  881. $price = $args['price']; 
  882. $qty = $args['qty']; 
  883.  
  884. 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 ) ); 
  885.  
  886. /** 
  887. * Returns the product categories in a list. 
  888. * 
  889. * @param int $product_id 
  890. * @param string $sep (default: ', '). 
  891. * @param string $before (default: ''). 
  892. * @param string $after (default: ''). 
  893. * @return string 
  894. */ 
  895. function wc_get_product_category_list( $product_id, $sep = ', ', $before = '', $after = '' ) { 
  896. return get_the_term_list( $product_id, 'product_cat', $before, $sep, $after ); 
  897.  
  898. /** 
  899. * Returns the product tags in a list. 
  900. * 
  901. * @param int $product_id 
  902. * @param string $sep (default: ', '). 
  903. * @param string $before (default: ''). 
  904. * @param string $after (default: ''). 
  905. * @return string 
  906. */ 
  907. function wc_get_product_tag_list( $product_id, $sep = ', ', $before = '', $after = '' ) { 
  908. return get_the_term_list( $product_id, 'product_tag', $before, $sep, $after ); 
  909.  
  910. /** 
  911. * Callback for array filter to get visible only. 
  912. * @since 3.0.0 
  913. * @param WC_Product $product 
  914. * @return bool 
  915. */ 
  916. function wc_products_array_filter_visible( $product ) { 
  917. return $product && is_a( $product, 'WC_Product' ) && $product->is_visible(); 
  918.  
  919. /** 
  920. * Callback for array filter to get products the user can edit only. 
  921. * 
  922. * @since 3.0.0 
  923. * @param WC_Product $product 
  924. * @return bool 
  925. */ 
  926. function wc_products_array_filter_editable( $product ) { 
  927. return $product && is_a( $product, 'WC_Product' ) && current_user_can( 'edit_product', $product->get_id() ); 
  928.  
  929. /** 
  930. * Sort an array of products by a value. 
  931. * @since 3.0.0 
  932. * @param array $products 
  933. * @param string $orderby 
  934. * @return array 
  935. */ 
  936. function wc_products_array_orderby( $products, $orderby = 'date', $order = 'desc' ) { 
  937. $orderby = strtolower( $orderby ); 
  938. $order = strtolower( $order ); 
  939. switch ( $orderby ) { 
  940. case 'title' : 
  941. case 'id' : 
  942. case 'date' : 
  943. case 'modified' : 
  944. case 'menu_order' : 
  945. case 'price' : 
  946. usort( $products, 'wc_products_array_orderby_' . $orderby ); 
  947. break; 
  948. default : 
  949. shuffle( $products ); 
  950. break; 
  951. if ( 'desc' === $order ) { 
  952. $products = array_reverse( $products ); 
  953. return $products; 
  954.  
  955. /** 
  956. * Sort by title. 
  957. * @since 3.0.0 
  958. * @param WC_Product object $a 
  959. * @param WC_Product object $b 
  960. * @return int 
  961. */ 
  962. function wc_products_array_orderby_title( $a, $b ) { 
  963. return strcasecmp( $a->get_name(), $b->get_name() ); 
  964.  
  965. /** 
  966. * Sort by id. 
  967. * @since 3.0.0 
  968. * @param WC_Product object $a 
  969. * @param WC_Product object $b 
  970. * @return int 
  971. */ 
  972. function wc_products_array_orderby_id( $a, $b ) { 
  973. if ( $a->get_id() === $b->get_id() ) { 
  974. return 0; 
  975. return ( $a->get_id() < $b->get_id() ) ? -1 : 1; 
  976.  
  977. /** 
  978. * Sort by date. 
  979. * @since 3.0.0 
  980. * @param WC_Product object $a 
  981. * @param WC_Product object $b 
  982. * @return int 
  983. */ 
  984. function wc_products_array_orderby_date( $a, $b ) { 
  985. if ( $a->get_date_created() === $b->get_date_created() ) { 
  986. return 0; 
  987. return ( $a->get_date_created() < $b->get_date_created() ) ? -1 : 1; 
  988.  
  989. /** 
  990. * Sort by modified. 
  991. * @since 3.0.0 
  992. * @param WC_Product object $a 
  993. * @param WC_Product object $b 
  994. * @return int 
  995. */ 
  996. function wc_products_array_orderby_modified( $a, $b ) { 
  997. if ( $a->get_date_modified() === $b->get_date_modified() ) { 
  998. return 0; 
  999. return ( $a->get_date_modified() < $b->get_date_modified() ) ? -1 : 1; 
  1000.  
  1001. /** 
  1002. * Sort by menu order. 
  1003. * @since 3.0.0 
  1004. * @param WC_Product object $a 
  1005. * @param WC_Product object $b 
  1006. * @return int 
  1007. */ 
  1008. function wc_products_array_orderby_menu_order( $a, $b ) { 
  1009. if ( $a->get_menu_order() === $b->get_menu_order() ) { 
  1010. return 0; 
  1011. return ( $a->get_menu_order() < $b->get_menu_order() ) ? -1 : 1; 
  1012.  
  1013. /** 
  1014. * Sort by price low to high. 
  1015. * @since 3.0.0 
  1016. * @param WC_Product object $a 
  1017. * @param WC_Product object $b 
  1018. * @return int 
  1019. */ 
  1020. function wc_products_array_orderby_price( $a, $b ) { 
  1021. if ( $a->get_price() === $b->get_price() ) { 
  1022. return 0; 
  1023. return ( $a->get_price() < $b->get_price() ) ? -1 : 1; 
  1024.  
  1025. /** 
  1026. * Queue a product for syncing at the end of the request. 
  1027. * 
  1028. * @param int $product_id 
  1029. */ 
  1030. function wc_deferred_product_sync( $product_id ) { 
  1031. global $wc_deferred_product_sync; 
  1032.  
  1033. if ( empty( $wc_deferred_product_sync ) ) { 
  1034. $wc_deferred_product_sync = array(); 
  1035.  
  1036. $wc_deferred_product_sync[] = $product_id; 
.