PHP
add_filter('posts_clauses', 'sort_out_of_stock_products_last', 9999);
function sort_out_of_stock_products_last($clauses) {
global $wpdb;
if (is_shop() || is_product_category() || is_product_tag()) {
$out_of_stock_products = $wpdb->get_col("SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_stock_status' AND meta_value = 'outofstock'");
$out_of_stock_products_clause = implode(',', $out_of_stock_products);
$clauses['orderby'] = "FIELD($wpdb->posts.ID, $out_of_stock_products_clause) ASC, $wpdb->posts.menu_order ASC";
$clauses['join'] .= " LEFT JOIN $wpdb->postmeta istockstatus ON ($wpdb->posts.ID = istockstatus.post_id) ";
$clauses['where'] .= " AND istockstatus.meta_key = '_stock_status' AND istockstatus.meta_value <> '' ";
}
return $clauses;
}