wc-term-functions.php
Functions
wc_change_get_terms_defaults()
Change get terms defaults for attributes to order by the sorting setting, or default to menu_order for sortable taxonomies.
wc_change_get_terms_defaults(array<string|int, mixed> $defaults, array<string|int, mixed> $taxonomies) : array<string|int, mixed>
Parameters
- $defaults : array<string|int, mixed>
-
An array of default get_terms() arguments.
- $taxonomies : array<string|int, mixed>
-
An array of taxonomies.
Tags
wc_change_pre_get_terms()
Adds support to get_terms for menu_order argument.
wc_change_pre_get_terms(WP_Term_Query $terms_query) : mixed
Parameters
- $terms_query : WP_Term_Query
-
Instance of WP_Term_Query.
Tags
wc_terms_clauses()
Adjust term query to handle custom sorting parameters.
wc_terms_clauses(array<string|int, mixed> $clauses, array<string|int, mixed> $taxonomies, array<string|int, mixed> $args) : array<string|int, mixed>
Parameters
- $clauses : array<string|int, mixed>
-
Clauses.
- $taxonomies : array<string|int, mixed>
-
Taxonomies.
- $args : array<string|int, mixed>
-
Arguments.
wc_get_object_terms()
Helper to get cached object terms and filter by field using wp_list_pluck().
wc_get_object_terms(int $object_id, string $taxonomy[, string $field = null ][, string $index_key = null ]) : array<string|int, mixed>
Works as a cached alternative for wp_get_post_terms() and wp_get_object_terms().
Parameters
- $object_id : int
-
Object ID.
- $taxonomy : string
-
Taxonomy slug.
- $field : string = null
-
Field name.
- $index_key : string = null
-
Index key name.
Tags
_wc_get_cached_product_terms()
Cached version of wp_get_post_terms().
_wc_get_cached_product_terms(int $product_id, string $taxonomy[, array<string|int, mixed> $args = array() ]) : array<string|int, mixed>
This is a private function (internal use ONLY).
Parameters
- $product_id : int
-
Product ID.
- $taxonomy : string
-
Taxonomy slug.
- $args : array<string|int, mixed> = array()
-
Query arguments.
Tags
wc_get_product_terms()
Wrapper used to get terms for a product.
wc_get_product_terms(int $product_id, string $taxonomy[, array<string|int, mixed> $args = array() ]) : array<string|int, mixed>
Parameters
- $product_id : int
-
Product ID.
- $taxonomy : string
-
Taxonomy slug.
- $args : array<string|int, mixed> = array()
-
Query arguments.
_wc_get_product_terms_name_num_usort_callback()
Sort by name (numeric).
_wc_get_product_terms_name_num_usort_callback(WP_Post $a, WP_Post $b) : int
Parameters
- $a : WP_Post
-
First item to compare.
- $b : WP_Post
-
Second item to compare.
_wc_get_product_terms_parent_usort_callback()
Sort by parent.
_wc_get_product_terms_parent_usort_callback(WP_Post $a, WP_Post $b) : int
Parameters
- $a : WP_Post
-
First item to compare.
- $b : WP_Post
-
Second item to compare.
wc_product_dropdown_categories()
WooCommerce Dropdown categories.
wc_product_dropdown_categories([array<string|int, mixed> $args = array() ]) : mixed
Parameters
- $args : array<string|int, mixed> = array()
-
Args to control display of dropdown.
wc_walk_category_dropdown_tree()
Custom walker for Product Categories.
wc_walk_category_dropdown_tree(mixed ...$args) : mixed
Previously used by wc_product_dropdown_categories, but wp_dropdown_categories has been fixed in core.
Parameters
- $args : mixed
-
Variable number of parameters to be passed to the walker.
wc_taxonomy_metadata_migrate_data()
Migrate data from WC term meta to WP term meta.
wc_taxonomy_metadata_migrate_data(string $wp_db_version, string $wp_current_db_version) : mixed
When the database is updated to support term meta, migrate WC term meta data across. We do this when the new version is >= 34370, and the old version is < 34370 (34370 is when term meta table was added).
Parameters
- $wp_db_version : string
-
The new $wp_db_version.
- $wp_current_db_version : string
-
The old (current) $wp_db_version.
wc_reorder_terms()
Move a term before the a given element of its hierarchy level.
wc_reorder_terms(int $the_term, int $next_id, string $taxonomy, int $index[, mixed $terms = null ]) : int
Parameters
- $the_term : int
-
Term ID.
- $next_id : int
-
The id of the next sibling element in save hierarchy level.
- $taxonomy : string
-
Taxonomy.
- $index : int
-
Term index (default: 0).
- $terms : mixed = null
-
List of terms. (default: null).
wc_set_term_order()
Set the sort order of a term.
wc_set_term_order(int $term_id, int $index, string $taxonomy[, bool $recursive = false ]) : int
Parameters
- $term_id : int
-
Term ID.
- $index : int
-
Index.
- $taxonomy : string
-
Taxonomy.
- $recursive : bool = false
-
Recursive (default: false).
_wc_term_recount()
Function for recounting product terms, ignoring hidden products.
_wc_term_recount(array<string|int, mixed> $terms, WP_Taxonomy $taxonomy[, bool $callback = true ][, bool $terms_are_term_taxonomy_ids = true ]) : mixed
This is used as the update_count_callback for the Product Category and Product Tag
taxonomies. By default, it actually calculates two (possibly different) counts for each
term, which it stores in two different places. The first count is the one done by WordPress
itself, and is based on the status of the objects that are assigned the terms. In this case,
only products with the publish status are counted. This count is stored in the
wp_term_taxonomy
table in the count
field.
The second count is based on WooCommerce-specific characteristics. In addition to the
publish status requirement, products are only counted if they are considered visible in the
catalog. This count is stored in the wp_termmeta
table. The wc_change_term_counts function
is used to override the first count with the second count in some circumstances.
Since the first count only needs to be recalculated when a product status is changed in some way, it can sometimes be skipped (thus avoiding some potentially expensive queries). Setting the $callback parameter to false skips the first count.
Parameters
- $terms : array<string|int, mixed>
-
List of terms. For legacy reasons, this can either be a list of taxonomy term IDs or an associative array in the format of term ID > parent term ID.
- $taxonomy : WP_Taxonomy
-
The relevant taxonomy.
- $callback : bool = true
-
Whether to also recalculate the term counts using the WP Core callback. Default true.
- $terms_are_term_taxonomy_ids : bool = true
-
Flag to indicate which format the list of terms is in. Default true, which indicates that it is a list of taxonomy term IDs.
wc_recount_after_stock_change()
Recount terms after the stock amount changes.
wc_recount_after_stock_change(int $product_id) : mixed
Parameters
- $product_id : int
-
Product ID.
wc_change_term_counts()
Overrides the original term count for product categories and tags with the product count.
wc_change_term_counts(array<string|int, mixed> $terms, string|array<string|int, mixed> $taxonomies) : array<string|int, mixed>
that takes catalog visibility into account.
Parameters
- $terms : array<string|int, mixed>
-
List of terms.
- $taxonomies : string|array<string|int, mixed>
-
Single taxonomy or list of taxonomies.
wc_get_term_product_ids()
Return products in a given term, and cache value.
wc_get_term_product_ids(int $term_id, string $taxonomy) : array<string|int, mixed>
To keep in sync, product_count will be cleared on "set_object_terms".
Parameters
- $term_id : int
-
Term ID.
- $taxonomy : string
-
Taxonomy.
wc_clear_term_product_ids()
When a post is updated and terms recounted (called by _update_post_term_count), clear the ids.
wc_clear_term_product_ids(int $object_id, array<string|int, mixed> $terms, array<string|int, mixed> $tt_ids, string $taxonomy, bool $append, array<string|int, mixed> $old_tt_ids) : mixed
Parameters
- $object_id : int
-
Object ID.
- $terms : array<string|int, mixed>
-
An array of object terms.
- $tt_ids : array<string|int, mixed>
-
An array of term taxonomy IDs.
- $taxonomy : string
-
Taxonomy slug.
- $append : bool
-
Whether to append new terms to the old terms.
- $old_tt_ids : array<string|int, mixed>
-
Old array of term taxonomy IDs.
wc_get_product_visibility_term_ids()
Get full list of product visibility term ids.
wc_get_product_visibility_term_ids() : array<string|int, int>
Tags
wc_recount_all_terms()
Recounts all terms for product categories and product tags.
wc_recount_all_terms([bool $include_callback = true ]) : void
Parameters
- $include_callback : bool = true
-
True to update the standard term counts in addition to the product-specific counts, which will cause a lot more queries to run.
Tags
_wc_recount_terms_by_product()
Recounts terms by product.
_wc_recount_terms_by_product([int $product_id = '' ]) : void
Parameters
- $product_id : int = ''
-
The ID of the product.
Tags
Source code
<?php
/**
* WooCommerce Terms
*
* Functions for handling terms/term meta.
*
* @package WooCommerce\Functions
* @version 2.1.0
*/
defined( 'ABSPATH' ) || exit;
use Automattic\WooCommerce\Enums\ProductStockStatus;
/**
* Change get terms defaults for attributes to order by the sorting setting, or default to menu_order for sortable taxonomies.
*
* @since 3.6.0 Sorting options are now set as the default automatically, so you no longer have to request to orderby menu_order.
*
* @param array $defaults An array of default get_terms() arguments.
* @param array $taxonomies An array of taxonomies.
* @return array
*/
function wc_change_get_terms_defaults( $defaults, $taxonomies ) {
if ( is_array( $taxonomies ) && 1 < count( $taxonomies ) ) {
return $defaults;
}
$taxonomy = is_array( $taxonomies ) ? (string) current( $taxonomies ) : $taxonomies;
$orderby = 'name';
if ( taxonomy_is_product_attribute( $taxonomy ) ) {
$orderby = wc_attribute_orderby( $taxonomy );
} elseif ( in_array( $taxonomy, apply_filters( 'woocommerce_sortable_taxonomies', array( 'product_cat' ) ), true ) ) {
$orderby = 'menu_order';
}
// Change defaults. Invalid values will be changed later @see wc_change_pre_get_terms.
// These are in place so we know if a specific order was requested.
switch ( $orderby ) {
case 'menu_order':
case 'name_num':
case 'parent':
$defaults['orderby'] = $orderby;
break;
}
return $defaults;
}
add_filter( 'get_terms_defaults', 'wc_change_get_terms_defaults', 10, 2 );
/**
* Adds support to get_terms for menu_order argument.
*
* @since 3.6.0
* @param WP_Term_Query $terms_query Instance of WP_Term_Query.
*/
function wc_change_pre_get_terms( $terms_query ) {
$args = &$terms_query->query_vars;
// Put back valid orderby values.
if ( 'menu_order' === $args['orderby'] ) {
$args['orderby'] = 'name';
$args['force_menu_order_sort'] = true;
}
if ( 'name_num' === $args['orderby'] ) {
$args['orderby'] = 'name';
$args['force_numeric_name'] = true;
}
// When COUNTING, disable custom sorting.
if ( 'count' === $args['fields'] ) {
return;
}
// Support menu_order arg used in previous versions.
if ( ! empty( $args['menu_order'] ) ) {
$args['order'] = 'DESC' === strtoupper( $args['menu_order'] ) ? 'DESC' : 'ASC';
$args['force_menu_order_sort'] = true;
}
if ( ! empty( $args['force_menu_order_sort'] ) ) {
$args['orderby'] = 'meta_value_num';
$args['meta_key'] = 'order'; // phpcs:ignore
$terms_query->meta_query->parse_query_vars( $args );
}
}
add_action( 'pre_get_terms', 'wc_change_pre_get_terms', 10, 1 );
/**
* Adjust term query to handle custom sorting parameters.
*
* @param array $clauses Clauses.
* @param array $taxonomies Taxonomies.
* @param array $args Arguments.
* @return array
*/
function wc_terms_clauses( $clauses, $taxonomies, $args ) {
global $wpdb;
// No need to filter when counting.
if ( strpos( $clauses['fields'], 'COUNT(*)' ) !== false ) {
return $clauses;
}
// Force numeric sort if using name_num custom sorting param.
if ( ! empty( $args['force_numeric_name'] ) ) {
$clauses['orderby'] = str_replace( 'ORDER BY t.name', 'ORDER BY t.name+0', $clauses['orderby'] );
}
// For sorting, force left join in case order meta is missing.
if ( ! empty( $args['force_menu_order_sort'] ) ) {
$clauses['join'] = str_replace( "INNER JOIN {$wpdb->termmeta} ON ( t.term_id = {$wpdb->termmeta}.term_id )", "LEFT JOIN {$wpdb->termmeta} ON ( t.term_id = {$wpdb->termmeta}.term_id AND {$wpdb->termmeta}.meta_key='order')", $clauses['join'] );
$clauses['where'] = str_replace( "{$wpdb->termmeta}.meta_key = 'order'", "( {$wpdb->termmeta}.meta_key = 'order' OR {$wpdb->termmeta}.meta_key IS NULL )", $clauses['where'] );
$clauses['orderby'] = 'DESC' === $args['order'] ? str_replace( 'meta_value+0', 'meta_value+0 DESC, t.name', $clauses['orderby'] ) : str_replace( 'meta_value+0', 'meta_value+0 ASC, t.name', $clauses['orderby'] );
}
return $clauses;
}
add_filter( 'terms_clauses', 'wc_terms_clauses', 99, 3 );
/**
* Helper to get cached object terms and filter by field using wp_list_pluck().
* Works as a cached alternative for wp_get_post_terms() and wp_get_object_terms().
*
* @since 3.0.0
* @param int $object_id Object ID.
* @param string $taxonomy Taxonomy slug.
* @param string $field Field name.
* @param string $index_key Index key name.
* @return array
*/
function wc_get_object_terms( $object_id, $taxonomy, $field = null, $index_key = null ) {
// Test if terms exists. get_the_terms() return false when it finds no terms.
$terms = get_the_terms( $object_id, $taxonomy );
if ( ! $terms || is_wp_error( $terms ) ) {
return array();
}
return is_null( $field ) ? $terms : wp_list_pluck( $terms, $field, $index_key );
}
/**
* Cached version of wp_get_post_terms().
* This is a private function (internal use ONLY).
*
* @since 3.0.0
* @param int $product_id Product ID.
* @param string $taxonomy Taxonomy slug.
* @param array $args Query arguments.
* @return array
*/
function _wc_get_cached_product_terms( $product_id, $taxonomy, $args = array() ) {
$cache_key = 'wc_' . $taxonomy . md5( wp_json_encode( $args ) );
$cache_group = WC_Cache_Helper::get_cache_prefix( 'product_' . $product_id ) . $product_id;
$terms = wp_cache_get( $cache_key, $cache_group );
if ( false !== $terms ) {
return $terms;
}
$terms = wp_get_post_terms( $product_id, $taxonomy, $args );
wp_cache_add( $cache_key, $terms, $cache_group );
return $terms;
}
/**
* Wrapper used to get terms for a product.
*
* @param int $product_id Product ID.
* @param string $taxonomy Taxonomy slug.
* @param array $args Query arguments.
* @return array
*/
function wc_get_product_terms( $product_id, $taxonomy, $args = array() ) {
if ( ! taxonomy_exists( $taxonomy ) ) {
return array();
}
return apply_filters( 'woocommerce_get_product_terms', _wc_get_cached_product_terms( $product_id, $taxonomy, $args ), $product_id, $taxonomy, $args );
}
/**
* Sort by name (numeric).
*
* @param WP_Post $a First item to compare.
* @param WP_Post $b Second item to compare.
* @return int
*/
function _wc_get_product_terms_name_num_usort_callback( $a, $b ) {
$a_name = (float) $a->name;
$b_name = (float) $b->name;
if ( abs( $a_name - $b_name ) < 0.001 ) {
return 0;
}
return ( $a_name < $b_name ) ? -1 : 1;
}
/**
* Sort by parent.
*
* @param WP_Post $a First item to compare.
* @param WP_Post $b Second item to compare.
* @return int
*/
function _wc_get_product_terms_parent_usort_callback( $a, $b ) {
if ( $a->parent === $b->parent ) {
return 0;
}
return ( $a->parent < $b->parent ) ? 1 : -1;
}
/**
* WooCommerce Dropdown categories.
*
* @param array $args Args to control display of dropdown.
*/
function wc_product_dropdown_categories( $args = array() ) {
global $wp_query;
$args = wp_parse_args(
$args,
array(
'pad_counts' => 1,
'show_count' => 1,
'hierarchical' => 1,
'hide_empty' => 1,
'show_uncategorized' => 1,
'orderby' => 'name',
'selected' => isset( $wp_query->query_vars['product_cat'] ) ? $wp_query->query_vars['product_cat'] : '',
'show_option_none' => __( 'Select a category', 'woocommerce' ),
'option_none_value' => '',
'value_field' => 'slug',
'taxonomy' => 'product_cat',
'name' => 'product_cat',
'class' => 'dropdown_product_cat',
)
);
if ( 'order' === $args['orderby'] ) {
$args['orderby'] = 'meta_value_num';
$args['meta_key'] = 'order'; // phpcs:ignore
}
wp_dropdown_categories( $args );
}
/**
* Custom walker for Product Categories.
*
* Previously used by wc_product_dropdown_categories, but wp_dropdown_categories has been fixed in core.
*
* @param mixed ...$args Variable number of parameters to be passed to the walker.
* @return mixed
*/
function wc_walk_category_dropdown_tree( ...$args ) {
if ( ! class_exists( 'WC_Product_Cat_Dropdown_Walker', false ) ) {
include_once WC()->plugin_path() . '/includes/walkers/class-wc-product-cat-dropdown-walker.php';
}
// The user's options are the third parameter.
if ( empty( $args[2]['walker'] ) || ! is_a( $args[2]['walker'], 'Walker' ) ) {
$walker = new WC_Product_Cat_Dropdown_Walker();
} else {
$walker = $args[2]['walker'];
}
return $walker->walk( ...$args );
}
/**
* Migrate data from WC term meta to WP term meta.
*
* When the database is updated to support term meta, migrate WC term meta data across.
* We do this when the new version is >= 34370, and the old version is < 34370 (34370 is when term meta table was added).
*
* @param string $wp_db_version The new $wp_db_version.
* @param string $wp_current_db_version The old (current) $wp_db_version.
*/
function wc_taxonomy_metadata_migrate_data( $wp_db_version, $wp_current_db_version ) {
if ( $wp_db_version >= 34370 && $wp_current_db_version < 34370 ) {
global $wpdb;
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;" ) ) {
$wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}woocommerce_termmeta" );
}
}
}
add_action( 'wp_upgrade', 'wc_taxonomy_metadata_migrate_data', 10, 2 );
/**
* Move a term before the a given element of its hierarchy level.
*
* @param int $the_term Term ID.
* @param int $next_id The id of the next sibling element in save hierarchy level.
* @param string $taxonomy Taxonomy.
* @param int $index Term index (default: 0).
* @param mixed $terms List of terms. (default: null).
* @return int
*/
function wc_reorder_terms( $the_term, $next_id, $taxonomy, $index = 0, $terms = null ) {
if ( ! $terms ) {
$terms = get_terms( $taxonomy, 'hide_empty=0&parent=0&menu_order=ASC' );
}
if ( empty( $terms ) ) {
return $index;
}
$id = intval( $the_term->term_id );
$term_in_level = false; // Flag: is our term to order in this level of terms.
foreach ( $terms as $term ) {
$term_id = intval( $term->term_id );
if ( $term_id === $id ) { // Our term to order, we skip.
$term_in_level = true;
continue; // Our term to order, we skip.
}
// the nextid of our term to order, lets move our term here.
if ( null !== $next_id && $term_id === $next_id ) {
$index++;
$index = wc_set_term_order( $id, $index, $taxonomy, true );
}
// Set order.
$index++;
$index = wc_set_term_order( $term_id, $index, $taxonomy );
/**
* After a term has had it's order set.
*/
do_action( 'woocommerce_after_set_term_order', $term, $index, $taxonomy );
// If that term has children we walk through them.
$children = get_terms( $taxonomy, "parent={$term_id}&hide_empty=0&menu_order=ASC" );
if ( ! empty( $children ) ) {
$index = wc_reorder_terms( $the_term, $next_id, $taxonomy, $index, $children );
}
}
// No nextid meaning our term is in last position.
if ( $term_in_level && null === $next_id ) {
$index = wc_set_term_order( $id, $index + 1, $taxonomy, true );
}
return $index;
}
/**
* Set the sort order of a term.
*
* @param int $term_id Term ID.
* @param int $index Index.
* @param string $taxonomy Taxonomy.
* @param bool $recursive Recursive (default: false).
* @return int
*/
function wc_set_term_order( $term_id, $index, $taxonomy, $recursive = false ) {
$term_id = (int) $term_id;
$index = (int) $index;
update_term_meta( $term_id, 'order', $index );
if ( ! $recursive ) {
return $index;
}
$children = get_terms( $taxonomy, "parent=$term_id&hide_empty=0&menu_order=ASC" );
foreach ( $children as $term ) {
$index++;
$index = wc_set_term_order( $term->term_id, $index, $taxonomy, true );
}
clean_term_cache( $term_id, $taxonomy );
return $index;
}
/**
* Function for recounting product terms, ignoring hidden products.
*
* This is used as the update_count_callback for the Product Category and Product Tag
* taxonomies. By default, it actually calculates two (possibly different) counts for each
* term, which it stores in two different places. The first count is the one done by WordPress
* itself, and is based on the status of the objects that are assigned the terms. In this case,
* only products with the publish status are counted. This count is stored in the
* `wp_term_taxonomy` table in the `count` field.
*
* The second count is based on WooCommerce-specific characteristics. In addition to the
* publish status requirement, products are only counted if they are considered visible in the
* catalog. This count is stored in the `wp_termmeta` table. The wc_change_term_counts function
* is used to override the first count with the second count in some circumstances.
*
* Since the first count only needs to be recalculated when a product status is changed in some
* way, it can sometimes be skipped (thus avoiding some potentially expensive queries). Setting
* the $callback parameter to false skips the first count.
*
* @param array $terms List of terms. For legacy reasons, this can
* either be a list of taxonomy term IDs or an
* associative array in the format of
* term ID > parent term ID.
* @param WP_Taxonomy $taxonomy The relevant taxonomy.
* @param bool $callback Whether to also recalculate the term counts
* using the WP Core callback. Default true.
* @param bool $terms_are_term_taxonomy_ids Flag to indicate which format the list of
* terms is in. Default true, which indicates
* that it is a list of taxonomy term IDs.
*/
function _wc_term_recount( $terms, $taxonomy, $callback = true, $terms_are_term_taxonomy_ids = true ) {
global $wpdb;
/**
* Filter to allow/prevent recounting of terms as it could be expensive.
* A likely scenario for this is when bulk importing products. We could
* then prevent it from recounting per product but instead recount it once
* when import is done. Of course this means the import logic has to support this.
*
* @since 5.2
* @param bool
*/
if ( ! apply_filters( 'woocommerce_product_recount_terms', true ) ) {
return;
}
if ( true === $terms_are_term_taxonomy_ids ) {
$taxonomy_term_ids = $terms;
$term_ids = array_map(
function ( $term_taxonomy_id ) use ( $taxonomy ) {
$term = get_term_by( 'term_taxonomy_id', $term_taxonomy_id, $taxonomy->name );
return $term instanceof WP_Term ? $term->term_id : null;
},
$terms
);
} else {
$taxonomy_term_ids = array(); // Defer querying these until the callback check.
$term_ids = array_keys( $terms );
}
$term_ids = array_unique( array_filter( $term_ids ) );
$taxonomy_term_ids = array_unique( array_filter( $taxonomy_term_ids ) );
// Exit if we have no terms to count.
if ( empty( $term_ids ) ) {
return;
}
// Standard WP callback for calculating post term counts.
if ( $callback ) {
if ( count( $taxonomy_term_ids ) < 1 ) {
$taxonomy_term_ids = array_map(
function ( $term_id ) use ( $taxonomy ) {
$term = get_term_by( 'term_id', $term_id, $taxonomy->name );
return $term instanceof WP_Term ? $term->term_taxonomy_id : null;
},
$term_ids
);
}
_update_post_term_count( $taxonomy_term_ids, $taxonomy );
}
$exclude_term_ids = array();
$product_visibility_term_ids = wc_get_product_visibility_term_ids();
if ( $product_visibility_term_ids['exclude-from-catalog'] ) {
$exclude_term_ids[] = $product_visibility_term_ids['exclude-from-catalog'];
}
if (
'yes' === get_option( 'woocommerce_hide_out_of_stock_items' )
&& $product_visibility_term_ids[ ProductStockStatus::OUT_OF_STOCK ]
) {
$exclude_term_ids[] = $product_visibility_term_ids[ ProductStockStatus::OUT_OF_STOCK ];
}
$query = array(
'fields' => "
SELECT COUNT( DISTINCT ID ) FROM {$wpdb->posts} p
",
'join' => '',
'where' => "
WHERE 1=1
AND p.post_status = 'publish'
AND p.post_type = 'product'
",
);
if ( count( $exclude_term_ids ) ) {
$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';
$query['where'] .= ' AND exclude_join.object_id IS NULL';
}
// Ancestors need counting.
if ( is_taxonomy_hierarchical( $taxonomy->name ) ) {
foreach ( $term_ids as $term_id ) {
$term_ids = array_merge( $term_ids, get_ancestors( $term_id, $taxonomy->name ) );
}
$term_ids = array_unique( $term_ids );
}
// Count the terms.
foreach ( $term_ids as $term_id ) {
$terms_to_count = array( absint( $term_id ) );
if ( is_taxonomy_hierarchical( $taxonomy->name ) ) {
// We need to get the $term's hierarchy so we can count its children too.
$children = get_term_children( $term_id, $taxonomy->name );
if ( $children && ! is_wp_error( $children ) ) {
$terms_to_count = array_unique( array_map( 'absint', array_merge( $terms_to_count, $children ) ) );
}
}
// Generate term query.
$term_query = $query;
$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';
// Get the count.
// phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$count = $wpdb->get_var( implode( ' ', $term_query ) );
// Update the count.
update_term_meta( $term_id, 'product_count_' . $taxonomy->name, absint( $count ) );
}
delete_transient( 'wc_term_counts' );
}
/**
* Recount terms after the stock amount changes.
*
* @param int $product_id Product ID.
*/
function wc_recount_after_stock_change( $product_id ) {
if ( 'yes' !== get_option( 'woocommerce_hide_out_of_stock_items' ) ) {
return;
}
_wc_recount_terms_by_product( $product_id );
}
add_action( 'woocommerce_product_set_stock_status', 'wc_recount_after_stock_change' );
/**
* Overrides the original term count for product categories and tags with the product count.
* that takes catalog visibility into account.
*
* @param array $terms List of terms.
* @param string|array $taxonomies Single taxonomy or list of taxonomies.
* @return array
*/
function wc_change_term_counts( $terms, $taxonomies ) {
if ( is_admin() || wp_doing_ajax() ) {
return $terms;
}
/**
* Filter which product taxonomies should have their term counts overridden to take catalog visibility into account.
*
* @since 2.1.0
*
* @param array $valid_taxonomies List of taxonomy slugs.
*/
$valid_taxonomies = apply_filters( 'woocommerce_change_term_counts', array( 'product_cat', 'product_tag' ) );
$current_taxonomies = array_intersect( (array) $taxonomies, $valid_taxonomies );
if ( empty( $current_taxonomies ) ) {
return $terms;
}
$o_term_counts = get_transient( 'wc_term_counts' );
$term_counts = false === $o_term_counts ? array() : $o_term_counts;
foreach ( $terms as &$term ) {
if ( $term instanceof WP_Term && in_array( $term->taxonomy, $current_taxonomies, true ) ) {
$key = $term->term_id . '_' . $term->taxonomy;
if ( ! isset( $term_counts[ $key ] ) ) {
$count = get_term_meta( $term->term_id, 'product_count_' . $term->taxonomy, true );
$count = '' !== $count ? absint( $count ) : 0;
$term_counts[ $key ] = $count;
}
$term->count = $term_counts[ $key ];
}
}
// Update transient.
if ( $term_counts !== $o_term_counts ) {
set_transient( 'wc_term_counts', $term_counts, MONTH_IN_SECONDS );
}
return $terms;
}
add_filter( 'get_terms', 'wc_change_term_counts', 10, 2 );
/**
* Return products in a given term, and cache value.
*
* To keep in sync, product_count will be cleared on "set_object_terms".
*
* @param int $term_id Term ID.
* @param string $taxonomy Taxonomy.
* @return array
*/
function wc_get_term_product_ids( $term_id, $taxonomy ) {
$product_ids = get_term_meta( $term_id, 'product_ids', true );
if ( false === $product_ids || ! is_array( $product_ids ) ) {
$product_ids = get_objects_in_term( $term_id, $taxonomy );
update_term_meta( $term_id, 'product_ids', $product_ids );
}
return $product_ids;
}
/**
* When a post is updated and terms recounted (called by _update_post_term_count), clear the ids.
*
* @param int $object_id Object ID.
* @param array $terms An array of object terms.
* @param array $tt_ids An array of term taxonomy IDs.
* @param string $taxonomy Taxonomy slug.
* @param bool $append Whether to append new terms to the old terms.
* @param array $old_tt_ids Old array of term taxonomy IDs.
*/
function wc_clear_term_product_ids( $object_id, $terms, $tt_ids, $taxonomy, $append, $old_tt_ids ) {
foreach ( $old_tt_ids as $term_id ) {
delete_term_meta( $term_id, 'product_ids' );
}
foreach ( $tt_ids as $term_id ) {
delete_term_meta( $term_id, 'product_ids' );
}
}
add_action( 'set_object_terms', 'wc_clear_term_product_ids', 10, 6 );
/**
* Get full list of product visibility term ids.
*
* @since 3.0.0
* @return int[]
*/
function wc_get_product_visibility_term_ids() {
if ( ! taxonomy_exists( 'product_visibility' ) ) {
wc_doing_it_wrong( __FUNCTION__, 'wc_get_product_visibility_term_ids should not be called before taxonomies are registered (woocommerce_after_register_post_type action).', '3.1' );
return array();
}
static $term_ids = array();
// The static variable doesn't work well with unit tests.
if ( count( $term_ids ) > 0 && ! class_exists( 'WC_Unit_Tests_Bootstrap' ) ) {
return $term_ids;
}
$term_ids = array_map(
'absint',
wp_parse_args(
wp_list_pluck(
get_terms(
array(
'taxonomy' => 'product_visibility',
'hide_empty' => false,
)
),
'term_taxonomy_id',
'name'
),
array(
'exclude-from-catalog' => 0,
'exclude-from-search' => 0,
'featured' => 0,
'outofstock' => 0,
'rated-1' => 0,
'rated-2' => 0,
'rated-3' => 0,
'rated-4' => 0,
'rated-5' => 0,
)
)
);
return $term_ids;
}
/**
* Recounts all terms for product categories and product tags.
*
* @since 5.2
*
* @param bool $include_callback True to update the standard term counts in addition to the product-specific counts,
* which will cause a lot more queries to run.
*
* @return void
*/
function wc_recount_all_terms( bool $include_callback = true ) {
$product_cats = get_terms(
array(
'taxonomy' => 'product_cat',
'hide_empty' => false,
'fields' => 'id=>parent',
)
);
_wc_term_recount( $product_cats, get_taxonomy( 'product_cat' ), $include_callback, false );
$product_tags = get_terms(
array(
'taxonomy' => 'product_tag',
'hide_empty' => false,
'fields' => 'id=>parent',
)
);
_wc_term_recount( $product_tags, get_taxonomy( 'product_tag' ), $include_callback, false );
}
/**
* Recounts terms by product.
*
* @since 5.2
* @param int $product_id The ID of the product.
* @return void
*/
function _wc_recount_terms_by_product( $product_id = '' ) {
if ( empty( $product_id ) ) {
return;
}
$product_terms = get_the_terms( $product_id, 'product_cat' );
if ( $product_terms ) {
$product_cats = array();
foreach ( $product_terms as $term ) {
$product_cats[ $term->term_id ] = $term->parent;
}
_wc_term_recount( $product_cats, get_taxonomy( 'product_cat' ), false, false );
}
$product_terms = get_the_terms( $product_id, 'product_tag' );
if ( $product_terms ) {
$product_tags = array();
foreach ( $product_terms as $term ) {
$product_tags[ $term->term_id ] = $term->parent;
}
_wc_term_recount( $product_tags, get_taxonomy( 'product_tag' ), false, false );
}
}