Here's a solution that worked for me.
function [YOUR-MODULE-NAME]_system_breadcrumb_alter(\Drupal\Core\Breadcrumb\Breadcrumb &$breadcrumb, \Drupal\Core\Routing\RouteMatchInterface $route_match, array $context) {
// Control Blog Post Breadcrumb Taxonomy Trail
$blogPostBreadcrumb = new BlogPostBreadcrumb();
$blogPostBreadcrumb->alter($breadcrumb);
}
use Drupal\Core\Breadcrumb\Breadcrumb;
use Drupal\Core\Entity\EntityBase;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Url;
use Drupal\node\Entity\Node;
use Drupal\taxonomy\Entity\Term;
use Exception;
use Symfony\Component\HttpFoundation\Request;
/**
* Class BlogPostBreadcrumb
*
* The purpose of this class is to control Blog post breadcrumbs, as described here:
*
* RULE 1:
* When the user is on the Tag page (Home > Blog > Tag) and clicks on a blog post, the blog post breadcrumbs will
* display: Home > Blog > [Tag Name] > Blog Post Title
*
* RULE 2:
* When the user is on the Category page (Home > Blog > Category) and clicks on a blog post, the blog post breadcrumbs
* will display: Home > Blog > [Category Name] > Blog Post Title
*
* RULE 3:
* When the user loads the blog post directly (i.e. Home > Blog, or comes from other sources unrelated to
* Category or Tag), the blog post breadcrumbs should default to Category:
* Home > Blog > [Category Name] > Blog Post Title
*
*/
class BlogPostBreadcrumb
{
/**
* @param Breadcrumb $breadcrumb
*/
public function alter(Breadcrumb &$breadcrumb): void
{
try {
// current node
$current_node = $this->currentNode();
// nothing found, nothing to do
if (empty($current_node)) {
return;
}
// what is the current content type
$content_type = $this->getField($current_node, 'type');
// apply rule only to blog_post content type, otherwise let's stop the madness right here
if ($content_type !== 'blog_post') {
return;
}
// determine if the tag can be extracted from referrer. If not, default it to college_tag
$altered_breadcrumb = $this->getTaxonomyLabelAndUrlByReferrer()
?? $this->getTaxonomyLabelAndUrlByCollege($current_node);
// nothing matched - just let it be. Display blog_tags rather than nothing.
if (empty($altered_breadcrumb)) {
return;
}
// modify existing breadcrumb list by replacing the third link with proper tag
// (Home > Blog > [college_tag/blog_tag] > Blog Title)
$breadcrumb->getLinks()[2]->setText($altered_breadcrumb['label']);
$breadcrumb->getLinks()[2]->setUrl($altered_breadcrumb['url']);
// we have to disable caching, otherwise whichever taxonomy opens first, it will get cached!
// load menu_link_content entity
$entity = \Drupal::entityTypeManager()
->getStorage('menu_link_content')
->load($current_node->getEntityTypeId());
// disable caching
$breadcrumb->addCacheableDependency($entity);
} catch (Exception $e) {
// if any exception is thrown, we don't want to stop the page from loading.
// Worse case scenario, it will load blog_tags into breadcrumbs.
}
}
/**
* Get Field from node
* Helper method for retrieving field value from given node.
*
* @param EntityInterface $node
* @param string $fieldName
* @return mixed|null
*/
protected function getField(EntityInterface $node, string $fieldName)
{
return $node->get($fieldName)->getValue()[0]['target_id'] ?? null;
}
/**
* Load node from current url
*
* @return EntityBase|null
*/
protected function currentNode():? EntityBase
{
if ($nid = \Drupal::routeMatch()->getParameter('node')) {
return Node::load($nid->id()) ?? null;
}
return null;
}
/**
* Determine Taxonomy Breadcrumb Label and Url by referrer
* We want to display Breadcrumb category depending from where the user is coming from:
* Blog College (category) or Blog tag page
*
* @return array|null
*/
protected function getTaxonomyLabelAndUrlByReferrer():? array
{
// capture referrer the "Drupal way"
$route_parameters = $this->referrer();
// if type is not a taxonomy term, it's not used to us
if (empty($route_parameters['taxonomy_term'])) {
return null;
}
// load node (taxonomy term)
$term = Term::load($route_parameters['taxonomy_term']);
// get taxonomy type
$taxonomy = $this->getField($term, 'vid');
// check if taxonomy belongs to college_tags or blog_tags
if (empty($taxonomy) && !in_array($taxonomy, ['college_tags', 'blog_tags'])) {
return null;
}
// if we can't find the url or the label
if (empty($term->url() || empty($term->label()))) {
return null;
}
return [
'label' => $term->label(),
'url' => Url::fromUserInput($term->url())
];
}
/**
* Determine Taxonomy Breadcrumb Label and Url by college
* This method will be used when referrer page doesn't exist, or the referrer is not Blog Category or Blog Tag page
*
* @param EntityInterface $node
* @return array|null
*/
protected function getTaxonomyLabelAndUrlByCollege(EntityInterface $node):? array
{
// blog can have one college tag (category)
$taxonomy_id = $this->getField($node, 'field_college');
$term = Term::load($taxonomy_id);
// if we can't find the url or the label
if (empty($term->url() || empty($term->label()))) {
return null;
}
return [
'label' => $term->label(),
'url' => Url::fromUserInput($term->url())
];
}
/**
* Referrer node
* Load referrer node the "drupal way"
*
* @return mixed
*/
protected function referrer()
{
// this would be college_tag or blog_tag page
$previousUrl = \Drupal::request()->server->get('HTTP_REFERER');
// fake the request to previous page, so we can get more info
$fake_request = Request::create($previousUrl);
// build url object.
$url_object = \Drupal::service('path.validator')->getUrlIfValid($fake_request->getRequestUri());
if ($url_object) {
return $url_object->getRouteParameters();
} else {
return null;
}
}
}