2

Simply speaking i want to do something like this.

http://example.com/parent-category/child-category/post-name

change into

http://example.com/child-category/post-name

I found some solutions, but they are not perfect. For instance i found this code which do the thing almost right.

add_filter( 'post_link', 'remove_parent_category', 10, 3 );
function remove_parent_category( $permalink, $post, $leavename )
{
$cats = get_the_category( $post->ID );
if ( $cats ) {
    usort( $cats, '_usort_terms_by_ID' );
    $category = $cats[0]->slug;
    if ( $parent = $cats[0]->parent ) {
        // Find parent categories and replace them in the link
        $parentcats = get_category_parents( $parent, false, '/', true );
        $permalink = str_replace( $parentcats, '', $permalink );
    }
}
return $permalink;
}

Because of str_replace it breaks links if categories has similar names. Like

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

It will delete house from both categories. Can someone fix this code to work with any names?

1

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( '/', $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[] = '/'. $url_path[ $child_cat_slot ];
                 $new_url_path[] = '/'. $url_path[ $name_slot ] .'/';

                 $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.

5
  • I also found wordpress plugin "No category parents" that works as intended. But from the first look it does not seem as efficient as yours. – Recofa May 28 '17 at 10:15
  • Ok. It does not work as well as i thought. There is a problem with permalinks of every category post that is not subcategory. They all lost their permalinks. – Recofa May 29 '17 at 21:56
  • As I stated, "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." "Permalinks" have not been "lost." Other than un-accepting the answer, what have you tried in terms of improving upon the code? – hwl May 29 '17 at 22:52
  • Eh. After testing i simply stated what problem there is with the code you written. And you are actually blaming me for stating the fact? I don't see a point of that. Yes they are lost. If permalinks are not generated it can be also said that they are lost. I fixed it by adding return $permalink;. If there are other problems i will check later when i have time and see for myself. – Recofa May 30 '17 at 0:00
  • Thanks for @hwl's answer and code, the code works great, but it doesn't work with the AMP url, can you fix it with AMP? Look forward to your return. – kenlly Jan 16 '20 at 5:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.