So there are a million snippets for how to get pages as parents of custom post types.
However, the reverse seems to be questionable. One would think, since everything in WordPress is technically a “post”, this would be trivial. However, it is not.
Thus far, I have:
add_filter( 'page_attributes_dropdown_pages_args',
function( $dropdown_args, $post = null ) {
$dropdown_args['post_type'] = 'portal';
return $dropdown_args;
} );
And sure enough, pages now allow for me to select portal
-type posts as parents. The URLs do seem to register correctly within wordpress, though they all 404.
How can I get wordpress to understand this hierarchy and load the pages that have posts as parents?
3 s
WordPress uses a set of rewrite rules to be able to convert an url into a database query.
The regex that handle urls for pages is very general, IIRC it is something like (.+.?)/?
, it mathes pratically everything has not be already matched by other rules.
For this reason is not possible to write a rewrite rule that will work in your case: because you can’t distinguish via regex that in a url like example.com/my-portal/sample-page
the ‘my-portal’ part is a CPT and sample-page
is a page.
Things become more complex if you have more levels of nesting: my-portal/my-portal-child/sample-page
.
To handle this kind of urls, WordPress uses get_page_by_path()
function: it explodes the page url by /
, obtaining page slugs, then queries database for all the pages that have those slugs.
E.g if you have a page whose slug is “sample-page” and you set as parent for it the CPT “my-portal” WordPress calls:
get_page_by_path('my-portal/sample-page')
but it doesn’t return any result because it looks for a page with slug ‘sample-page’ whose parent is another page with slug ‘my-portal’. That page doesn’t exists, so you get the 404 error.
However, get_page_by_path()
accepts as 3rd argument an array of post types: if you set it to array('page', 'portal')
then the function will be able to correctly find the page.
So you can solve the issue by manually setting the page id (retrieved as explained above) into WP query vars.
The 'parse_request'
hook is perfect for the scope:
- it runs after the url has been parsed
- it passes to hooking callbacks the instance of
$wp
object you can use to set query vars
Code:
add_action('parse_request', function ($wp) {
// only if WP found a page
if (isset($wp->query_vars['pagename']) && ! empty($wp->query_vars['pagename'])) {
$page = get_page_by_path( // let's find the page object
$wp->query_vars['pagename'],
OBJECT,
array('page', 'portal') // we need to set both post types
);
if ($page instanceof WP_Post) { // if we find a page
unset($wp->query_vars['pagename']); // remove pagename var
$wp->query_vars['page_id'] = $page->ID; // replace with page_id query var
}
}
});
This code, in combination with the filter in OP, is all you need.
Note that code works even with nested hierarchical portals.