How to Remove Parents Category Permalink from Posts

With either str_replace or preg_replace, without either some naming conventions on the user end or some strong work in the method’s directives (linked, you can read about patterns here, ), you’re going to run into issues with the

House | My House | New House scenarios; especially once you’re dealing with slugs, and if those slugs are things like myhouse and newhouse.

Then the questions become “what of grandchildren?”, and “what if the post is in multiple child categories with different parents and the wrong parent is in the first position of that $cats array?”


Possible Solution: filter the permalink to remove parent categories

The below seemed to work on my end for the scenarios I mentioned above, and it avoids str_– and preg_ -replace altogether.

I assume by your link example:

http://example.com/myhouse/house/post-title

that your permalinks structure is /%category%/%postname%/ And that the goal here is to limit the permalink to the youngest member of the category family and post title. So, example.com/child-cat/post-title/ or example.com/grandchild-cat/post-title

Since we know we only want those two last values to remain, and we have the permalink, let’s parse it, explode the path portion into an array, count the array entries, keep the last and second-to-last entries, and reform the permalink and return it.

After we make sure we’re dealing with a post that even has a child category, of course.

Something like this maybe:

    add_filter( 'post_link', 'remove_parent_category', 10, 3 );
    function remove_parent_category( $permalink, $post, $leavename ) {

         $cats = get_the_category( $post->ID );

         if ( $cats ) {

             foreach ($cats as $cat => $parent) {

               $cat_parent_values[] = $parent->parent; 
             }

             if( array_sum( $cat_parent_values ) > 0 )  {

                 $url_array      = parse_url( $permalink );
                 $url_path       = array_filter( explode( "https://wordpress.stackexchange.com/", $url_array['path'] ) );
                 $url_path      = array_values($url_path);
                 $url_path_count = count( $url_path );

                 $name_slot      = $url_path_count - 1;  
                 $child_cat_slot = $name_slot - 1;

                 $new_url_path[] = "https://wordpress.stackexchange.com/". $url_path[ $child_cat_slot ];
                 $new_url_path[] = "https://wordpress.stackexchange.com/". $url_path[ $name_slot ] ."https://wordpress.stackexchange.com/";

                 $new_permalink  = $url_array['scheme'] .'://'. $url_array['host'] . $new_url_path[0] . $new_url_path[1];

             }//end if sum > 0
         }//end if $cats

        return $new_permalink;
    }  

A preliminary test of it seems to work on my end on a fresh install, and it with no str_replace it passes the House|MyHouse test. I’m sure it can be improved upon, and you will have to really test it and make sure there aren’t any other issues/scenarios that break it. There could very well be a better approach altogether that doesn’t involve filtering the permalink, but I’m not sure of one off hand.

Explanation:

First we use get_the_category like you already had to get an array of category objects.
We verify we have some, and then we do a foreach loop of array as $key => $value, casting each value to a new array.

WordPress stores the value of parent for a category as the id, and that id is an (int). If there is no parent, it gets a 0.
So, if the sum of all values in our new array is greater than 0, we have at least one parent category in there.
And that means we have at least one path part in our permalink we want removed, so we continue.

php’s parse_url will give us an array() of 'scheme' => 'http', 'host' => 'example.com', 'path' => '/category/child-cat/postname/'

we explode() that, $url_array, at each / which gives us an array of each section. It also gives us some empty array values,
so we wrap it in array_filter and it cleans that up for us hopefully and we set it to $url_path. The we can use [array_values()][5] to clean up any missing numerical keys removed by array_filter and give us [0],[1],[2] for keys in an array of three items.

As mentioned earlier, we are assuming the name of the post is at the end, so with our filtered and re-valued array we use count() to know how many keys are in this array,
and use that to find our post name by subtracting “one” since the array starts at [0]. Then subtract one from that and we have our “youngest category”.

Then we put the permalink back together with using our $url_array and the path parts for the post name and category we want to keep, ignoring whatever else was there.

and we return it to the post_link filter.


Other possible issues I noticed with your example:

There are some other potential issues with the example you provided:
get_the_category() will return an array of category objects, but if that usort() is intended to arrange them by their ids, then if you create a category that becomes the parent to an existing category then first position of that array, $cats[0], will not necessarily be a parent category. (unless I’m misunderstanding the nature of _usort_terms_by_id.

It is also unclear what the $parent variable in if($parent = $cats[0]->parent) is intended to be set to.

Leave a Comment