I want to change the default post permalink structure from (example.com/post-name) to (example.com/author_firstname-author_lastname/post-name).
I tried to add a new rewrite tag which I could use in the custom permalink structure to use the author first name and last name in the URL but it didn't work (404 error page).
add_filter('post_link', 'pauthor_permalink', 10, 3);
add_filter('post_type_link', 'pauthor_permalink', 10, 3);
function pauthor_permalink($permalink, $post_id, $leavename) {
add_rewrite_tag( '%pauthor%', '(.+)', 'pauthor=' );
if (strpos($permalink, '%pauthor%') === FALSE) return $permalink;
// Get post
$post = get_post($post_id);
if (!$post) return $permalink;
// Get Author Data
$post_author = get_post_field( 'post_author', $post->ID );
$user_info = get_userdata($post_author);
$first_name = $user_info->first_name;
$last_name = $user_info->last_name;
$pauthor = $first_name.'-'.$last_name;
return str_replace('%pauthor%', $pauthor, $permalink);
}
any help!?
Changing the Permalink structure in wordpress can be done without any sort of php.
See Settings > Permalinks
https://codex.wordpress.org/Settings_Permalinks_Screen
The structure you are looking for appears to be /%author%/%postname%/
More tags can be found here:
https://codex.wordpress.org/Using_Permalinks#Structure_Tags
Creating Custom Tags
You can create custom tags by creating a new taxonomy with 'rewrite' => true, for example:
add_action( 'init', 'my_rating_init' );
function my_rating_init() {
if ( ! is_taxonomy( 'rating' ) ) {
register_taxonomy(
'rating',
'post',
array(
'hierarchical' => FALSE,
'label' => __( 'Rating' ),
'public' => TRUE,
'show_ui' => TRUE,
'query_var' => 'rating',
'rewrite' => true
)
);
}
}
Source: https://wordpress.stackexchange.com/questions/168946/add-more-structure-tag-to-permalink
Related
In my WP v6.1, I have two custom port types: company, product and custom taxonomy country.
Desired URL structure is %country%/%company_postname% and %country%/%product_postname% respectively and below is the code for $wp_rewrite:
add_action('init', 'custom_init');
function custom_init() {
global $wp_rewrite;
$company_url = '/%country%/%company_postname%';
$product_url = '/%country%/%product_postname%';
$wp_rewrite->add_permastruct('company', $company_url, false);
$wp_rewrite->add_permastruct('product', $product_url, false);
$wp_rewrite->add_rewrite_tag("%company_postname%", '([^/]+)', "company=");
$wp_rewrite->add_rewrite_tag("%product_postname%", '([^/]+)', "product=");
}
With above code and another post_type_link filter function, I am able to generate my custom URLs. However the issue is regular post and page posts are not found returning error_404.
Regular post / page standard URL structure: www.example.com/%postname%
Have tried add_permastruct for posts & pages, but that did not worked. How do I show pages and posts while having the custom URLs for my custom posts.
Update 1
Custom posts and taxonomies were created by code.
Example of company code
function company_post_type() {
$labels = array(
'name' => _x('Company', 'Post Type General Name', 'text'),
);
$args = array(
'labels' => $labels,
'supports' => array('title', 'editor', 'custom-fields'),
'taxonomies' => array('country'),
'query_var' => true,
'rewrite' => false
);
register_post_type('company', $args);
}
add_action('init', 'company_post_type', 0);
Update 2
And my post_type_link function is:
function post_type_link_function($url, $post) {
// only if post is published
if ('' != $url && !in_array($post->post_status, array('draft', 'pending', 'auto-draft'))) {
// get country terms
$terms = wp_get_object_terms($post->ID, 'country');
// country
if (strpos($url, '%country%') !== FALSE) {
if (!is_wp_error($terms) && !empty($terms) && is_object($terms[0])) {
$country = urlencode($terms[0]->slug);
$url = str_replace('%country%', $country, $url);
}
}
// post names
$postnames = array('%company_postname%', '%product_postname%', '%postname%');
foreach ($postnames as $postname) {
$postname = $post->post_name;
$url = str_replace($postnames, $postname, $url);
}
return $url;
}
return $url;
}
Update 3
When permalinks set to Plain www.example.com/?p=123, all posts, pages and custom posts are loading fine.
Update 4
I have observed that posts and pages are not using either single.php or page.php template. It is using index.php.
Whereas, I have not attached any templates to these pages or posts.
Update 5 - resolved
It was due the 'rewrite' => array('slug' => '/', 'with_front' => FALSE) in the country custom taxonomy.
Without this rewrite now the pages and posts are fine.
You can use the post_type_link hook.
Add your custom post type and taxonomy with your custom rewrite:
function company_post_type() {
$labels = array(
'name' => _x('Company', 'Post Type General Name', 'text'),
);
$args = array(
'labels' => $labels,
'show_ui' => true,
'public' => true,
'publicly_queryable' => true,
'supports' => array('title', 'editor', 'custom-fields'),
'taxonomies' => array('country'),
'query_var' => true,
'rewrite' => array( 'slug' => '%country%', 'with_front' => false ),
);
register_post_type('company', $args);
$tax_args = array(
'hierarchical' => true,
'public' => true,
'show_ui' => true,
'show_admin_column' => true,
'query_var' => true,
'show_in_rest' => true,
);
register_taxonomy( 'country', 'company', $tax_args );
}
add_action('init', 'company_post_type', 0);
then we run a replace on the permalink via the hook:
function replace_post_link( $post_link, $id = 0 ){
$post = get_post($id);
$post_type = get_post_type( $id );
if ( is_object( $post ) && $post_type == 'company'){
$cat = 'country';
$terms = wp_get_object_terms( $post->ID, $cat );
if( $terms && !is_wp_error($terms) ){
return str_replace( '%country%' , $terms[0]->slug , $post_link );
}
}
return $post_link;
}
add_filter( 'post_type_link', 'replace_post_link', 1, 3 );
UPDATE:
There seems to be a quirk with wordpress when using this syntax for your rewrite so you will need to preface the term with a front.
'rewrite' => array( 'slug' => 'country/%country%', 'with_front' => false )
and in the replace post link:
return str_replace( 'country/%country%', $terms[0]->slug , $post_link );
Then your URL will be:
country/%country%/%company_postname%
make sure to flush your rewrite rules when you are done by going to: Settings > Permalinks and clicking 'save changes'
In my WordPress v6.0.2, I have a custom taxonomy country and a custom post_type interest.
With post_type_link filter (another working function) and with the below function, I am rewriting custom post_type URLs as https://www.example.com/country_name/interest_term:
add_filter('generate_rewrite_rules', 'country_cpt_generating_rule');
function country_cpt_generating_rule($wp_rewrite) {
$rules = array();
$terms = get_terms(array(
'taxonomy' => 'country',
'hide_empty' => false,
));
$post_type = 'interest';
foreach ($terms as $term) {
$rules[$term->slug . '/interest/([^/]*)$'] = 'index.php?interest=$matches[1]&post_type=' . $post_type . 'name=$matches[1]';
}
$wp_rewrite->rules = $rules + $wp_rewrite->rules;
}
While I have single-interest.php custom post_type template to show custom content, single posts are redirecting to home page with error404 class added to the body.
A var_dump(get_queried_object()); returning null.
I have flushed permalinks and also tried checking is_singular() to template redirecting, that did not worked.
How can I have a custom page template for single-interest with above custom URL?
here is the complete solution for your requirement. Please note that you do not have to regenerate new rewrite rules to be able to work with custom post-type URLs. instead, I have used filters to modify URLs based on your need. Let me know if this solves your issue. Please do not forget to flush your permalinks. There might be some edge cases that I did not put my focus on because mainly I was focusing on achieving your requirements.
add_action('init', 'register_cpt_type');
add_filter('pre_term_link', 'change_term_links', 10, 2);
function change_term_links( $termlink, $term ) {
$post_type = isset( get_taxonomy( $term->taxonomy )->object_type[0] ) ? get_taxonomy( $term->taxonomy )->object_type[0] : '';
if( $post_type === 'interest' ) {
$termlink = explode('/', $termlink) != '' ? explode('/', $termlink): '';
unset($termlink[1]);
$termlink = implode('', $termlink);
}
return $termlink;
}
function register_cpt_type() {
$args = array(
'label' => 'Interest',
'description' => 'Add new interest from here',
'public' => true,
'public_queryable' => true,
'exclude_from_search' => false,
'show_ui' => true,
'show_in_menu' => true,
'show_in_admin_bar' => true,
'query_var' => true,
'capability_type' => 'post',
'rewrite' => array(
'slug' => '%country%',
'with_front' => false
),
'hierarchical' => false,
'show_in_rest' => true,
'with_front' => false,
'has_archive' => true,
'menu_icon' => 'dashicons-chart-pie',
'supports' => array('title', 'editor', 'thumbnail', 'excerpt', 'author', 'revisions', 'custom-fields', 'comments')
);
register_post_type('interest', $args);
$args = array(
'label' => 'Country',
'public' => true,
'show_ui' => true,
'query_var' => true,
'show_in_rest' => true,
'hierarchical' => true,
'rewrite' => array(
'slug' => '%country%',
'with_front' => false
),
);
register_taxonomy( 'country', 'interest', $args );
}
add_filter('post_type_link', 'single_cpt_postlink', 10, 2);
function single_cpt_postlink( $post_link, $post ) {
$post_type = get_post_type( $post->ID ) != '' ? get_post_type( $post->ID ) : '';
if( $post_type == 'interest' ) {
$term_slug = get_the_terms( $post->ID, 'country' ) != '' ? get_the_terms( $post->ID, 'country' )[0]->slug : '';
if( ! empty( $term_slug ) ) {
$post_link = str_replace('%country%', $term_slug, $post_link);
}
}
return $post_link;
}
It seems you are wanting to access the CPT single page. In order to do that please follow the code below
add_filter( 'single_template', 'get_interest_single_template' ), 99);
function get_interest_single_template( $single_template_path ) {
if (is_singular('interest')) {
$single_template = plugin_dir_path(__FILE__) . '/single-interest.php' ;
}
return $single_template;
}
Now inside the "single-interest.php". you can var_dump( get_queried_object() ). Which should give you the values of the WP_Post Object data. Since this template is directly coming from a plugin the custom template file should be placed inside a plugin or the custom single-interest file should be placed inside the theme file in this format "single-interest.php". Also, I would like to add the following points, since you are using a custom post type, you do not have to regenerate custom slugs. Because WordPress does regenerate the custom slugs for you. I would recommend you not use 'generate_rewrite_rules' because WordPress did create those slugs for you while creating the custom post type. What you can do is pass the slug value inside the 'parse_request' filter hook. For reference please refer to this page here
I have taken cue from this SO answer and below code worked for me:
// custom url
add_action('init', 'custom_init');
add_filter('post_type_link', 'interest_permalink', 10, 3);
function custom_init() {
global $wp_rewrite;
$interest_structure = '/%country%/%interest%';
$wp_rewrite->add_rewrite_tag("%interest%", '([^/]+)', "interest=");
$wp_rewrite->add_permastruct('interest', $interest_structure, false);
}
function interest_permalink($permalink, $post_id, $leavename) {
$post = get_post($post_id);
$rewritecode = array(
'%country%',
$leavename ? '' : '%postname%',
);
if ('' != $permalink && !in_array($post->post_status, array('draft', 'pending', 'auto-draft'))) {
if (strpos($permalink, '%country%') !== FALSE) {
$terms = wp_get_object_terms($post->ID, 'country');
if (!is_wp_error($terms) && !empty($terms) && is_object($terms[0])) {
$country = $terms[0]->slug;
} else {
$country = 'unassigned_country';
}
}
$countryreplace = array(
$country,
$post->post_name,
);
$permalink = str_replace($rewritecode, $countryreplace, $permalink);
} else {
}
return $permalink;
}
I am having the following issues with rendering custom permalinks using WordPress, the Advanced Custom Fields post object field and Timber. I have posts and a custom post type photo galleries that are related and are connected by a link setup using the post object field attached to the story post. The rendered links are being displayed like this: http://example.com/photos/%locations%/bordeaux/. The %locations% segment should be replaced by world/france in this example.
The permalinks are properly rendered when access using the post's permalink (http://example.com/photos/world/france/bordeaux), attached to a WordPress menu or navigated to using the core search functionality.
The post object has the following parameters set:
Filter by Post Type: Photo Gallery (custom post type)
Return Format: Post Object
I have included my custom post type, taxonomy, and post_type_link functions below.
Custom post type (abbreviated)
function gerryfeehan_register_photos_post_type() {
$args = [
'label' => 'Photo Galleries',
'labels' => [],
'supports' => array(),
'public' => true,
'menu_position' => 5,
'menu_icon' => 'dashicons-format-gallery',
'capability_type' => 'post',
'taxonomies' => [ 'locations', ],
'has_archive' => true,
'delete_with_user' => false,
'rewrite' => [
'slug' => 'photos/%locations%',
'with_front' => false,
],
];
register_post_type( 'photos', $args );
}
add_action( 'init', 'gerryfeehan_register_photos_post_type' );
Custom taxonomy (abbreviated)
function gerryfeehan_register_locations_taxonomy() {
$args = [
'labels' => [],
'hierarchical' => true,
'rewrite' => [
'slug' => 'locations',
'hierarchical' => true,
],
];
register_taxonomy( 'locations', [ 'photos' ], $args );
}
add_action( 'init', 'gerryfeehan_register_locations_taxonomy' );
Post type link filter function
add_filter( 'post_type_link', 'gerryfeehan_post_type_link', 10, 2 );
function gerryfeehan_post_type_link( $post_link ) {
$taxonomy = 'locations';
$terms = get_the_terms( get_the_ID(), $taxonomy );
$slug = [];
// Warning: Invalid argument supplied for foreach()
foreach ( $terms as $term ) {
if ( $term->parent == 0 ) {
array_unshift( $slug, sanitize_title_with_dashes( $term->name ) );
} else {
array_push( $slug, sanitize_title_with_dashes( $term->name ) );
}
}
if ( ! empty( $slug ) ) {
return str_replace( '%' . $taxonomy . '%' , join( '/', $slug ) , $post_link );
}
return $post_link;
}
Timber get_field function
{{ story.get_field( 'associated_photo_gallery' ).link }}
Any suggestions on why the permalinks are not rendering correctly when being used by Advanced Custom Fields and Timber.
As far as I can see, your configuration for your custom permalink structure in register_post_type() looks fine.
The reason why you get an "Invalid argument supplied for foreach()" warning is because the post_type_link filter runs for every post type. But the locations taxonomy probably isn’t registered for every post type of your site and the get_the_terms() function returns false if there are no terms.
To fix this you should add a bailout that returns early when it’s not the photos post type. For that, you can use the $post parameter, that is provided as the second argument in the filter.
add_filter( 'post_type_link', 'gerryfeehan_post_type_link', 10, 2 );
function gerryfeehan_post_type_link( $post_link, $post ) {
// Bail out if not photos post type.
if ( 'photos' !== $post->post_type ) {
return $post_link;
}
$taxonomy = 'locations';
$terms = get_the_terms( get_the_ID(), $taxonomy );
$slug = [];
foreach ( $terms as $term ) {
if ( $term->parent == 0 ) {
array_unshift( $slug, sanitize_title_with_dashes( $term->name ) );
} else {
array_push( $slug, sanitize_title_with_dashes( $term->name ) );
}
}
if ( ! empty( $slug ) ) {
$post_link = str_replace( '%' . $taxonomy . '%', join( '/', $slug ), $post_link );
}
return $post_link;
}
Also, you need to make sure that you flush your permalinks by visiting the Settings → Permalinks page in the WordPress admin.
I need my post permalinks to include a custom taxonomy value, type, like this:
http://staging.mysite.com/article/post-title
Where article is that post's type taxonomy value. I've gotten this to work, but the problem I'm running into is that now all of my site's pages are 404ing. My custom post type and normal post urls work as intended, it's just page urls that are broken. Here is the code that causes the issue with page urls:
// Type taxonomy (no issues here)
function create_type_taxonomy() {
register_taxonomy(
'type',
'post',
array(
'labels' => array(
'name' => 'Type',
'add_new_item' => 'Add New Type',
'new_item_name' => 'New Type'
),
'show_ui' => true,
'show_tagcloud' => false,
'hierarchical' => false,
'rewrite' => array(
'slug' => 'type',
'with_front' => true
),
)
);
}
add_action( 'init', 'create_type_taxonomy', 0 );
add_action( 'init', 'st_default_post_type', 1 );
// Re-register built-in posts with a custom rewrite rule
function st_default_post_type() {
register_post_type( 'post', array(
'labels' => array(
'name_admin_bar' => _x( 'Post', 'add new on admin bar' ),
),
'public' => true,
'_builtin' => false,
'_edit_link' => 'post.php?post=%d',
'capability_type' => 'post',
'map_meta_cap' => true,
'hierarchical' => false,
'rewrite' => array( 'slug' => '%type%', 'with_front' => false ), // custom rewrite rule
'query_var' => false,
'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'trackbacks', 'custom-fields', 'comments', 'revisions', 'post-formats' ),
) );
}
add_filter('post_type_link', 'st_posts_permalink_structure', 10, 4);
// Replace custom rewrite rule on posts (%type%) with the taxonomy value
function st_posts_permalink_structure($post_link, $post, $leavename, $sample){
if ($post->post_type != 'post') {
var_dump($post->post_type);
return $post_link;
}
else{
if (strpos($post_link, '%type%') === FALSE){
return $post_link;
}
$post = get_post($post);
if (!$post){
return $post_link;
}
$terms = wp_get_object_terms($post->ID, 'type');
if ( !is_wp_error($terms) && !empty($terms) && is_object($terms[0]) ){
$taxonomy_slug = $terms[0]->slug;
}
else{
$taxonomy_slug = 'type';
}
return str_replace('%type%', $taxonomy_slug, $post_link);
}
}
Hoping another set of eyes might catch something that would cause page permalinks to 404. I've already tried just changing the permalinks setting in the admin to be /%type%/%postname%/, but this has the same issue. I found a couple other questions here that looked like the same issue I have, but none of them were answered:
WordPress Taxonomy Causing Pages to 404
When I add custom post type permalink rewrite, my regular post permalinks stop working. Can't get both to work at the same time
Hi I know this is a late answer, I had this issue today and found this info:
https://rudrastyh.com/wordpress/taxonomy-slug-in-post-type-url.html
Credit goes to: Misha Rudrastyh for this answer obviously, he has not used the rewrite declaration in his cpt instead seems to use filters to do the same thing, this got me past all my posts and pages 404ing.
To quote his code for posterity, he explained to use this code to solve the issue:
function rudr_post_type_permalink($permalink, $post_id, $leavename) {
$post_type_name = 'post_type_name'; // post type name, you can find it in admin area or in register_post_type() function
$post_type_slug = 'post_type_name'; // the part of your product URLs, not always matches with the post type name
$tax_name = 'custom_taxonomy_name'; // the product categories taxonomy name
$post = get_post( $post_id );
if ( strpos( $permalink, $post_type_slug ) === FALSE || $post->post_type != $post_type_name ) // do not make changes if the post has different type or its URL doesn't contain the given post type slug
return $permalink;
$terms = wp_get_object_terms( $post->ID, $tax_name ); // get all terms (product categories) of this post (product)
if ( !is_wp_error( $terms ) && !empty( $terms ) && is_object( $terms[0] ) ) // rewrite only if this product has categories
$permalink = str_replace( $post_type_slug, $terms[0]->slug, $permalink );
return $permalink;
}
add_filter('request', 'rudr_post_type_request', 1, 1 );
function rudr_post_type_request( $query ){
global $wpdb;
$post_type_name = 'post_type_name'; // specify your own here
$tax_name = 'custom_taxonomy_name'; // and here
$slug = $query['attachment']; // when we change the post type link, WordPress thinks that these are attachment pages
// get the post with the given type and slug from the database
$post_id = $wpdb->get_var(
"
SELECT ID
FROM $wpdb->posts
WHERE post_name = '$slug'
AND post_type = '$post_type_name'
"
);
$terms = wp_get_object_terms( $post_id, $tax_name ); // our post should have the terms
if( isset( $slug ) && $post_id && !is_wp_error( $terms ) && !empty( $terms ) ) : // change the query
unset( $query['attachment'] );
$query[$post_type_name] = $slug;
$query['post_type'] = $post_type_name;
$query['name'] = $slug;
endif;
return $query;
}
In addition like I said you will not need to declare the "rewrite" portion in the CPT arguments, you can leave the filters to handle that "after the fact" type thing.
I have an URL like this:
example.com/movies/157336/Interstellar
For reference:
example.com/movies/%movie_id%/%movie_name%
Please note, the movie_id is a custom field, and not the actual post ID.
The movie_name is the post title slug, but is only there for SEO reasons.
I need WordPress to load the page based on the custom field movie_id found in the URL and not use the page name, and if the movie_id is not found, throw the regular 404 error.
The main problem I am having, is that I can’t seem to get WordPress to load a page based on the custom field movie_id from the URL, it always uses movie_name as reference point.
How do I know that? Well the correct URL would be example.com/movies/157336/Interstellar and if I change the title to example.com/movies/157336/Intersterlarxyz then WordPress gives a 404 error.
And if I change the ID but leave the movie name correct, like this: example.com/movies/123/Interstellar then WordPress still loads the correct page.
Based on this behavior, it's safe to say WordPress loads the page based on the page slug from the URL, rather than the movie ID, and that is what I need to fix.
Here is my code so far:
movie-plugin.php
// Register Custom Post Type "movies"
function register_moviedb_post_type() {
register_post_type( 'movies',
array(
'labels' => array(
'name' => __( 'Movies' ),
'singular_name' => __( 'Movies' )
),
'taxonomies' => array('category'),
'public' => true,
'has_archive' => true,
'publicly_queryable' => true,
'show_ui' => true,
'show_in_menu' => true,
'query_var' => true,
'rewrite' => array('slug' => 'movies','with_front' => FALSE),
'supports' => array( 'title', 'editor', 'custom-fields','comments','page-attributes','trackbacks','revisions', 'thumbnail')
)
);
flush_rewrite_rules();
}
add_action( 'init', 'register_moviedb_post_type' );
// Add custom rewrite tag 'movie_id'
function custom_rewrite_tag() {
add_rewrite_tag('%movie_id%', '([^/]+)', 'movie_id=');
}
add_action('init', 'custom_rewrite_tag');
// Add rewrite rule
function custom_rewrite_basic() {
add_rewrite_rule(
'^movies/([^/]*)/([^/]*)/?',
//'index.php?post_type=movies&movie_id=$matches[1]',
'index.php?post_type=movies&movie_id=$matches[1]&name=$matches[2]',
'top'
);
}
add_action('init', 'custom_rewrite_basic');
// Query var 'movie_id'
function add_query_vars_filter( $vars ){
$vars[] = "movie_id";
return $vars;
}
add_filter( 'query_vars', 'add_query_vars_filter' );
// Custom Page Template
function custom_movie_page_template() {
if ( is_singular( 'movies' ) ) {
add_filter( 'template_include', function() {
return plugin_dir_path( __FILE__ ) . '/movies.php';
});
}
}
add_action( 'template_redirect', 'custom_movie_page_template' );
// Create custom post type link for movies
function movie_post_type_link( $link, $post = 0 ){
if ( $post->post_type == 'movies' ){
$id = $post->ID;
$post = &get_post($id);
$movie_id = get_post_meta($post->ID,'movie_id', true);
empty ( $post->slug )
and $post->slug = sanitize_title_with_dashes( $post->post_title );
return home_url(
user_trailingslashit( "movies/$movie_id/$post->slug" )
);
} else {
return $link;
}
}
add_filter( 'post_type_link', movie_post_type_link, 1, 2 );
If I remove the movie_name from the URL in add_rewrite_rule, WordPress just loads the archive page of that post type.
'index.php?post_type=movies&movie_id=$matches[1]',
If I use the page name in the URL rewrite, it always loads the page based on the name, rather than the movie_id.
'index.php?post_type=movies&movie_id=$matches[1]&name=$matches[2]',
I didn't test code below, but movies.php could be like this:
<?php $movieId = (int)get_query_var('movie_id') ;
if(movieId ==! 0):
$args = array(
'post_type' => 'movies',
'posts_per_page' => 1,
'meta_key' => 'movie_id',
'meta_value' => $movieId
);
$the_query = new WP_Query( $args );
if ( $the_query->have_posts() ) : while ( $the_query->have_posts() ) : $the_query->the_post(); ?>
//code that display data
<?php endwhile; else : ?>
//code to create post
<?php endif;
else:
//normal loop
endif;