0

I have a blog in Drupal 8 and it uses the Categories vocabulary to display breadcrumbs as Home > Blog > [Category] > Post title.

I added taxonomies from the Tags vocabulary, which means every post can have one category, and multiple tags. Since I added the Tags vocabulary, it has taken over the breadcrumbs on individual blog posts, which are now Home > Blog > [Tag] > Post title.

Is it possible to control which vocabulary should have priority when it comes to displaying breadcrumbs? Is it possible to control which vocabulary is displayed on blog posts based on where the user came from? For example, if the user is on the individual Tag page (Home > Blog > [Tag]) and clicks on a blog post belonging to that tag, the blog post should display the breadcrumbs using the Tags vocabulary (Home > Blog > [Tag] > Post title). However, if the user is on the individual Category page (Home > Blog > [Category]) and clicks on a blog post belonging to that category, the blog post should display the breadcrumbs using the Category vocabulary (Home > Blog > [Category] > Post title).

4
  • 1
    Afaik, default drupal breadcrumbs match the url path. Adding or removing tags wouldn't affect breadcrumbs unless the url pattern depended on them. It sounds like you're probably not using the default breadcrumbs so the first thing I'd do is try to determine how your breadcrumbs are currently being created.
    – sonfd
    Commented Feb 18, 2021 at 20:40
  • Do you have a single field with both vocabs enabled, or one field for each vocab?
    – Clive
    Commented Feb 18, 2021 at 20:46
  • Each vocab has a dedicated field (unique machine name).
    – bdereta
    Commented Feb 18, 2021 at 21:10
  • Blog post URL does not contain category, nor the tag. It's just /blog/post-title for every blog post.
    – bdereta
    Commented Feb 18, 2021 at 21:12

1 Answer 1

0

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;
    }
  }
}

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.