I have this weird situation when I deleted two child taxonomies but they still seem to be assigned to their parent.
I need a overview site for all our products with the taxonomy title ahead. If there is a child taxonomy the products should display under the child, and not under the parent taxonomy.
the code
$args = array(
'type' => 'anco_produkt',
'orderby' => 'menu_order',
'order' => 'ASC',
'taxonomy' => 'produkt_category'
);
$taxonomies = get_categories($args);
foreach ($taxonomies as $taxonomy) {
if($taxonomy->parent == 0) {
echo '<h3 class="alignnone">'.$taxonomy->name.'</h3>';
} else {
echo '<h4>'.$taxonomy->name.'</h4>';
}
// Get the children of current taxonomy
$tax_children = get_term_children($taxonomy->term_id , 'produkt_category');
if(count($tax_children) == 0) { // Are there children?
// Do something with the posts
}
It works great!
But I have a taxonomy with two former child-taxonomies. I deleted them and assigned all the affected posts to the parent category, but the posts don’t show up under the parent category.
I tested if I get any children with:
print_r($tax_children);
echo count($tax_children);
And voilà , I get their IDs (19 and 22) and a count of 2 children for this “childrenless” parent. In the DB are no taxonomies/categories with these IDs. I tried wp_delete_term()
and wp_delete_category()
, this parent just can’t let go of it’s children :((
the problem in action:
http://ch-de.ancotech.com/produkte/
the taxonomies
There should be 15 products under ‘Spezialbewehrungen’…
Has someone a idea?
Ps. Should I send the parent to therapy to accept the loss of its children?
The problem
There’s on (imho) serious issue with WordPress and Taxonomies and their term hierarchy (and children): They aren’t really fetched from the actual state, but someone who thought (s)he might be really “smart” stuffed that into the
*_options
table.Just take a look at the source of
get_term_children()
: It makes a call to_get_term_hierarchy()
. And in there the following is used to retrieve the children of a taxonomy:Now it might sound smart at first to leverage a static array instead of a real count, but at the long term it’s a maintenance nightmare. WordPress has to keep that value updated and in case someone forgets to trigger that update programmatically, it gets out of sync and fails.
Real world example
To get a proof for that, you can do a plain DB call in (for e.g.) phpMyAdmin (or whatever you use): Just replace
{$wpdb->prefix}
with your DB table prefix.The result will be something like the following – a serialized array:
Or when deserialized:
So the keys are the parent terms, while the sub arrays are the associated child terms.
What’s wrong?
First off: I’m not completely sure about this, as WP uses plenty of plain functions and DB calls and tracing all routes is quite tough – maybe I got lost in some rabbit hole.
When I look at
wp_delete_term()
it seems that there’s nowhere an update of the actual option happening. Andwp_delete_category()
is just a wrapper for that. So in order to keep your children in sync, you’ll have to manually update that option.I’ve written an importer once, where I stumbled upon the same problem and had to update the option manually as well. So unless no one smarter than me enters the stage, I think this is the only way to go:
update_option()
:EDIT (1)
One more thing for everyone who wants to reproduce that or get some proof: There’s no way to actually see that the children are out of synch, unless you take a look at the taxonomy list table screen in admin or comparing the option return value manually. Everything else will just work as expected and look ok.
EDIT (2)
As @MannyFleurmond just commented, there might be another option: Deleting the option and let WP regenerate it for you. I searched core and looked into
wp_delete_term()
in~/wp-includes/taxonomy.php
… and found something:clean_term_cache
Source really has a part that does this:Point still is, that it doesn’t trigger. The reason is simple: The 3rd argument is
true
per default. And therefore – see source – it won’t trigger an update.A possible solution might be the following: Use a hook and clean it manually to trigger an update:
So we could use that:
Keep in mind that above plugin isn’t tested. But if it works, please leave a comment so people can simply use this as mu-plugin to automatically fix this crap 🙂