I have a loop to pull in custom taxonomy terms that I'd like to order by slug, but when I add in the 'orderby' command my loop is broken and returns nothing. No doubt I'm using incorrect syntax, but I can't see where for the life of me!
<?php
$loop = new WP_Query(
array(
'post_type' => 'camp',
'orderby' => 'name',
'order' => 'ASC',
'tax_query' => array(
array(
'taxonomy' => 'types',
'field' => 'slug',
'terms' => $term->slug,
),
)
)
);
if ($loop->have_posts()) :
while ($loop->have_posts()) : $loop->the_post();
$terms = get_lowest_taxonomy_terms(get_the_terms( get_the_ID(), array(
'taxonomy' => 'destinations',
'orderby' => 'slug',
'order' => 'ASC'
)) );
?>
Any help would be very much appreciated :)
Added additional function:
The get_lowest_taxonomy_terms is running as follows:
get_lowest_taxonomy_terms( $terms ) {
// if terms is not array or its empty don't proceed
if ( ! is_array( $terms ) || empty( $terms ) ) {
return false;
}
$filter = function($terms) use (&$filter) {
$return_terms = array();
$term_ids = array();
foreach ($terms as $t){
if( $t->parent == 0 ) {
$term_ids[] = $t->term_id;
}
}
foreach ( $terms as $t ) {
if( $t->parent == false || !in_array($t->parent,$term_ids) ) {
//remove this term
}
else{
$return_terms[] = $t;
}
}
if( count($return_terms) ){
return $filter($return_terms);
}
else {
return $terms;
}
};
return $filter($terms);
}
Desired Output
The loop should query the taxonomy type 'destination', where that destination has the additional taxonomy of 'type'.
Screen grab of the current output:
The results are ordered into 3 cols by CSS, but as you can see, not sorting alphabetically.
get_the_terms doesn't take an array as second parameter.
get_the_terms( int|object $post, string $taxonomy )
Parameters:
$post (int|object) (Required) Post ID or object.
$taxonomy (string) (Required) Taxonomy name.
More details of get_the_terms
By default, get_the_terms doesn't have any sorting option. But, you can use usort to sort the returned array.
So, your last part of the code should be:
$terms = get_lowest_taxonomy_terms(get_the_terms( get_the_ID(), 'destinations' ));
This is a quick example of how you can sort it using usort:
$terms = get_the_terms( get_the_ID(), 'destinations' );
usort($terms,"so980_sort_terms_alphabetically");
function so980_sort_terms_alphabetically($a,$b) {
return $a['slug'] > $b['slug'];//using 'slug' as sorting order
}
so980_sort_terms_alphabetically function should be in your theme's functions.php file.
So finally, your last part will become:
$terms = get_the_terms( get_the_ID(), 'destinations' );
usort($terms,"so980_sort_terms_alphabetically");
$terms = get_lowest_taxonomy_terms($terms);
Update
I have just tested it, and it is returning a fatal error, as I have made a mistake. Error message: Fatal error: Cannot use object of type WP_Term as array.
get_the_terms returns an object. So, using $a['slug'] was causing the error.
Here, so980_sort_terms_alphabetically should be implemented in following way:
function so980_sort_terms_alphabetically($a,$b) {
return $a->slug > $b->slug;//using 'slug' as sorting order
}
**This is tested, and working.
Update 2
Since your output codes are not available in your question, I will assume that you are printing inside the loop. Instead of doing that, we will save returned terms in another array, and sort it outside the loop, so that we can get all of the terms at once.
Here is a quick example of how you can do it:
if ($loop->have_posts()) :
while ($loop->have_posts()) : $loop->the_post();
$pre_terms = get_lowest_taxonomy_terms(get_the_terms(get_the_ID(), 'product_tag'));
if ( $pre_terms ) {
foreach ( $pre_terms as $pre_term ) {
$terms[] = $pre_term;
}
}
endwhile;
endif;
usort($terms,"so980_sort_terms_alphabetically");
//a quick test to see if it's working or not
foreach ( $terms as $term ) {
echo $term->slug;
echo '<br/>';
}
Related
I am trying to find ways to sort my loop from a numeric value(distance) that i can only get via a shortcode(by calculating custom field address). shortcode works, i successfully got the distance value but now i want to sort my data from closest distance to farthest.
i was trying to use usort, but i don't know how to execute it properly.
$loop = new WP_Query( $args );
function customCompare($Aint, $Bint)
{
$Aint = $distance;
$Bint = $distance;
return ($Aint < $Bint);
}
usort($loop->posts, 'customCompare');
while ( $loop->have_posts() ) : $loop->the_post();
$address = get_field('acf_address');
$distance = do_shortcode("[distance address='".$address."']");
im expecting to display my data from lowest distance value to highest but right now it doesn't do anything to my loop, just displays the default order. which means my code doesn't work. I would appreciate any help/suggestion
i updated my code, saved the data i needed in array and used array_multisort
$merchantPost = get_posts( $args );
foreach ( $merchantPost as $post ) :
setup_postdata( $post );
$merchant_list[] = array(
'acf_address' => get_post_meta( $post->ID, 'acf_address', true ),
'distance' => do_shortcode("[distance address_to='".get_field('acf_address')."']"),
'post_title' => get_the_title(),
'permalink' => get_the_permalink(),
'gallery' => get_field('gallery'),
'image' => wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), 'single-post-thumbnail' ),
'terms' => get_the_terms( $post->ID, 'merchant_categories' ),
);
endforeach;
wp_reset_postdata();
$all_distance = array();
foreach ( $merchant_list as $mlist ) {
$all_distance[] = $mlist['distance'];
}
array_multisort($all_distance, SORT_ASC, $merchant_list, SORT_NUMERIC);
now my problem is pagination, since im not using wordpress default loop., but thats for another topic.
I'm setting a variable in my site this way:
$args = array('parent' => 2818,'hide_empty' => false);
$best_of_cat_child_terms = get_terms( $args ); -> (functions.php:26)
$best_of_cat = $best_of_cat_child_terms;
The problem is that I'm also getting this php error:
Warning:
Invalid argument supplied for foreach()
Location:
wp-includes/class-wp-term-query.php:373
Call Stack:
WP_Term_Query->get_terms()
wp-includes/class-wp-term-query.php:287
WP_Term_Query->query()
wp-includes/taxonomy.php:1217
get_terms()
wp-content/themes/theme-child/functions.php:26 (-> functions.php line 26 marked above)
Am I setting this the right way?
You can check for the error with is_wp_error(),
$terms = get_terms( array(
'taxonomy'=> 'category',
'parent' => 2818,
'hide_empty' => 0)
);
if ( ! empty( $terms ) && ! is_wp_error( $terms ) ){
echo '<ul>';
foreach ( $terms as $term ) {
echo '<li>' . $term->name . '</li>';
}
echo '</ul>';
}
else{
$error_string = $terms->get_error_message();
echo '<div id="message" class="error"><p>' . $error_string .'</p></div>';
}
About the error you told in comment, it's look like you don't run with a WordPress version under 4.5 ?
Prior to 4.5.0, the first parameter of get_terms() was a taxonomy or list of taxonomies:
$terms = get_terms( 'post_tag', array(
'hide_empty' => false,
) );
Since 4.5.0, taxonomies should be passed via the ‘taxonomy’ argument in the $args array:
$terms = get_terms( array(
'taxonomy' => 'post_tag',
'hide_empty' => false,
) );
About get_terms()
In your case, remove taxonomy from $args and
$terms = get_terms('category', $args);
Provide the taxonomy arg:
$args = array(
'taxonomy' => 'your_taxonomy',
'parent' => 2818,
'hide_empty' => false
);
I'm creating related posts by taxonomy.
I have code:
(in functions.php)
// Create a query for the custom taxonomy
function related_posts_by_taxonomy( $post_id, $taxonomy, $args=array() ) {
$terms = wp_get_object_terms( $post_id, $taxonomy );
// Make sure we have terms from the current post
if ( count( $terms ) ) {
$post_ids = get_objects_in_term( $terms[0]->term_id, $taxonomy );
$post = get_post( $post_id );
$post_type = get_post_type( $post );
// Only search for the custom taxonomy on whichever post_type
// we AREN'T currently on
// This refers to the custom post_types you created so
// make sure they are spelled/capitalized correctly
if ( strcasecmp($post_type, 'colors') == 0 ) {
$type = 'colors';
} else {
$type = 'colors';
}
$args = wp_parse_args( $args, array(
'post_type' => $type,
'post__not_in' => array($post_id),
'post__in' => $post_ids,
'taxonomy' => $taxonomy,
'term' => $terms[1]->slug,
'posts_per_page' => 5,
'orderby' => 'rand'
) );
$query = new WP_Query( $args );
}
print_r($post_id);
// Return our results in query form
return $query;
}
in my template single-colors.php
<?php $related = related_posts_by_taxonomy( $post->ID, 'colors_tax' );
while ( $related->have_posts() ): $related->the_post(); ?>
<strong class="center block fz">
<?php echo $ncolors; ?>
</strong>
<h1 class="no-m"><?php the_title();?></h1>
<?php endwhile; ?>
The code works. But this code shows also duplicate for my post (in related posts by taxonomy). How to eliminate it? 'post__not_in' => array($post_id) doesn't work.
As I already stated, your code is completely wrong and will not work. I'm not going to go into details about your code as I would just scrap your code and start from the beginning
Here is a copy of a post I have recently done on WPSE. This is basically a copy and paste solution, the only thing here being that posts aren't radomly ordered, so you need to add random ordering into the query arguments.
Here is the post:
I would just just scrap the function above as there are several bugs in the code and is also not quite effecient. I am actually surpriced that it really works for you.
Your best solution here would be to write a complete new function. Here is want we want to do and how we are going to accomplish this
Get the current post object on the single post page. $post is unreliable, so we are going to use the main query object here which we will return with get_queried_object(). From here we can use the post ID and post type to get other related info
Get the terms of the current post being viewed with wp_get_post_term(). We will set the fields paremeter to only get the term ids. This returnes array of term ids can then be used in our tax_query
Add in some validation to validate the user input and also sanitize the input
Lets put that all in code (CAVEAT: This is all untested and requires at least PHP 5.4+)
function get_related_posts( $taxonomy = '', $args = [] )
{
/*
* Before we do anything and waste unnecessary time and resources, first check if we are on a single post page
* If not, bail early and return false
*/
if ( !is_single() )
return false;
/*
* Check if we have a valid taxonomy and also if the taxonomy exists to avoid bugs further down.
* Return false if taxonomy is invalid or does not exist
*/
if ( !$taxonomy )
return false;
$taxonomy = filter_var( $taxonomy, FILTER_SANITIZE_STRING );
if ( !taxonomy_exists( $taxonomy ) )
return false;
/*
* We have made it to here, so we should start getting our stuff togther.
* Get the current post object to start of
*/
$current_post = get_queried_object();
/*
* Get the post terms, just the ids
*/
$terms = wp_get_post_terms( $current_post->ID, $taxonomy, ['fields' => 'ids'] );
/*
* Lets only continue if we actually have post terms and if we don't have an WP_Error object. If not, return false
*/
if ( !$terms || is_wp_error( $terms ) )
return false;
/*
* Set the default query arguments
*/
$defaults = [
'post_type' => $current_post->post_type,
'post__not_in' => [$current_post->ID],
'tax_query' => [
[
'taxonomy' => $taxonomy,
'terms' => $terms,
'include_children' => false
],
],
];
/*
* Validate and merge the defaults with the user passed arguments
*/
if ( is_array( $args ) ) {
$args = wp_parse_args( $args, $defaults );
} else {
$args = $defaults;
}
/*
* Now we can query our related posts and return them
*/
$q = get_posts( $args );
return $q;
}
Now that we have a better function in place, we can use it in our single post page or content template parts depending on your exact use case. As you might have noticed, our new function get_related_posts() has two parameters, the first one which accepts a single taxonomy value, and the second an array of arguments. This arguments will the arguments passed to our query, so you can pass any valid array of arguments that is acceptible to WP_Query and get_posts here.
Example:
You need one post to be returned, so you can try the following: (Please note, do not use the post type parameter or any of the taxonomy type parameters here, you might get unexpected output)
if ( function_exists( 'get_related_posts' ) ) {
$related_posts = get_related_posts( 'my_taxonomy_name', ['posts_per_page' => 1] );
if ( $related_posts ) {
foreach ( $related_posts as $post ) {
setup_postdata( $post );
// Use your template tags and html mark up as normal like
the_title();
the_content();
// etc etc
}
wp_reset_postdata();
}
}
EDIT
From comments it seems that your PHP version is older than 5.4 which does not support the new short array syntax ([]), and therefor you get the dreaded WSOD. For this to work, you need change the new array syntax to the old syntax (array()).
You can try the following:
function get_related_posts( $taxonomy = '', $args = array() )
{
/*
* Before we do anything and waste unnecessary time and resources, first check if we are on a single post page
* If not, bail early and return false
*/
if ( !is_single() )
return false;
/*
* Check if we have a valid taxonomy and also if the taxonomy exists to avoid bugs further down.
* Return false if taxonomy is invalid or does not exist
*/
if ( !$taxonomy )
return false;
$taxonomy = filter_var( $taxonomy, FILTER_SANITIZE_STRING );
if ( !taxonomy_exists( $taxonomy ) )
return false;
/*
* We have made it to here, so we should start getting our stuff togther.
* Get the current post object to start of
*/
$current_post = get_queried_object();
/*
* Get the post terms, just the ids
*/
$terms = wp_get_post_terms( $current_post->ID, $taxonomy, array( 'fields' => 'ids') );
/*
* Lets only continue if we actually have post terms and if we don't have an WP_Error object. If not, return false
*/
if ( !$terms || is_wp_error( $terms ) )
return false;
/*
* Set the default query arguments
*/
$defaults = array(
'post_type' => $current_post->post_type,
'post__not_in' => array( $current_post->ID),
'tax_query' => array(
array(
'taxonomy' => $taxonomy,
'terms' => $terms,
'include_children' => false
),
),
);
/*
* Validate and merge the defaults with the user passed arguments
*/
if ( is_array( $args ) ) {
$args = wp_parse_args( $args, $defaults );
} else {
$args = $defaults;
}
/*
* Now we can query our related posts and return them
*/
$q = get_posts( $args );
return $q;
}
And then to use the code in templates, change to
if ( function_exists( 'get_related_posts' ) ) {
$related_posts = get_related_posts( 'my_taxonomy_name', array( 'posts_per_page' => 1) );
if ( $related_posts ) {
foreach ( $related_posts as $post ) {
setup_postdata( $post );
// Use your template tags and html mark up as normal like
the_title();
the_content();
// etc etc
}
wp_reset_postdata();
}
}
I have written some code that uses the Wordpress get_terms function to display a list of sub-categories for a user-defined category and also shows the relevant thumbnail for each sub-category. As far as I'm aware, there isn't a WP function to display all child categories across multiple parents, so I was wondering if it's possible to merge the results of two or more get_terms results?
The code I have written so far is working fine to get_terms from just one parent category but I'm not sure where to go from here…
function get_wc_child_cat_thumbs($catParent, $listClassName) {
// Our wordpress get_terms arguments
$args = array(
'number' => $number,
'orderby' => $orderby,
'order' => $order,
'hide_empty' => $hide_empty,
'include' => $ids,
'parent' => $catParent,
);
// Get the terms
$product_categories = get_terms( 'product_cat', $args );
// See if any results are returned
$count = count($product_categories);
// If there are, populate a list with the subcategories details
if ( $count > 0 ){
echo '<ul class="'.$listClassName.'">';
foreach ( $product_categories as $product_category ) {
// Get the thumbnail id for the subcategory
$thumbnail_id = get_woocommerce_term_meta( $product_category->term_id, 'thumbnail_id', true );
// Show the results…
echo '<li>'.wp_get_attachment_image( $thumbnail_id ).'<br />'.$product_category->term_id.' - ' . $product_category->name . '</li>';
//echo $image = wp_get_attachment_image( $thumbnail_id );
}
echo '</ul>';
} // END if ( $count > 0 ){
} // END function get_wc_child_cat_thumbs`
Is it possible to change the $catParent argument so that it accepts either single or array values and add in an array_merge somewhere?
Would this work?
$product_categories = array();
foreach ($catParent as $parent) {
$args = array(
'number' => $number,
'orderby' => $orderby,
'order' => $order,
'hide_empty' => $hide_empty,
'include' => $ids,
'parent' => $parent,
);
// Get the terms
$categories = get_terms('product_cat', $args );
foreach ($categories as $category) {
$product_categories[] = $category;
}
}
assuming $catParent is an array
edit second foreach
foreach ( $product_categories as $product_category ) {
// Get the thumbnail id for the subcategory
$thumbnail_id = get_woocommerce_term_meta( $product_category->term_id, 'thumbnail_id', true );
// Show the results…
echo '<li>'.wp_get_attachment_image( $thumbnail_id ).'<br />'.$product_category->term_id.' - ' . $product_category->name . '</li>';
//echo $image = wp_get_attachment_image( $thumbnail_id );
}
I managed to sort the problem (literally) with great help from #RST. In the end I merged the multiple arrays and sorted the results alphabetically using usort and the name value from the array as the comparison. Here's the finished code…
// Used instead of brands plugin - was duplicating content
function get_multiple_woocommerce_child_cats($catParent, $listClassName) {
$product_categories = array();
foreach ($catParent as $parent) {
$args = array(
'number' => $number,
'orderby' => $orderby,
'order' => $order,
'hide_empty' => $hide_empty,
'include' => $ids,
'parent' => $parent,
);
// Get the terms
$product_categories[] = get_terms( 'product_cat', $args );
}
// See if any results were returned
$count = count($product_categories);
// If there are, populate a list with the subcategories details
if ( $count > 0 ){
// Merge the separate product category arrays into one big array
$mergedcats = call_user_func_array('array_merge', $product_categories);
// Function to sort the array by name value. Need to use the class operator in Wordpress $a->name instead of $a['name'] otherwise we get an error
function sorteed($a, $b) {
return strcmp($a->name, $b->name);
}
// Sort the big array by the category name value
usort($mergedcats, sorteed);
// For testing…
//echo "<pre>";
//print_r($result);
//echo"</pre>";
// Print out the results in the desired format
echo '<ul class="'.$listClassName.'">';
foreach ( $mergedcats as $product_category ) {
// Get the thumbnail id for the subcategory
$thumbnail_id = get_woocommerce_term_meta( $product_category->term_id, 'thumbnail_id', true );
// Show the results…
echo ' <li>
<div>
'.wp_get_attachment_image( $thumbnail_id ).'
</div>
<p>Cat id is - '.$product_category->term_id.'</p><p> Cat name is - ' . $product_category->name . '</p>
</li>';
} // END foreach
echo '</ul>';
} else {}
} // END function get_wc_child_cat_thumbs
The title should give you a pretty good idea about my misadventures. I am working on a project that's made in wordpress and uses WooCommerce, and after a lot of brainstorming and thought about possible compromise, i have reached the point where i am pretty much certain i have to get into the php code to solve the problem conveniently.
The problem is that i have the following website:
As you may have noticed, there is a mash of all the product categories, and what i need to do is split them into 2 main categories: food and drink. I turned what woocommerce can do by its built in functions and i just can't get it to work so i figured i'd have to write my own function. Now if any of you knows that i can actually do it with what i have i'd be happy if somebody told me. If not what i need is to create a function which can actually select all the categories belonging to a parent category or something the likes.
public function product_categories( $atts ) {
global $woocommerce_loop;
extract( shortcode_atts( array (
'number' => null,
'orderby' => 'name',
'order' => 'ASC',
'columns' => '4',
'hide_empty' => 1,
'parent' => ''
), $atts ) );
if ( isset( $atts[ 'ids' ] ) ) {
$ids = explode( ',', $atts[ 'ids' ] );
$ids = array_map( 'trim', $ids );
} else {
$ids = array();
}
$hide_empty = ( $hide_empty == true || $hide_empty == 1 ) ? 1 : 0;
// get terms and workaround WP bug with parents/pad counts
$args = array(
'orderby' => $orderby,
'order' => $order,
'hide_empty' => $hide_empty,
'include' => $ids,
'pad_counts' => true,
'child_of' => $parent
);
$product_categories = get_terms( 'product_cat', $args );
if ( $parent !== "" )
$product_categories = wp_list_filter( $product_categories, array( 'parent' => $parent ) );
if ( $number )
$product_categories = array_slice( $product_categories, 0, $number );
$woocommerce_loop['columns'] = $columns;
ob_start();
// Reset loop/columns globals when starting a new loop
$woocommerce_loop['loop'] = $woocommerce_loop['column'] = '';
if ( $product_categories ) {
woocommerce_product_loop_start();
foreach ( $product_categories as $category ) {
woocommerce_get_template( 'content-product_cat.php', array(
'category' => $category
) );
}
woocommerce_product_loop_end();
}
woocommerce_reset_loop();
return '<div class="woocommerce">' . ob_get_clean() . '</div>';
}
This i identified as the menacing WooCommerce function that does not behave. Help. Please help me :(
What about using WooCommerce's built-in [product_categories] shortcode? You could pass categories you want through the id="" attribute.