Force post-type to keep only post-name in permalink
-
Hi,
My manager want that the different posts made of custom post-types on our website should be reachable by a permalink which contains only the domain name, ‘/’, followed by the post-name only (not the post-type).
Before that, the “rewrite” parameter (string on false and with_front on false) in my register_post_type function didn’t work btw.
I managed to do it by adding this part of code on my custom plugin which add my several CPT : https://wordpress.stackexchange.com/questions/203951/remove-slug-from-custom-post-type-post-urlsBut I got two issues: the posts are well displayed if we write the permalink as https://domain/post_name but they’re also still reachable if we use the post-type in the link, like https://domain/post_type/post_name.
Moreover, the different widgets keep this kind of permalink (with post_type) to call them, like in the menu that I created with the plugin “ubermenu”, or the blog posts list.
Is there a solution to avoid that behaviour and keep only the post-name in the permalink by default ?
(Know that it could create conflit, but don’t worry about that, we’re okay on it)Thank you in advance,
N.C.
-
Hi Nokaa
So everything is basically OK, your only issue is the links generated by script will contain the post type even though it’s not required. Yes?
Then the solution would depend on how the links are generated. I expect in many cases it will come from get_permalink() or one of its relatives. In such cases you could hook the “post_link” filter and strip out any post type terms. I would make an array of post type slugs, then loop through the array to do a str_replace() on the link. Don’t forget to include a slash in the search argument so you do not end up with a double slashed URL.
How you define a post type array would depend on your needs and the possibility of other types being added. In some cases simply hardcoding the list may be adequate. Or you could do a SQL query to get distinct post type records where the post type is not in a list of default types, like post, page, menu_item, etc.
I think it’s already what I did with the functions I found in the first link of my post.
But what will be fine is the possibility to apply my permalink setting (in Settings -> permalink) to the CPT too. What I need (using only %post_name%) works on default posts, but not on my CPT.
Permalink settings :
Work on default posts :
Don’t work on CPT (still keep the post type prefix) :
Is there a way to make my CPT use the permalink settings ?
Ah, I see. I think your best bet would be to use the “pre_get_posts” action. Get the post_type query var and add you CPT to the list, then set the result. The value could possibly be either an array or comma delimited list. Adding your CPT requires a different method depending on the form. I believe it’s generally a single item, so adding a comma and your CPT would be simplest. For completely correct code, you should determine the data form and add your CPT accordingly.
I don’t even know how well this will work. Do some testing before writing some great bombproof code. Unless adding the CPT is qualified by certain conditions, you may get CPT posts where it’s inappropriate, such as on the back end posts list table. That’s easy to fix with a conditional like
if ( ! is_admin())
, but other conditions may be trickier to determine. Based on what you are trying to do, the conditionalif ( is_single())
should be enough, but it’s untested.You’ll still need to modify the link output like I mentioned previously.
Thank you for your help.
Finally my manager wanted to display the category instead of the post-type in the link.
So I managed to do something like that :function na_remove_slug( $post_link, $post, $leavename ) { if ( 'formulaire' != $post->post_type || 'publish' != $post->post_status ) { return $post_link; } $category = get_the_terms( $post->ID, 'category'); if (!is_wp_error( $category)) $post_link = str_replace( '/' . $post->post_type . '/', '/' . $category[0]->slug . '/', $post_link ); else $post_link = str_replace( '/' . $post->post_type . '/', '/', $post_link ); return $post_link; } add_filter( 'post_type_link', 'na_remove_slug', 10, 3 ); function na_parse_request( $query ) { if ( ! $query->is_main_query() || 2 != count( $query->query ) || ! isset( $query->query['page'] ) ) { return; } if ( ! empty( $query->query['name'] ) ) { $query->set( 'post_type', array( 'post', 'formulaire', 'page' ) ); } } add_action( 'pre_get_posts', 'na_parse_request' );
The pemalink is well displayed in the UI :
However even if I refresh the permalink, I got an error 404 when I try to display the post.
What did I wrong ?
I think it’s resolved, finally did it by creating a kind of filter that change my permalink structure writed directly in the register function.
Here’s an example :register_post_type( 'artisan', array( 'labels' => array( 'name' => 'Artisans', 'singular_name' => 'Artisan', 'menu_name' => _x( 'Artisans', 'taxonomy general name'), ), 'description' => 'List of artisans', 'public' => true, 'menu_position' => 5, 'menu_icon' => plugins_url( 'icons/icon_artisan.svg', __FILE__ ), 'supports' => array( 'title', 'editor', 'custom-fields' ), 'taxonomies' => array( 'category' ), 'rewrite' => array( 'slug' => '/%category%', 'with_front' => false, ), )); /* Change permalink structure */ add_filter('post_type_link', 'events_permalink_structure', 10, 4); function events_permalink_structure($post_link, $post, $leavename, $sample) { if ( false !== strpos( $post_link, '%category%' ) ) { $category_term = get_the_terms( $post->ID, 'category' ); $post_link = str_replace( '%category%', array_pop( $category_term )->slug, $post_link ); } return $post_link; }
Note: Works also with custom taxonomies, by remplacing %category% and ‘category’ by the custom taxonomies slug.
I thought that modify directly the $post_link variable should bring some mess, but finally seems to be the good way.
Not resolved.
Seems to work for only one CPT.
Doesn’t know how to make the filter work for all CPT…
EDIT : And seems to break some others links like “pages” which return a 404 Error… I’m lost.- This reply was modified 7 years, 5 months ago by Nokaa.
Ummm, I’m a bit lost myself. If I understand correctly, the permastruct you are now after is roughly domain/post-slug/category and this needs to work for several post types. The problem here is how to create a rewrite rule that is applied where needed but not applied for similar appearing requests using two parameters. Ideally, some fixed base parameter is desirable to make this distinction. Or at least have all other two parameter requests include a base so the lack of a base unequivocally indicates the slug/category rewrite rule be applied. Not knowing how your site is setup, I cannot advise on how to best accomplish this.
A post type related permalink is quite different than a taxonomy related permalink. The permalink settings offers very limited options for permalinks. Anything more than what’s offered requires adding rewrite rules to tell WP how to manage such permalinks. Additional permastruct tags and/or endpoints are sometimes required as well.
When making single post requests by slug, it’s counter productive to include more parameters since the slug alone should typically be enough. However, it is now possible for two different post types to have the same slug, so providing a multiple post_type query var can possibly return several posts, all using the same slug. A filter can be used to ensure a slug is completely unique within the site when WP checks for existing slugs within only a single post type.
When adding rewrite rules, you do not need to use all parameters in the permalink. Only the parameters needed to get the desired results need to be passed on to WP. So in our slug/category permastruct, only the slug needs to be passed if all slugs are unique. The category could be used to differentiate post types using the same slug, but if the category requested should fail to correlate to the categories assigned, you will get a nothing found result, even if the slug exists.
Another complication is the posts_link filter does not apply to pages. So outputting the proper permalink will depend on on what post type is being referred to even though the link does not differentiate.
Regardless of what rewrite rule or permalink is used, if the request does not include a post type parameter, you need to adjust the post_type query var to list all possible post types so something is returned regardless of the associated post type.
In summary, there are four things to coordinate so this scheme works. Output the correct permalink depending on post type. Ensure there are no duplicate slugs among all post types. Add a rewrite rule that recognizes the request, but does not interfere with other similar unrelated requests. Adjust the post_type query var in “pre_get_posts” to account for any possible post type.
In summary, there are four things to coordinate so this scheme works. Output the correct permalink depending on post type. Ensure there are no duplicate slugs among all post types. Add a rewrite rule that recognizes the request, but does not interfere with other similar unrelated requests. Adjust the post_type query var in “pre_get_posts” to account for any possible post type.
So, you mean somethink like that ?
add_filter('post_type_link', 'change_permalink_structure', 10, 4); function change_permalink_structure($post_link, $post, $leavename, $sample) { if ( 'formulaire' == get_post_type() ) { $category_terms = get_the_terms( $post->ID, 'category' ); if(!is_wp_error($category_terms)) $term = $category_terms[0]->slug; $post_link = str_replace( '/' . $post->post_type . '/', '/' . $term . '/', $post_link ); } elseif ( 'metier-ville' == get_post_type() ) { $category_terms = get_the_terms( $post->ID, 'category' ); if(!is_wp_error($category_terms)) $term = $category_terms[0]->slug; $post_link = str_replace( '/' . $post->post_type . '/', '/' . $term . '/', $post_link ); } elseif ( 'metier-artisans' == get_post_type() ) { $category_terms = get_the_terms( $post->ID, 'category' ); if(!is_wp_error($category_terms)) $term = $category_terms[0]->slug; $post_link = str_replace( '/' . $post->post_type . '/', '/' . $term . '/', $post_link ); } return $post_link; } function na_parse_request( $query ) { if ( ! $query->is_main_query() || 2 != count( $query->query ) || ! isset( $query->query['page'] ) ) { return; } if ( ! empty( $query->query['name'] ) ) { $query->set( 'post_type', array( 'post', 'formulaire', 'metier-ville', 'metier-artisans', 'page' ) ); } } add_action( 'pre_get_posts', 'na_parse_request' );
The permalink is well set in the back-office when you edit the post, however when we try to display it, it first display the right custom permalink on the web browser (“domain/category/post_name”) and while the page is loading suddenly “poof”, it transform the category into the post_type again (“domain/post_type/post_name”). I don’t understand this behaviour. I tried to change the priority of execution but didn’t work.
Well, your code addresses 2 of the four things I mentioned. The ensuring there are no duplicate slugs thing is not urgent, nor required for this to work right now. It prevents future problems. We can delay in addressing that part for now.
There is also no rewrite rule added. I suggested it because I thought the ordering of permalink terms was required to be slug — category. By using category — slug, WP can figure out what the request is for without a rewrite rule. But then you get the behavior you’ve observed where the address changes after the page loads. This is due to WP redirecting the request to the “proper” (according to WP) permalink structure for the post that WP determined the request was for, which is the standard post_type/slug structure that occurs by default.
I’m not entirely sure how to deal with this redirect to the permastruct behavior. The obvious thing to do is to change the permalink structure in settings to be a custom structure domain
/%category%/%postname%/
. This will resolve the behavior for the post post_type, but not pages and CPTs. WP expects CPT permalinks to have a base permalink element. We’ve worked around this requirement by supplying all post types as a query var, but this obviously does not change the redirect behavior.The first thing to try beyond the permalink settings is still to add a rewrite rule. I don’t know if that will resolve the redirects or not. In some cases, this redirect behavior is due to a redirect_canonical() function that is called as part of the request process. It appears to not always be the cause of redirects. Something else is at play that I’ve yet to identify. I believe redirect_canonical() determines what to do based on the rewrite rules, so adding a rewrite rule should also address what rewrite_canonical() does. It’s this something else that concerns me.
All I can suggest at this point is to go ahead and add a proper rewrite rule by using the Rewrite API. If we’re lucky, it’ll resolve the redirects. If not, it’s likely to make request parsing more stable anyway, so it’s a good thing to do no matter what. If redirects continue to happen, I’ll have to dig deeper to find out what that something else thing is. I’ve been curious about this anyway. I’ve been wanting to learn more about this, but a good reason to do so has not presented itself so it has been far down my “to do” list.
- The topic ‘Force post-type to keep only post-name in permalink’ is closed to new replies.