Assuming that the product category term name "sold out" exists, try the following:
add_action( 'woocommerce_before_shop_loop_item_title', 'mytheme_display_sold_out_loop_woocommerce' );
function mytheme_display_sold_out_loop_woocommerce() {
global $product;
$term_name = 'Sold out';
// Get the product category term Id for "Sold out" term name
$term_id = get_term_by( 'name', $term_name, 'product_cat' )->term_id;
// 1. Add product category "Sold out"
if ( ! $product->is_in_stock() && ! has_term( $term_id, 'product_cat', $product->get_id() ) ) {
// Get product categories (if there is any)
$term_ids = (array) $product->get_category_ids();
// Add the product category term Id for "Sold out" term name to $term_ids array
$term_ids[] = $term_id;
$product->set_category_ids( $term_ids ); // Update product categories
$product->save(); // Save to database
}
// 2. Remove product category "Sold out"
elseif ( $product->is_in_stock() && has_term( $term_id, 'product_cat', $product->get_id() ) ) {
// Get product categories (if there is any)
$term_ids = (array) $product->get_category_ids();
// Remove the product category term Id for "Sold out" term name in $term_ids array
if ( ( $key = array_search( $term_id, $term_ids ) ) !== false ) {
unset($term_ids[$key]);
}
$product->set_category_ids( $term_ids ); // Update product categories
$product->save(); // Save to database
}
}
Code goes in functions.php file of your active child theme (or active theme). It should work.
Now it should be lighter to use the term Id for "sold out" product category, so this way you could replace (in the code):
$term_name = 'Sold out';
// Get the product category term Id for "Sold out" term name
$term_id = get_term_by( 'name', $term_name, 'product_cat' )->term_id;
by only the term id for 'Sold out' product category term name like (replace 19
by the real term Id):
$term_id = 19;