Ask your WordPress questions! Pay money and get answers fast! (more info)

Hierarchical Custom Post Type child returns 404

  • SOLVED

Hi!

First of all: Yes, I have flushed permalink settings.

My question:

I have two custom post types. <strong>Open List</strong> (open_list) and <strong>User Images</strong> (user_images).

The open_list post type is the parent, and the user_images post type is the child. In other words:

Every user_images custom post is a child to a open_list custom post.

This is my permalink setting:

<strong>http://site.com/%postname%/</strong>

I register the post types like this:

$image_type_labels = array(
'name' => _x('User images', 'post type general name'),
'singular_name' => _x('User Image', 'post type singular name'),
'add_new' => _x('Add New User Image', 'image'),
'add_new_item' => __('Add New User Image'),
'edit_item' => __('Edit User Image'),
'new_item' => __('Add New User Image'),
'all_items' => __('View User Images'),
'view_item' => __('View User Image'),
'search_items' => __('Search User Images'),
'not_found' => __('No User Images found'),
'not_found_in_trash' => __('No User Images found in Trash'),
'parent_item_colon' => '',
'menu_name' => 'User Images'
);

$image_type_args = array(
'labels' => $image_type_labels,
'public' => true,
'query_var' => true,
'rewrite' => true,
'capability_type' => 'post',
'has_archive' => false,
'hierarchical' => true,
'map_meta_cap' => true,
'menu_position' => null,
'supports' => array('title', 'editor', 'author', 'thumbnail', 'page-attributes', 'custom-fields'),
'taxonomies' => array('category'),
);

register_post_type('user_images', $image_type_args);

$open_list_labels = array(
'name' => _x('Open lists', 'post type general name'),
'singular_name' => _x('Open list', 'post type singular name'),
'add_new' => _x('Add New Open List', 'image'),
'add_new_item' => __('Add New Open list'),
'edit_item' => __('Edit Open List'),
'new_item' => __('Add New Open List'),
'all_items' => __('View Open Lists'),
'view_item' => __('View Open List'),
'search_items' => __('Search Open Lists'),
'not_found' => __('No Open Lists found'),
'not_found_in_trash' => __('No Open Lists found in Trash'),
'parent_item_colon' => '',
'menu_name' => 'Open Lists'
);

$open_list_args = array(
'labels' => $open_list_labels,
'public' => true,
'query_var' => true,
'rewrite' => true,
'capability_type' => 'post',
'has_archive' => false,
'hierarchical' => true,
'map_meta_cap' => true,
'menu_position' => null,
'supports' => array('title', 'editor', 'author', 'thumbnail', 'page-attributes'),
'taxonomies' => array('category'),
);

register_post_type('open_list', $open_list_args);


I also have the following code to remove the post type name from the slug of these custom post types and to match the name on old posts:

function jf_remove_cpt_slug( $post_link, $post, $leavename ) {

if ( ! in_array( $post->post_type, array( 'usp_post', 'open_list', 'user_images' ) ) || 'publish' != $post->post_status )
return $post_link;

$post_link = str_replace( '/' . $post->post_type . '/', '/', $post_link );

return $post_link;
}
add_filter( 'post_type_link', 'jf_remove_cpt_slug', 10, 3 );

/**
* Have WordPress match postname to any of our public post types (page, post, race)
* All of our public post types can have /post-name/ as the slug, so they better be unique across all posts
* By default, core only accounts for posts and pages where the slug is /post-name/
*/
function jf_parse_request_trick( $query ) {

// Only noop the main query
if ( ! $query->is_main_query() )
return;

// Only noop our very specific rewrite rule match
if ( 2 != count( $query->query ) || ! isset( $query->query['page'] ) ) {
return;
}

// 'name' will be set if post permalinks are just post_name, otherwise the page rule will match
if ( ! empty( $query->query['name'] ) ) {
$query->set( 'post_type', array( 'post', 'usp_post', 'open_list', 'user_images', 'page' ) );
}
}
add_action( 'pre_get_posts', 'jf_parse_request_trick' );


When visiting the parent post, everything works as expected. Like this:

<strong>http://mysite.com/post-slug-of-open-list-post-type/</strong>

But if I visit the child post, WordPress returns a 404. Like this:

<strong>http://mysite.com/post-slug-of-open-list-post-type/post-slug-of-user-images-post-type/</strong>

Any ideas how I can solve this? It's important for me to keep this hierarchy.

