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.

Leave a Reply

Your email address will not be published. Required fields are marked *