Add post type archives to the navigation block
Published: – Leave a comment
Currently, you cannot add post type archives to the navigation block. At least not in a way that it recognizes its state, e.g. whether it’s the current active menu item. This is a known issue in the Gutenberg project (see GitHub) since at least May 2021. And unfortunately, it doesn’t seem to be addressed very soon.
When I needed it for a custom post type archive “job”, I came up with a solution that changes the output of the navigation block. This is solely required to set the current-menu-item
and current-menu-ancestor
class.
The script basically checks whether the current request is requesting the post archive or a singular page of the desired post type and then adds the correct class to the menu item via PHP’s DOMDocument
. I strictly rely on the used URL on the menu item, which in my case is /job/
.
So for a single post type this is what the code looks like:
/**
* Set job archive menu item as active if needed.
*
* Currently, post type archives cannot be added to the navigation block.
*
* @see https://github.com/WordPress/gutenberg/issues/31452
*
* @param string $block_content Block content
* @return string Updated block content
*/
static function my_set_job_archive_active( string $block_content ): string {
if ( ! \is_post_type_archive( 'job' ) && ! \is_singular( 'job' ) ) {
return $block_content;
}
$libxml_use_errors = \libxml_use_internal_errors( true );
$dom = new \DOMDocument();
$dom->loadHTML(
'<html><meta charset="utf-8">' . $block_content . '</html>',
LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD
);
$has_changed = false;
/** @var \DOMElement $list_element */
foreach ( $dom->getElementsByTagName( 'li' ) as $list_element ) {
$class_name = '';
// get classes of first link
foreach ( $list_element->getElementsByTagName( 'a' ) as $child_link ) {
$class_name = $child_link->getAttribute( 'class' );
break;
}
// current item already has desired class
if (
\str_contains( $class_name, 'current-menu-item' )
|| \str_contains( $class_name, 'current-menu-ancestor' )
) {
continue;
}
if ( \str_contains( $list_element->getAttribute( 'class' ), 'has-child' ) ) {
$is_target = false;
// check if a child is active
foreach ( $list_element->getElementsByTagName( 'a' ) as $child_link ) {
if ( \str_ends_with( $child_link->getAttribute( 'href' ), '/job/' ) ) {
$is_target = true;
}
}
if ( $is_target ) {
foreach ( $list_element->getElementsByTagName( 'a' ) as $link ) {
$link->setAttribute( 'class', \trim( $link->getAttribute( 'class' ) . ' current-menu-ancestor' ) );
$has_changed = true;
// only check the first item
break;
}
}
}
else {
foreach ( $list_element->getElementsByTagName( 'a' ) as $link ) {
if ( \str_ends_with( $link->getAttribute( 'href' ), '/job/' ) ) {
$new_class = 'current-menu-' . ( \is_singular( 'job' ) ? 'ancestor' : 'item' );
$list_element->setAttribute( 'class', \trim( $list_element->getAttribute( 'class' ) . ' ' . $new_class ) );
$has_changed = true;
}
// only check the first item
break;
}
}
}
if ( $has_changed ) {
$block_content = $dom->saveHTML( $dom->documentElement->firstChild->nextSibling ); // phpcs:ignore WordPress.NamingConventions.ValidVariableName.UsedPropertyNotSnakeCase
}
\libxml_use_internal_errors( $libxml_use_errors );
return $block_content;
}
\add_filter( 'render_block_core/navigation', 'my_set_job_archive_active' );
Code language: PHP (php)
To use multiple different post types, just loop the code for every post type accordingly.