I'm pretty sure that this kind of hierarchy, between two different post types, should work, because I can fetch the child posts inside the parent post without a problem. The problem is when I try to visit the child post, this is where the 404 shows up.

Thanks!

// Jens.

<strong>Edit:</strong> I just noticed that I can access the child post if I go to this page:

<em>http://mysite.com/post-slug-of-user-images-post-type/</em>

But this is wrong, I need the parent post type slug in there as well, to show that this is a hierarchical post...

Answers (3)

2015-10-05

Rempty answers:

Try this, dont forget to flush rewrite rules

$image_type_labels = array(

'name' => _x('User images', 'post type general name'),

'singular_name' => _x('User Image', 'post type singular name'),

'add_new' => _x('Add New User Image', 'image'),

'add_new_item' => __('Add New User Image'),

'edit_item' => __('Edit User Image'),

'new_item' => __('Add New User Image'),

'all_items' => __('View User Images'),

'view_item' => __('View User Image'),

'search_items' => __('Search User Images'),

'not_found' => __('No User Images found'),

'not_found_in_trash' => __('No User Images found in Trash'),

'parent_item_colon' => '',

'menu_name' => 'User Images'

);



$image_type_args = array(

'labels' => $image_type_labels,

'public' => true,

'query_var' => true,

"rewrite" => array( "slug" => "open-list/%open-listname%", "with_front" => true ),

'capability_type' => 'post',

'has_archive' => false,

'hierarchical' => true,

'map_meta_cap' => true,

'menu_position' => null,

'supports' => array('title', 'editor', 'author', 'thumbnail', 'page-attributes', 'custom-fields'),

'taxonomies' => array('category'),

);



register_post_type('user_images', $image_type_args);



$open_list_labels = array(

'name' => _x('Open lists', 'post type general name'),

'singular_name' => _x('Open list', 'post type singular name'),

'add_new' => _x('Add New Open List', 'image'),

'add_new_item' => __('Add New Open list'),

'edit_item' => __('Edit Open List'),

'new_item' => __('Add New Open List'),

'all_items' => __('View Open Lists'),

'view_item' => __('View Open List'),

'search_items' => __('Search Open Lists'),

'not_found' => __('No Open Lists found'),

'not_found_in_trash' => __('No Open Lists found in Trash'),

'parent_item_colon' => '',

'menu_name' => 'Open Lists'

);



$open_list_args = array(

'labels' => $open_list_labels,

'public' => true,

'query_var' => true,

"rewrite" => array( "slug" => "open-list", "with_front" => true ),

'capability_type' => 'post',

'has_archive' => false,

'hierarchical' => true,

'map_meta_cap' => true,

'menu_position' => null,

'supports' => array('title', 'editor', 'author', 'thumbnail', 'page-attributes'),

'taxonomies' => array('category'),

);



register_post_type('open_list', $open_list_args);

add_action( 'init', function() {

add_rewrite_rule( '^open-list/(.*)/?$','index.php?user_images=$matches[2]','top' );

});

add_filter( 'post_type_link', function( $link, $post ) {
if ( 'user_images' == get_post_type( $post ) ) {

if( $post->post_parent ) {
$parent = get_post( $post->post_parent );
if( !empty($parent->post_name) ) {
return str_replace( '%open-listname%', $parent->post_name, $link );
}
} else {
//This seems to not work. It is intented to build pretty permalinks
//when user_images has not parent, but it seems that it would need
//additional rewrite rules
//return str_replace( '/%open-listname%', '', $link );
}

}
return $link;
}, 10, 2 );


Jens Filipsson comments:

Looks promising! Just wondering about the else statement, should everything be uncommented?


Rempty comments:

There is 1 problem, first need a permalink structure like open-list/%open-listname%/%user_images%
Why?
Because you need to use the rewite rule and do the replacement.

