/includes/wc-term-functions.php

  1. <?php 
  2. /** 
  3. * WooCommerce Terms 
  4. * 
  5. * Functions for handling terms/term meta. 
  6. * 
  7. * @author WooThemes 
  8. * @category Core 
  9. * @package WooCommerce/Functions 
  10. * @version 2.1.0 
  11. */ 
  12.  
  13. if ( ! defined( 'ABSPATH' ) ) { 
  14. exit; // Exit if accessed directly 
  15.  
  16. /** 
  17. * Helper to get cached object terms and filter by field using wp_list_pluck(). 
  18. * Works as a cached alternative for wp_get_post_terms() and wp_get_object_terms(). 
  19. * 
  20. * @since 3.0.0 
  21. * @param int $object_id Object ID. 
  22. * @param string $taxonomy Taxonomy slug. 
  23. * @param string $field Field name. 
  24. * @param string $index_key Index key name. 
  25. * @return array 
  26. */ 
  27. function wc_get_object_terms( $object_id, $taxonomy, $field = null, $index_key = null ) { 
  28. // Test if terms exists. get_the_terms() return false when it finds no terms. 
  29. $terms = get_the_terms( $object_id, $taxonomy ); 
  30.  
  31. if ( $terms && ! is_wp_error( $terms ) ) { 
  32. if ( ! is_null( $field ) ) { 
  33. $terms = wp_list_pluck( $terms, $field, $index_key ); 
  34. } else { 
  35. $terms = array(); 
  36.  
  37. return $terms; 
  38.  
  39. /** 
  40. * Cached version of wp_get_post_terms(). 
  41. * This is a private function (internal use ONLY). 
  42. * 
  43. * @since 3.0.0 
  44. * @param int $product_id Product ID. 
  45. * @param string $taxonomy Taxonomy slug. 
  46. * @param array $args Query arguments. 
  47. * @return array 
  48. */ 
  49. function _wc_get_cached_product_terms( $product_id, $taxonomy, $args = array() ) { 
  50. $cache_key = 'wc_' . $taxonomy . md5( json_encode( $args ) ); 
  51. $terms = wp_cache_get( $product_id, $cache_key ); 
  52.  
  53. if ( false !== $terms ) { 
  54. return $terms; 
  55.  
  56. // @codingStandardsIgnoreStart 
  57. $terms = wp_get_post_terms( $product_id, $taxonomy, $args ); 
  58. // @codingStandardsIgnoreEnd 
  59.  
  60. wp_cache_add( $product_id, $terms, $cache_key ); 
  61.  
  62. return $terms; 
  63.  
  64. /** 
  65. * Wrapper for wp_get_post_terms which supports ordering by parent. 
  66. * 
  67. * NOTE: At this point in time, ordering by menu_order for example isn't possible with this function. wp_get_post_terms has no. 
  68. * filters which we can utilise to modify it's query. https://core.trac.wordpress.org/ticket/19094. 
  69. * 
  70. * @param int $product_id Product ID. 
  71. * @param string $taxonomy Taxonomy slug. 
  72. * @param array $args Query arguments. 
  73. * @return array 
  74. */ 
  75. function wc_get_product_terms( $product_id, $taxonomy, $args = array() ) { 
  76. if ( ! taxonomy_exists( $taxonomy ) ) { 
  77. return array(); 
  78.  
  79. if ( empty( $args['orderby'] ) && taxonomy_is_product_attribute( $taxonomy ) ) { 
  80. $args['orderby'] = wc_attribute_orderby( $taxonomy ); 
  81.  
  82. // Support ordering by parent. 
  83. if ( ! empty( $args['orderby'] ) && in_array( $args['orderby'], array( 'name_num', 'parent' ) ) ) { 
  84. $fields = isset( $args['fields'] ) ? $args['fields'] : 'all'; 
  85. $orderby = $args['orderby']; 
  86.  
  87. // Unset for wp_get_post_terms. 
  88. unset( $args['orderby'] ); 
  89. unset( $args['fields'] ); 
  90.  
  91. $terms = _wc_get_cached_product_terms( $product_id, $taxonomy, $args ); 
  92.  
  93. switch ( $orderby ) { 
  94. case 'name_num' : 
  95. usort( $terms, '_wc_get_product_terms_name_num_usort_callback' ); 
  96. break; 
  97. case 'parent' : 
  98. usort( $terms, '_wc_get_product_terms_parent_usort_callback' ); 
  99. break; 
  100.  
  101. switch ( $fields ) { 
  102. case 'names' : 
  103. $terms = wp_list_pluck( $terms, 'name' ); 
  104. break; 
  105. case 'ids' : 
  106. $terms = wp_list_pluck( $terms, 'term_id' ); 
  107. break; 
  108. case 'slugs' : 
  109. $terms = wp_list_pluck( $terms, 'slug' ); 
  110. break; 
  111. } elseif ( ! empty( $args['orderby'] ) && 'menu_order' === $args['orderby'] ) { 
  112. // wp_get_post_terms doesn't let us use custom sort order. 
  113. $args['include'] = wc_get_object_terms( $product_id, $taxonomy, 'term_id' ); 
  114.  
  115. if ( empty( $args['include'] ) ) { 
  116. $terms = array(); 
  117. } else { 
  118. // This isn't needed for get_terms. 
  119. unset( $args['orderby'] ); 
  120.  
  121. // Set args for get_terms. 
  122. $args['menu_order'] = isset( $args['order'] ) ? $args['order'] : 'ASC'; 
  123. $args['hide_empty'] = isset( $args['hide_empty'] ) ? $args['hide_empty'] : 0; 
  124. $args['fields'] = isset( $args['fields'] ) ? $args['fields'] : 'names'; 
  125.  
  126. // Ensure slugs is valid for get_terms - slugs isn't supported. 
  127. $args['fields'] = ( 'slugs' === $args['fields'] ) ? 'id=>slug' : $args['fields']; 
  128. $terms = get_terms( $taxonomy, $args ); 
  129. } else { 
  130. $terms = _wc_get_cached_product_terms( $product_id, $taxonomy, $args ); 
  131.  
  132. return apply_filters( 'woocommerce_get_product_terms' , $terms, $product_id, $taxonomy, $args ); 
  133.  
  134. /** 
  135. * Sort by name (numeric). 
  136. * @param WP_POST object $a 
  137. * @param WP_POST object $b 
  138. * @return int 
  139. */ 
  140. function _wc_get_product_terms_name_num_usort_callback( $a, $b ) { 
  141. $a_name = (int) $a->name; 
  142. $b_name = (int) $b->name; 
  143.  
  144. if ( $a_name === $b_name ) { 
  145. return 0; 
  146. return ( $a_name < $b_name ) ? -1 : 1; 
  147.  
  148. /** 
  149. * Sort by parent. 
  150. * @param WP_POST object $a 
  151. * @param WP_POST object $b 
  152. * @return int 
  153. */ 
  154. function _wc_get_product_terms_parent_usort_callback( $a, $b ) { 
  155. if ( $a->parent === $b->parent ) { 
  156. return 0; 
  157. return ( $a->parent < $b->parent ) ? 1 : -1; 
  158.  
  159. /** 
  160. * WooCommerce Dropdown categories. 
  161. * 
  162. * Stuck with this until a fix for https://core.trac.wordpress.org/ticket/13258. 
  163. * We use a custom walker, just like WordPress does. 
  164. * 
  165. * @param int $deprecated_show_uncategorized (default: 1) 
  166. * @return string 
  167. */ 
  168. function wc_product_dropdown_categories( $args = array(), $deprecated_hierarchical = 1, $deprecated_show_uncategorized = 1, $deprecated_orderby = '' ) { 
  169. global $wp_query; 
  170.  
  171. if ( ! is_array( $args ) ) { 
  172. wc_deprecated_argument( 'wc_product_dropdown_categories()', '2.1', 'show_counts, hierarchical, show_uncategorized and orderby arguments are invalid - pass a single array of values instead.' ); 
  173.  
  174. $args['show_count'] = $args; 
  175. $args['hierarchical'] = $deprecated_hierarchical; 
  176. $args['show_uncategorized'] = $deprecated_show_uncategorized; 
  177. $args['orderby'] = $deprecated_orderby; 
  178.  
  179. $current_product_cat = isset( $wp_query->query_vars['product_cat'] ) ? $wp_query->query_vars['product_cat'] : ''; 
  180. $defaults = array( 
  181. 'pad_counts' => 1,  
  182. 'show_count' => 1,  
  183. 'hierarchical' => 1,  
  184. 'hide_empty' => 1,  
  185. 'show_uncategorized' => 1,  
  186. 'orderby' => 'name',  
  187. 'selected' => $current_product_cat,  
  188. 'menu_order' => false,  
  189. ); 
  190.  
  191. $args = wp_parse_args( $args, $defaults ); 
  192.  
  193. if ( 'order' === $args['orderby'] ) { 
  194. $args['menu_order'] = 'asc'; 
  195. $args['orderby'] = 'name'; 
  196.  
  197. $terms = get_terms( 'product_cat', apply_filters( 'wc_product_dropdown_categories_get_terms_args', $args ) ); 
  198.  
  199. if ( empty( $terms ) ) { 
  200. return; 
  201.  
  202. $output = "<select name='product_cat' class='dropdown_product_cat'>"; 
  203. $output .= '<option value="" ' . selected( $current_product_cat, '', false ) . '>' . esc_html__( 'Select a category', 'woocommerce' ) . '</option>'; 
  204. $output .= wc_walk_category_dropdown_tree( $terms, 0, $args ); 
  205. if ( $args['show_uncategorized'] ) { 
  206. $output .= '<option value="0" ' . selected( $current_product_cat, '0', false ) . '>' . esc_html__( 'Uncategorized', 'woocommerce' ) . '</option>'; 
  207. $output .= "</select>"; 
  208.  
  209. echo $output; 
  210.  
  211. /** 
  212. * Walk the Product Categories. 
  213. * 
  214. * @return mixed 
  215. */ 
  216. function wc_walk_category_dropdown_tree() { 
  217. $args = func_get_args(); 
  218.  
  219. if ( ! class_exists( 'WC_Product_Cat_Dropdown_Walker', false ) ) { 
  220. include_once( WC()->plugin_path() . '/includes/walkers/class-product-cat-dropdown-walker.php' ); 
  221.  
  222. // the user's options are the third parameter 
  223. if ( empty( $args[2]['walker'] ) || ! is_a( $args[2]['walker'], 'Walker' ) ) { 
  224. $walker = new WC_Product_Cat_Dropdown_Walker; 
  225. } else { 
  226. $walker = $args[2]['walker']; 
  227.  
  228. return call_user_func_array( array( &$walker, 'walk' ), $args ); 
  229.  
  230. /** 
  231. * When a term is split, ensure meta data maintained. 
  232. * @param int $old_term_id 
  233. * @param int $new_term_id 
  234. * @param string $term_taxonomy_id 
  235. * @param string $taxonomy 
  236. */ 
  237. function wc_taxonomy_metadata_update_content_for_split_terms( $old_term_id, $new_term_id, $term_taxonomy_id, $taxonomy ) { 
  238. global $wpdb; 
  239.  
  240. if ( get_option( 'db_version' ) < 34370 ) { 
  241. if ( 'product_cat' === $taxonomy || taxonomy_is_product_attribute( $taxonomy ) ) { 
  242. $old_meta_data = $wpdb->get_results( $wpdb->prepare( "SELECT meta_key, meta_value FROM {$wpdb->prefix}woocommerce_termmeta WHERE woocommerce_term_id = %d;", $old_term_id ) ); 
  243.  
  244. // Copy across to split term 
  245. if ( $old_meta_data ) { 
  246. foreach ( $old_meta_data as $meta_data ) { 
  247. $wpdb->insert( 
  248. "{$wpdb->prefix}woocommerce_termmeta",  
  249. array( 
  250. 'woocommerce_term_id' => $new_term_id,  
  251. 'meta_key' => $meta_data->meta_key,  
  252. 'meta_value' => $meta_data->meta_value,  
  253. ); 
  254. add_action( 'split_shared_term', 'wc_taxonomy_metadata_update_content_for_split_terms', 10, 4 ); 
  255.  
  256. /** 
  257. * Migrate data from WC term meta to WP term meta 
  258. * 
  259. * When the database is updated to support term meta, migrate WC term meta data across. 
  260. * We do this when the new version is >= 34370, and the old version is < 34370 (34370 is when term meta table was added). 
  261. * 
  262. * @param string $wp_db_version The new $wp_db_version. 
  263. * @param string $wp_current_db_version The old (current) $wp_db_version. 
  264. */ 
  265. function wc_taxonomy_metadata_migrate_data( $wp_db_version, $wp_current_db_version ) { 
  266. if ( $wp_db_version >= 34370 && $wp_current_db_version < 34370 ) { 
  267. global $wpdb; 
  268. if ( $wpdb->query( "INSERT INTO {$wpdb->termmeta} ( term_id, meta_key, meta_value ) SELECT woocommerce_term_id, meta_key, meta_value FROM {$wpdb->prefix}woocommerce_termmeta;" ) ) { 
  269. $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}woocommerce_termmeta" ); 
  270. add_action( 'wp_upgrade', 'wc_taxonomy_metadata_migrate_data', 10, 2 ); 
  271.  
  272. /** 
  273. * WooCommerce Term Meta API 
  274. * 
  275. * WC tables for storing term meta are @deprecated from WordPress 4.4 since 4.4 has its own table. 
  276. * This function serves as a wrapper, using the new table if present, or falling back to the WC table. 
  277. * 
  278. * @param mixed $term_id 
  279. * @param string $meta_key 
  280. * @param mixed $meta_value 
  281. * @param string $prev_value (default: '') 
  282. * @return bool 
  283. */ 
  284. function update_woocommerce_term_meta( $term_id, $meta_key, $meta_value, $prev_value = '' ) { 
  285. return function_exists( 'update_term_meta' ) ? update_term_meta( $term_id, $meta_key, $meta_value, $prev_value ) : update_metadata( 'woocommerce_term', $term_id, $meta_key, $meta_value, $prev_value ); 
  286.  
  287. /** 
  288. * WooCommerce Term Meta API 
  289. * 
  290. * WC tables for storing term meta are @deprecated from WordPress 4.4 since 4.4 has its own table. 
  291. * This function serves as a wrapper, using the new table if present, or falling back to the WC table. 
  292. * 
  293. * @param mixed $term_id 
  294. * @param mixed $meta_key 
  295. * @param mixed $meta_value 
  296. * @param bool $unique (default: false) 
  297. * @return bool 
  298. */ 
  299. function add_woocommerce_term_meta( $term_id, $meta_key, $meta_value, $unique = false ) { 
  300. return function_exists( 'add_term_meta' ) ? add_term_meta( $term_id, $meta_key, $meta_value, $unique ) : add_metadata( 'woocommerce_term', $term_id, $meta_key, $meta_value, $unique ); 
  301.  
  302. /** 
  303. * WooCommerce Term Meta API 
  304. * 
  305. * WC tables for storing term meta are @deprecated from WordPress 4.4 since 4.4 has its own table. 
  306. * This function serves as a wrapper, using the new table if present, or falling back to the WC table. 
  307. * 
  308. * @param mixed $term_id 
  309. * @param string $meta_key 
  310. * @param string $meta_value (default: '') 
  311. * @param bool $deprecated (default: false) 
  312. * @return bool 
  313. */ 
  314. function delete_woocommerce_term_meta( $term_id, $meta_key, $meta_value = '', $deprecated = false ) { 
  315. return function_exists( 'delete_term_meta' ) ? delete_term_meta( $term_id, $meta_key, $meta_value ) : delete_metadata( 'woocommerce_term', $term_id, $meta_key, $meta_value ); 
  316.  
  317. /** 
  318. * WooCommerce Term Meta API 
  319. * 
  320. * WC tables for storing term meta are @deprecated from WordPress 4.4 since 4.4 has its own table. 
  321. * This function serves as a wrapper, using the new table if present, or falling back to the WC table. 
  322. * 
  323. * @param mixed $term_id 
  324. * @param string $key 
  325. * @param bool $single (default: true) 
  326. * @return mixed 
  327. */ 
  328. function get_woocommerce_term_meta( $term_id, $key, $single = true ) { 
  329. return function_exists( 'get_term_meta' ) ? get_term_meta( $term_id, $key, $single ) : get_metadata( 'woocommerce_term', $term_id, $key, $single ); 
  330.  
  331. /** 
  332. * Move a term before the a given element of its hierarchy level. 
  333. * 
  334. * @param int $the_term 
  335. * @param int $next_id the id of the next sibling element in save hierarchy level 
  336. * @param string $taxonomy 
  337. * @param int $index (default: 0) 
  338. * @param mixed $terms (default: null) 
  339. * @return int 
  340. */ 
  341. function wc_reorder_terms( $the_term, $next_id, $taxonomy, $index = 0, $terms = null ) { 
  342. if ( ! $terms ) $terms = get_terms( $taxonomy, 'menu_order=ASC&hide_empty=0&parent=0' ); 
  343. if ( empty( $terms ) ) return $index; 
  344.  
  345. $id = $the_term->term_id; 
  346.  
  347. $term_in_level = false; // flag: is our term to order in this level of terms 
  348.  
  349. foreach ( $terms as $term ) { 
  350.  
  351. if ( $term->term_id == $id ) { // our term to order, we skip 
  352. $term_in_level = true; 
  353. continue; // our term to order, we skip 
  354. // the nextid of our term to order, lets move our term here 
  355. if ( null !== $next_id && $term->term_id == $next_id ) { 
  356. $index++; 
  357. $index = wc_set_term_order( $id, $index, $taxonomy, true ); 
  358.  
  359. // set order 
  360. $index++; 
  361. $index = wc_set_term_order( $term->term_id, $index, $taxonomy ); 
  362.  
  363. // if that term has children we walk through them 
  364. $children = get_terms( $taxonomy, "parent={$term->term_id}&menu_order=ASC&hide_empty=0" ); 
  365. if ( ! empty( $children ) ) { 
  366. $index = wc_reorder_terms( $the_term, $next_id, $taxonomy, $index, $children ); 
  367.  
  368. // no nextid meaning our term is in last position 
  369. if ( $term_in_level && null === $next_id ) { 
  370. $index = wc_set_term_order( $id, $index + 1, $taxonomy, true ); 
  371.  
  372. return $index; 
  373.  
  374. /** 
  375. * Set the sort order of a term. 
  376. * 
  377. * @param int $term_id 
  378. * @param int $index 
  379. * @param string $taxonomy 
  380. * @param bool $recursive (default: false) 
  381. * @return int 
  382. */ 
  383. function wc_set_term_order( $term_id, $index, $taxonomy, $recursive = false ) { 
  384.  
  385. $term_id = (int) $term_id; 
  386. $index = (int) $index; 
  387.  
  388. // Meta name 
  389. if ( taxonomy_is_product_attribute( $taxonomy ) ) 
  390. $meta_name = 'order_' . esc_attr( $taxonomy ); 
  391. else 
  392. $meta_name = 'order'; 
  393.  
  394. update_woocommerce_term_meta( $term_id, $meta_name, $index ); 
  395.  
  396. if ( ! $recursive ) return $index; 
  397.  
  398. $children = get_terms( $taxonomy, "parent=$term_id&menu_order=ASC&hide_empty=0" ); 
  399.  
  400. foreach ( $children as $term ) { 
  401. $index++; 
  402. $index = wc_set_term_order( $term->term_id, $index, $taxonomy, true ); 
  403.  
  404. clean_term_cache( $term_id, $taxonomy ); 
  405.  
  406. return $index; 
  407.  
  408. /** 
  409. * Add term ordering to get_terms. 
  410. * 
  411. * It enables the support a 'menu_order' parameter to get_terms for the product_cat taxonomy. 
  412. * By default it is 'ASC'. It accepts 'DESC' too. 
  413. * 
  414. * To disable it, set it ot false (or 0). 
  415. * 
  416. * @param array $clauses 
  417. * @param array $taxonomies 
  418. * @param array $args 
  419. * @return array 
  420. */ 
  421. function wc_terms_clauses( $clauses, $taxonomies, $args ) { 
  422. global $wpdb; 
  423.  
  424. // No sorting when menu_order is false. 
  425. if ( isset( $args['menu_order'] ) && ( false === $args['menu_order'] || 'false' === $args['menu_order'] ) ) { 
  426. return $clauses; 
  427.  
  428. // No sorting when orderby is non default. 
  429. if ( isset( $args['orderby'] ) && 'name' !== $args['orderby'] ) { 
  430. return $clauses; 
  431.  
  432. // No sorting in admin when sorting by a column. 
  433. if ( is_admin() && isset( $_GET['orderby'] ) ) { 
  434. return $clauses; 
  435.  
  436. // No need to filter counts 
  437. if ( strpos( 'COUNT(*)', $clauses['fields'] ) !== false ) { 
  438. return $clauses; 
  439.  
  440. // WordPress should give us the taxonomies asked when calling the get_terms function. Only apply to categories and pa_ attributes. 
  441. $found = false; 
  442. foreach ( (array) $taxonomies as $taxonomy ) { 
  443. if ( taxonomy_is_product_attribute( $taxonomy ) || in_array( $taxonomy, apply_filters( 'woocommerce_sortable_taxonomies', array( 'product_cat' ) ) ) ) { 
  444. $found = true; 
  445. break; 
  446. if ( ! $found ) { 
  447. return $clauses; 
  448.  
  449. // Meta name
  450. if ( ! empty( $taxonomies[0] ) && taxonomy_is_product_attribute( $taxonomies[0] ) ) { 
  451. $meta_name = 'order_' . esc_attr( $taxonomies[0] ); 
  452. } else { 
  453. $meta_name = 'order'; 
  454.  
  455. // Query fields. 
  456. $clauses['fields'] = 'DISTINCT ' . $clauses['fields'] . ', tm.meta_value'; 
  457.  
  458. // Query join. 
  459. if ( get_option( 'db_version' ) < 34370 ) { 
  460. $clauses['join'] .= " LEFT JOIN {$wpdb->woocommerce_termmeta} AS tm ON (t.term_id = tm.woocommerce_term_id AND tm.meta_key = '" . esc_sql( $meta_name ) . "') "; 
  461. } else { 
  462. $clauses['join'] .= " LEFT JOIN {$wpdb->termmeta} AS tm ON (t.term_id = tm.term_id AND tm.meta_key = '" . esc_sql( $meta_name ) . "') "; 
  463.  
  464. // Default to ASC. 
  465. if ( ! isset( $args['menu_order'] ) || ! in_array( strtoupper( $args['menu_order'] ), array( 'ASC', 'DESC' ) ) ) { 
  466. $args['menu_order'] = 'ASC'; 
  467.  
  468. $order = "ORDER BY tm.meta_value+0 " . $args['menu_order']; 
  469.  
  470. if ( $clauses['orderby'] ) { 
  471. $clauses['orderby'] = str_replace( 'ORDER BY', $order . ', ', $clauses['orderby'] ); 
  472. } else { 
  473. $clauses['orderby'] = $order; 
  474.  
  475. return $clauses; 
  476.  
  477. add_filter( 'terms_clauses', 'wc_terms_clauses', 10, 3 ); 
  478.  
  479. /** 
  480. * Function for recounting product terms, ignoring hidden products. 
  481. * 
  482. * @param array $terms 
  483. * @param string $taxonomy 
  484. * @param bool $callback 
  485. * @param bool $terms_are_term_taxonomy_ids 
  486. */ 
  487. function _wc_term_recount( $terms, $taxonomy, $callback = true, $terms_are_term_taxonomy_ids = true ) { 
  488. global $wpdb; 
  489.  
  490. // Standard callback. 
  491. if ( $callback ) { 
  492. _update_post_term_count( $terms, $taxonomy ); 
  493.  
  494. $exclude_term_ids = array(); 
  495. $product_visibility_term_ids = wc_get_product_visibility_term_ids(); 
  496.  
  497. if ( $product_visibility_term_ids['exclude-from-catalog'] ) { 
  498. $exclude_term_ids[] = $product_visibility_term_ids['exclude-from-catalog']; 
  499.  
  500. if ( 'yes' === get_option( 'woocommerce_hide_out_of_stock_items' ) && $product_visibility_term_ids['outofstock'] ) { 
  501. $exclude_term_ids[] = $product_visibility_term_ids['outofstock']; 
  502.  
  503. $query = array( 
  504. 'fields' => " 
  505. SELECT COUNT( DISTINCT ID ) FROM {$wpdb->posts} p 
  506. ",  
  507. 'join' => '',  
  508. 'where' => " 
  509. WHERE 1=1 
  510. AND p.post_status = 'publish' 
  511. AND p.post_type = 'product' 
  512.  
  513. ",  
  514. ); 
  515.  
  516. if ( count( $exclude_term_ids ) ) { 
  517. $query['join'] .= " LEFT JOIN ( SELECT object_id FROM {$wpdb->term_relationships} WHERE term_taxonomy_id IN ( " . implode( ', ', array_map( 'absint', $exclude_term_ids ) ) . " ) ) AS exclude_join ON exclude_join.object_id = p.ID"; 
  518. $query['where'] .= " AND exclude_join.object_id IS NULL"; 
  519.  
  520. // Pre-process term taxonomy ids. 
  521. if ( ! $terms_are_term_taxonomy_ids ) { 
  522. // We passed in an array of TERMS in format id=>parent. 
  523. $terms = array_filter( (array) array_keys( $terms ) ); 
  524. } else { 
  525. // If we have term taxonomy IDs we need to get the term ID. 
  526. $term_taxonomy_ids = $terms; 
  527. $terms = array(); 
  528. foreach ( $term_taxonomy_ids as $term_taxonomy_id ) { 
  529. $term = get_term_by( 'term_taxonomy_id', $term_taxonomy_id, $taxonomy->name ); 
  530. $terms[] = $term->term_id; 
  531.  
  532. // Exit if we have no terms to count. 
  533. if ( empty( $terms ) ) { 
  534. return; 
  535.  
  536. // Ancestors need counting. 
  537. if ( is_taxonomy_hierarchical( $taxonomy->name ) ) { 
  538. foreach ( $terms as $term_id ) { 
  539. $terms = array_merge( $terms, get_ancestors( $term_id, $taxonomy->name ) ); 
  540.  
  541. // Unique terms only. 
  542. $terms = array_unique( $terms ); 
  543.  
  544. // Count the terms. 
  545. foreach ( $terms as $term_id ) { 
  546. $terms_to_count = array( absint( $term_id ) ); 
  547.  
  548. if ( is_taxonomy_hierarchical( $taxonomy->name ) ) { 
  549. // We need to get the $term's hierarchy so we can count its children too 
  550. if ( ( $children = get_term_children( $term_id, $taxonomy->name ) ) && ! is_wp_error( $children ) ) { 
  551. $terms_to_count = array_unique( array_map( 'absint', array_merge( $terms_to_count, $children ) ) ); 
  552.  
  553. // Generate term query 
  554. $term_query = $query; 
  555. $term_query['join'] .= " INNER JOIN ( SELECT object_id FROM {$wpdb->term_relationships} INNER JOIN {$wpdb->term_taxonomy} using( term_taxonomy_id ) WHERE term_id IN ( " . implode( ', ', array_map( 'absint', $terms_to_count ) ) . " ) ) AS include_join ON include_join.object_id = p.ID"; 
  556.  
  557. // Get the count 
  558. $count = $wpdb->get_var( implode( ' ', $term_query ) ); 
  559.  
  560. // Update the count 
  561. update_woocommerce_term_meta( $term_id, 'product_count_' . $taxonomy->name, absint( $count ) ); 
  562.  
  563. delete_transient( 'wc_term_counts' ); 
  564.  
  565. /** 
  566. * Recount terms after the stock amount changes. 
  567. * 
  568. * @param int $product_id 
  569. */ 
  570. function wc_recount_after_stock_change( $product_id ) { 
  571. if ( 'yes' !== get_option( 'woocommerce_hide_out_of_stock_items' ) ) { 
  572. return; 
  573.  
  574. $product_terms = get_the_terms( $product_id, 'product_cat' ); 
  575.  
  576. if ( $product_terms ) { 
  577. $product_cats = array(); 
  578.  
  579. foreach ( $product_terms as $term ) { 
  580. $product_cats[ $term->term_id ] = $term->parent; 
  581.  
  582. _wc_term_recount( $product_cats, get_taxonomy( 'product_cat' ), false, false ); 
  583.  
  584. $product_terms = get_the_terms( $product_id, 'product_tag' ); 
  585.  
  586. if ( $product_terms ) { 
  587. $product_tags = array(); 
  588.  
  589. foreach ( $product_terms as $term ) { 
  590. $product_tags[ $term->term_id ] = $term->parent; 
  591.  
  592. _wc_term_recount( $product_tags, get_taxonomy( 'product_tag' ), false, false ); 
  593. add_action( 'woocommerce_product_set_stock_status', 'wc_recount_after_stock_change' ); 
  594.  
  595.  
  596. /** 
  597. * Overrides the original term count for product categories and tags with the product count. 
  598. * that takes catalog visibility into account. 
  599. * 
  600. * @param array $terms 
  601. * @param string|array $taxonomies 
  602. * @return array 
  603. */ 
  604. function wc_change_term_counts( $terms, $taxonomies ) { 
  605. if ( is_admin() || is_ajax() ) { 
  606. return $terms; 
  607.  
  608. if ( ! isset( $taxonomies[0] ) || ! in_array( $taxonomies[0], apply_filters( 'woocommerce_change_term_counts', array( 'product_cat', 'product_tag' ) ) ) ) { 
  609. return $terms; 
  610.  
  611. $term_counts = $o_term_counts = get_transient( 'wc_term_counts' ); 
  612.  
  613. foreach ( $terms as &$term ) { 
  614. if ( is_object( $term ) ) { 
  615. $term_counts[ $term->term_id ] = isset( $term_counts[ $term->term_id ] ) ? $term_counts[ $term->term_id ] : get_woocommerce_term_meta( $term->term_id, 'product_count_' . $taxonomies[0] , true ); 
  616.  
  617. if ( '' !== $term_counts[ $term->term_id ] ) { 
  618. $term->count = absint( $term_counts[ $term->term_id ] ); 
  619.  
  620. // Update transient 
  621. if ( $term_counts != $o_term_counts ) { 
  622. set_transient( 'wc_term_counts', $term_counts, DAY_IN_SECONDS * 30 ); 
  623.  
  624. return $terms; 
  625. add_filter( 'get_terms', 'wc_change_term_counts', 10, 2 ); 
  626.  
  627. /** 
  628. * Return products in a given term, and cache value. 
  629. * 
  630. * To keep in sync, product_count will be cleared on "set_object_terms". 
  631. * 
  632. * @param int $term_id 
  633. * @param string $taxonomy 
  634. * @return array 
  635. */ 
  636. function wc_get_term_product_ids( $term_id, $taxonomy ) { 
  637. $product_ids = get_woocommerce_term_meta( $term_id, 'product_ids', true ); 
  638.  
  639. if ( false === $product_ids || ! is_array( $product_ids ) ) { 
  640. $product_ids = get_objects_in_term( $term_id, $taxonomy ); 
  641. update_woocommerce_term_meta( $term_id, 'product_ids', $product_ids ); 
  642.  
  643. return $product_ids; 
  644.  
  645. /** 
  646. * When a post is updated and terms recounted (called by _update_post_term_count), clear the ids. 
  647. * @param int $object_id Object ID. 
  648. * @param array $terms An array of object terms. 
  649. * @param array $tt_ids An array of term taxonomy IDs. 
  650. * @param string $taxonomy Taxonomy slug. 
  651. * @param bool $append Whether to append new terms to the old terms. 
  652. * @param array $old_tt_ids Old array of term taxonomy IDs. 
  653. */ 
  654. function wc_clear_term_product_ids( $object_id, $terms, $tt_ids, $taxonomy, $append, $old_tt_ids ) { 
  655. foreach ( $old_tt_ids as $term_id ) { 
  656. delete_woocommerce_term_meta( $term_id, 'product_ids' ); 
  657. foreach ( $tt_ids as $term_id ) { 
  658. delete_woocommerce_term_meta( $term_id, 'product_ids' ); 
  659. add_action( 'set_object_terms', 'wc_clear_term_product_ids', 10, 6 ); 
  660.  
  661. /** 
  662. * Get full list of product visibilty term ids. 
  663. * 
  664. * @since 3.0.0 
  665. * @return int[] 
  666. */ 
  667. function wc_get_product_visibility_term_ids() { 
  668. return array_map( 'absint', wp_parse_args( 
  669. wp_list_pluck( 
  670. get_terms( array( 
  671. 'taxonomy' => 'product_visibility',  
  672. 'hide_empty' => false,  
  673. ) ),  
  674. 'term_taxonomy_id',  
  675. 'name' 
  676. ),  
  677. array( 
  678. 'exclude-from-catalog' => 0,  
  679. 'exclude-from-search' => 0,  
  680. 'featured' => 0,  
  681. 'outofstock' => 0,  
  682. 'rated-1' => 0,  
  683. 'rated-2' => 0,  
  684. 'rated-3' => 0,  
  685. 'rated-4' => 0,  
  686. 'rated-5' => 0,  
  687. ) ); 
.