add_rewrite_rule( '^'open-list/(.*)/([^/]+)/?$','index.php?user_images=$matches[2]','top' );


Rempty comments:

i made some modifications use 'hierarchical' => false to user_images and fixed some typos, but can't get it working without "slug post_type"

$image_type_labels = array(

'name' => _x('User images', 'post type general name'),
'singular_name' => _x('User Image', 'post type singular name'),
'add_new' => _x('Add New User Image', 'image'),

'add_new_item' => __('Add New User Image'),

'edit_item' => __('Edit User Image'),

'new_item' => __('Add New User Image'),

'all_items' => __('View User Images'),

'view_item' => __('View User Image'),

'search_items' => __('Search User Images'),

'not_found' => __('No User Images found'),
'not_found_in_trash' => __('No User Images found in Trash'),

'parent_item_colon' => '',

'menu_name' => 'User Images'

);


$image_type_args = array(

'labels' => $image_type_labels,

'public' => true,

'query_var' => true,

"rewrite" => array( "slug" => "open-list/%open-listname%", "with_front" => true ),

'capability_type' => 'post',

'has_archive' => false,

'hierarchical' => false,

'map_meta_cap' => true,

'menu_position' => null,

'supports' => array('title', 'editor', 'author', 'thumbnail', 'page-attributes', 'custom-fields'),
'taxonomies' => array('category'),

);


register_post_type('user_images', $image_type_args);


$open_list_labels = array(

'name' => _x('Open lists', 'post type general name'),
'singular_name' => _x('Open list', 'post type singular name'),

'add_new' => _x('Add New Open List', 'image'),

'add_new_item' => __('Add New Open list'),

'edit_item' => __('Edit Open List'),

'new_item' => __('Add New Open List'),

'all_items' => __('View Open Lists'),

'view_item' => __('View Open List'),

'search_items' => __('Search Open Lists'),

'not_found' => __('No Open Lists found'),

'not_found_in_trash' => __('No Open Lists found in Trash'),
'parent_item_colon' => '',

'menu_name' => 'Open Lists'

);







$open_list_args = array(



'labels' => $open_list_labels,

'public' => true,

'query_var' => true,

"rewrite" => array( "slug" => "open-list", "with_front" => true ),

'capability_type' => 'post',

'has_archive' => false,

'hierarchical' => true,

'map_meta_cap' => true,

'menu_position' => null,

'supports' => array('title', 'editor', 'author', 'thumbnail', 'page-attributes'),



'taxonomies' => array('category'),



);




register_post_type('open_list', $open_list_args);



add_action( 'init', function() {

add_rewrite_rule( '^open-list/(.*)/([^/]+)/?$','index.php?user_images=$matches[2]','top' );


});



add_filter( 'post_type_link', function( $link, $post ) {

if ( 'user_images' == get_post_type( $post ) ) {



if( $post->post_parent ) {

$parent = get_post( $post->post_parent );

if( !empty($parent->post_name) ) {

return str_replace( '%open-listname%', $parent->post_name, $link );

}

} else {

//This seems to not work. It is intented to build pretty permalinks

//when user_images has not parent, but it seems that it would need

//additional rewrite rules

//return str_replace( '/%open-listname%', '', $link );

}



}

return $link;

}, 10, 2 );


Jens Filipsson comments:

So I should remove the filter I currently have in place you mean?


Rempty comments:

Yes, because dont work like pages /pageparent/pagechild
Need a base slug, i am using "open-list" as base
open-list/listparent-slug
open-list/listparent-slug/userimage_slug

Userimage can't be hiererchical
open-list/listparent-slug/userimage1/useimage2 Will not work


Jens Filipsson comments:

But that's a good thing, since I don't want userimages to be hierarchial. I only want them to be hierarchial to the open_list. My filter takes care of removing the base, so could it work as we want with your code + my filter?

// Jens.


Jens Filipsson comments:

This is what I want:

listparent-slug/userimage-slug

Would it be possible to remove the open-list part of your code?

// Jens.


Jens Filipsson comments:

Remove open-list from this url I mean:

open-list/listparent-slug/userimage_slug


Rempty comments:

After a lot of work
Delete your filters and flush rewite rules

$image_type_labels = array(
'name' => _x('User images', 'post type general name'),
'singular_name' => _x('User Image', 'post type singular name'),
'add_new' => _x('Add New User Image', 'image'),
'add_new_item' => __('Add New User Image'),
'edit_item' => __('Edit User Image'),
'new_item' => __('Add New User Image'),
'all_items' => __('View User Images'),
'view_item' => __('View User Image'),
'search_items' => __('Search User Images'),
'not_found' => __('No User Images found'),
'not_found_in_trash' => __('No User Images found in Trash'),
'parent_item_colon' => '',
'menu_name' => 'User Images'

);
$image_type_args = array(
'labels' => $image_type_labels,
'public' => true,
'query_var' => true,
"rewrite" => true,
'capability_type' => 'post',
'has_archive' => false,
'hierarchical' => false,
'map_meta_cap' => true,
'menu_position' => null,
'supports' => array('title', 'editor', 'author', 'thumbnail', 'page-attributes', 'custom-fields'),
'taxonomies' => array('category'),
);

register_post_type('user_images', $image_type_args);

$open_list_labels = array(
'name' => _x('Open lists', 'post type general name'),
'singular_name' => _x('Open list', 'post type singular name'),
'add_new' => _x('Add New Open List', 'image'),
'add_new_item' => __('Add New Open list'),
'edit_item' => __('Edit Open List'),
'new_item' => __('Add New Open List'),
'all_items' => __('View Open Lists'),
'view_item' => __('View Open List'),
'search_items' => __('Search Open Lists'),
'not_found' => __('No Open Lists found'),
'not_found_in_trash' => __('No Open Lists found in Trash'),
'parent_item_colon' => '',
'menu_name' => 'Open Lists'
);


$open_list_args = array(
'labels' => $open_list_labels,
'public' => true,
'query_var' => true,
"rewrite" => true,
'capability_type' => 'post',
'has_archive' => false,
'hierarchical' => true,
'map_meta_cap' => true,
'menu_position' => null,
'supports' => array('title', 'editor', 'author', 'thumbnail', 'page-attributes'),
'taxonomies' => array('category'),
);

add_filter(
'post_type_link',
'custom_post_type_link',
10,
3
);

function custom_post_type_link($permalink, $post, $leavename) {
if (!gettype($post) == 'post') {
return $permalink;
}
$parent = get_post( $post->post_parent );
switch ($post->post_type) {
case 'user_images':
$permalink = get_home_url() . '/'.$parent->post_name.'/'.$post->post_name . '/';
break;
case 'open_list':
$permalink = get_home_url() . '/'.$post->post_name . '/';
break;
}

return $permalink;
}

add_action( 'pre_get_posts', 'custom_pre_get_posts');

function custom_pre_get_posts($query) {
global $wpdb;

if(!$query->is_main_query()) {
return;
}

if($query->get('attachment')!=''){
$post_name = $query->get('attachment');
}else{
$post_name = $query->get('name');
}
//echo '<pre>' . print_r($query, true) . '</pre>';

$post_type = $wpdb->get_var(
$wpdb->prepare(
'SELECT post_type FROM ' . $wpdb->posts . ' WHERE post_name = %s LIMIT 1',
$post_name
)
);

$result = $wpdb->get_row(
$wpdb->prepare(
'SELECT wpp1.post_type, wpp2.post_name AS parent_post_name FROM ' . $wpdb->posts . ' as wpp1 LEFT JOIN ' . $wpdb->posts . ' as wpp2 ON wpp1.post_parent = wpp2.ID WHERE wpp1.post_name = %s LIMIT 1',
$post_name
)
);

switch($post_type) {
case 'user_images':
$query->set('user_images', $post_name);
$query->set('post_type', $post_type);
$query->is_single = true;
$query->is_page = false;
break;
case 'open_list':
$query->set('open_list', $post_name);
$query->set('post_type', $post_type);
$query->is_single = true;
$query->is_page = false;
break;
}
return $query;
}


Jens Filipsson comments:

This seems to work perfectly. Thank you so much for your help! Greatly appreciated!


Jens Filipsson comments:

I might have been a bit quick to vote for the answer. Right now the pages (not posts) on the site returns 404 errors. If I remove the functions, it works.

Would it be possible to make your functions only affect the two post types and nothing else on the site?

2015-10-05

Sébastien | French WordpressDesigner answers:

could you just refresh the page of setting of permalinks please ?


Jens Filipsson comments:

Thanks for you answer, Sébastien!

As I stated in the top of my post:

<em>First of all: Yes, I have flushed permalink settings.</em>

So I have already tried this...


Jens Filipsson comments:

But now I noticed something:

I can access the child post if I visit:

<strong>http://mysite.com/post-slug-of-user-images-post-type/</strong>

But this is wrong, I need to show that it is hierarchical. Any ideas what might be wrong?


Sébastien | French WordpressDesigner comments:

do you say that the both url works and display the same page :
http://mysite.com/post-slug-of-open-list-post-type/post-slug-of-user-images-post-type/
http://mysite.com/post-slug-of-user-images-post-type/


Jens Filipsson comments:

No. This <strong>works:</strong>

http://mysite.com/post-slug-of-open-list-post-type/

And this <strong>works:</strong>

http://mysite.com/post-slug-of-user-images-post-type/

But this <strong>doesn't work</strong> (although this is the output of the_permalink to the child post type. And this is what I want)

http://mysite.com/post-slug-of-open-list-post-type/post-slug-of-user-images-post-type/

2015-10-05

Hai Bui answers:

How did you create the parent-child relationship between the two custom post types?