You are here

public function EasyBreadcrumbBuilder::build in Easy Breadcrumb 8

Same name and namespace in other branches
  1. 2.x src/EasyBreadcrumbBuilder.php \Drupal\easy_breadcrumb\EasyBreadcrumbBuilder::build()

Builds the breadcrumb.

Parameters

\Drupal\Core\Routing\RouteMatchInterface $route_match: The current route match.

Return value

\Drupal\Core\Breadcrumb\Breadcrumb A breadcrumb.

Overrides BreadcrumbBuilderInterface::build

File

src/EasyBreadcrumbBuilder.php, line 259

Class

EasyBreadcrumbBuilder
Primary implementation for the Easy Breadcrumb builder.

Namespace

Drupal\easy_breadcrumb

Code

public function build(RouteMatchInterface $route_match) {
  $breadcrumb = new Breadcrumb();
  $links = [];
  $exclude = [];
  $curr_lang = $this->languageManager
    ->getCurrentLanguage()
    ->getId();
  $replacedTitles = [];
  $mapValues = preg_split('/[\\r\\n]+/', $this->config
    ->get(EasyBreadcrumbConstants::REPLACED_TITLES));
  $limit_display = $this->config
    ->get(EasyBreadcrumbConstants::LIMIT_SEGMENT_DISPLAY);
  $segment_limit = $this->config
    ->get(EasyBreadcrumbConstants::SEGMENT_DISPLAY_LIMIT);
  foreach ($mapValues as $mapValue) {
    $values = explode("::", $mapValue);
    if (count($values) == 2) {
      $replacedTitles[$values[0]] = $values[1];
    }
  }

  // Set request context from the $route_match if route is available.
  $this
    ->setRouteContextFromRouteMatch($route_match);

  // General path-based breadcrumbs. Use the actual request path, prior to
  // resolving path aliases so the breadcrumb can be defined by creating a
  // hierarchy of path aliases.
  $path = trim($this->context
    ->getPathInfo(), '/');
  $path = urldecode($path);
  $path_elements = explode('/', $path);
  $front = $this->siteConfig
    ->get('page.front');

  // Give the option to keep the breadcrumb on the front page.
  $keep_front = !empty($this->config
    ->get(EasyBreadcrumbConstants::HOME_SEGMENT_TITLE)) && $this->config
    ->get(EasyBreadcrumbConstants::HOME_SEGMENT_KEEP);
  $exclude[$front] = $keep_front;
  $exclude[''] = !$keep_front;
  $exclude['/user'] = TRUE;

  // See if we are doing a Custom Path override.
  $path_crumb_row = preg_split('/[\\r\\n]+/', $this->config
    ->get(EasyBreadcrumbConstants::CUSTOM_PATHS));
  $path_crumb_row = array_filter($path_crumb_row);
  foreach ($path_crumb_row as $path_crumb) {
    $values = explode("::", $path_crumb);

    // Shift path off array.
    $custom_path = array_shift($values);

    // Strip of leading/ending slashes and spaces/tabs (allows indenting
    // rows on config page).
    $custom_path = mb_strtolower(trim($custom_path, "/ \t"));

    // Check if custom path includes the flag used to signify that the
    // path is expressed as a regular expression pattern.
    $regex_match = [];
    $is_regex = preg_match('/^regex\\s*!\\s*\\/(.*)/', $custom_path, $regex_match);
    if ($is_regex) {
      $custom_path = $regex_match[1];
      $regex_group_matches = [];
    }

    // If the path matches the current path, build the breadcrumbs.
    if ($is_regex && preg_match("|" . $custom_path . "|", $path, $regex_group_matches) || !$is_regex && $path == $custom_path) {
      if ($this->config
        ->get(EasyBreadcrumbConstants::INCLUDE_HOME_SEGMENT)) {
        $links[] = Link::createFromRoute($this->config
          ->get(EasyBreadcrumbConstants::HOME_SEGMENT_TITLE), '<front>');
      }
      if ($is_regex && count($regex_group_matches) > 1) {

        // Discard first element as that's the full matched string
        // rather than a captured group.
        array_shift($regex_group_matches);
      }

      // Get $title|[$url] pairs from $values.
      foreach ($values as $pair) {
        $settings = explode("|", $pair);
        $title = Html::decodeEntities(Xss::filter(trim($settings[0])));
        $use_current_page_title = trim($settings[0]) === '<title>';

        // If the custom title uses the current page title, fetch it.
        if ($use_current_page_title) {
          $route_request = $this
            ->getRequestForPath($path, []);
          if ($route_request) {
            $route_match = RouteMatch::createFromRequest($route_request);
            $access = $this->accessManager
              ->check($route_match, $this->currentUser, NULL, TRUE);
            $breadcrumb = $breadcrumb
              ->addCacheableDependency($access);

            // The set of breadcrumb links depends on the access result, so merge
            // the access result's cacheability metadata.
            if ($access
              ->isAllowed()) {
              if ($this->config
                ->get(EasyBreadcrumbConstants::TITLE_FROM_PAGE_WHEN_AVAILABLE)) {
                $title = $this
                  ->normalizeText($this
                  ->getTitleString($route_request, $route_match, $replacedTitles));
              }
            }
          }
        }
        elseif ($is_regex) {
          foreach ($regex_group_matches as $group_num => $captured_str) {
            $title = str_replace('$' . ($group_num + 1), urlencode($captured_str), $title);
          }
        }

        // Get URL if it is provided.
        $url = '';
        if (isset($settings[1])) {
          $url = trim($settings[1]);

          // If the custom path includes any regex match groups
          // (eg. "/foo/(\d*)/bar") then check if the urls for any segments
          // have matched group variables (eg. $1 or $3) and if they do
          // substitute them out for the the corresponding
          // matched strings.
          if ($is_regex) {
            foreach ($regex_group_matches as $group_num => $captured_str) {
              $url = str_replace('$' . ($group_num + 1), urlencode($captured_str), $url);
            }
          }

          // If URL is invalid, then display warning and disable the link.
          if (!UrlHelper::isValid($url)) {
            $this->messenger
              ->addWarning($this
              ->t("EasyBreadcrumb: Custom crumb for @path URL '@url' is invalid.", [
              '@path' => $path,
              '@url' => $url,
            ]));
            $url = '';
          }

          // If URL is not start with slash then display warning
          // and disable the link.
          if ($url[0] != '/') {
            $this->messenger
              ->addWarning($this
              ->t("EasyBreadcrumb: Custom crumb for @path URL '@url' should start with slash(/).", [
              '@path' => $path,
              '@url' => $url,
            ]));
            $url = '';
          }
        }
        if ($url) {
          $links[] = new Link($title, Url::fromUserInput($url, [
            'absolute' => TRUE,
          ]));
        }
        else {
          $links[] = Link::createFromRoute($title, '<none>');
        }
      }

      // Handle views path expiration cache expiration.
      $parameters = $route_match
        ->getParameters();
      foreach ($parameters as $key => $parameter) {
        if ($key === 'view_id') {
          $breadcrumb
            ->addCacheTags([
            'config:views.view.' . $parameter,
          ]);
        }
        if ($parameter instanceof CacheableDependencyInterface) {
          $breadcrumb
            ->addCacheableDependency($parameter);
        }
      }

      // Expire cache by languages and config changes.
      $breadcrumb
        ->addCacheContexts([
        'route',
        'url.path',
        'languages',
      ]);

      // Expire cache context for config changes.
      $breadcrumb
        ->addCacheableDependency($this->config);
      return $breadcrumb
        ->setLinks($links);
    }
  }

  // Handle views path expiration cache expiration.
  $parameters = $route_match
    ->getParameters();
  foreach ($parameters as $key => $parameter) {
    if ($key === 'view_id') {
      $breadcrumb
        ->addCacheTags([
        'config:views.view.' . $parameter,
      ]);
    }
    if ($parameter instanceof CacheableDependencyInterface) {
      $breadcrumb
        ->addCacheableDependency($parameter);
    }
  }

  // Expire cache by languages and config changes.
  $breadcrumb
    ->addCacheContexts([
    'route',
    'url.path',
    'languages',
  ]);
  $breadcrumb
    ->addCacheableDependency($this->config);
  $i = 0;
  $add_langcode = FALSE;

  // Remove the current page if it's not wanted.
  if (!$this->config
    ->get(EasyBreadcrumbConstants::INCLUDE_TITLE_SEGMENT)) {
    array_pop($path_elements);
  }
  if (isset($path_elements[0])) {

    // Remove the first parameter if it matches the current language.
    if (!$this->config
      ->get(EasyBreadcrumbConstants::LANGUAGE_PATH_PREFIX_AS_SEGMENT)) {
      if (mb_strtolower($path_elements[0]) == mb_strtolower($curr_lang)) {

        // Preserve case in language to allow path matching to work properly.
        $curr_lang = $path_elements[0];
        array_shift($path_elements);
        $add_langcode = TRUE;
      }
    }
  }

  // Remove leading breadcrumb segments by limiting the following loop.
  $loop_limit = $limit_display && isset($segment_limit) ? $segment_limit : 0;
  while (count($path_elements) > $loop_limit) {
    $check_path = '/' . implode('/', $path_elements);
    if ($add_langcode) {
      $check_path = '/' . $curr_lang . $check_path;
    }

    // Copy the path elements for up-casting.
    $route_request = $this
      ->getRequestForPath($check_path, $exclude);
    if ($this->config
      ->get(EasyBreadcrumbConstants::EXCLUDED_PATHS)) {
      $config_textarea = $this->config
        ->get(EasyBreadcrumbConstants::EXCLUDED_PATHS);
      $excludes = preg_split('/[\\r\\n]+/', $config_textarea, -1, PREG_SPLIT_NO_EMPTY);
      if (in_array(end($path_elements), $excludes)) {
        array_pop($path_elements);
        continue;
      }
    }
    if ($route_request) {
      $route_match = RouteMatch::createFromRequest($route_request);
      $access = $this->accessManager
        ->check($route_match, $this->currentUser, NULL, TRUE);
      $breadcrumb = $breadcrumb
        ->addCacheableDependency($access);

      // The set of breadcrumb links depends on the access result, so merge
      // the access result's cacheability metadata.
      if ($access
        ->isAllowed()) {
        if ($this->config
          ->get(EasyBreadcrumbConstants::TITLE_FROM_PAGE_WHEN_AVAILABLE)) {

          // Get the title if the current route represents an entity.
          if (($route = $route_match
            ->getRouteObject()) && ($parameters = $route
            ->getOption('parameters'))) {
            foreach ($parameters as $name => $options) {
              if (isset($options['type']) && strpos($options['type'], 'entity:') === 0) {
                $entity = $route_match
                  ->getParameter($name);
                if ($entity instanceof ContentEntityInterface && $entity
                  ->hasLinkTemplate('canonical')) {
                  $title = $entity
                    ->label();
                  if ($this->config
                    ->get(EasyBreadcrumbConstants::TRUNCATOR_MODE)) {
                    $title = $this
                      ->truncator($title);
                  }
                  break;
                }
              }
            }
          }
          else {
            $title = $this
              ->normalizeText($this
              ->getTitleString($route_request, $route_match, $replacedTitles));
            if ($this->config
              ->get(EasyBreadcrumbConstants::TRUNCATOR_MODE)) {
              $title = $this
                ->truncator($title);
            }
            if (empty($title)) {
              unset($title);
            }

            // If the title is to be replaced...
            if (!empty($title) && array_key_exists($title, $replacedTitles)) {

              // Replaces the title.
              $title = $replacedTitles[(string) $title];
            }
          }
        }
        if (!isset($title)) {
          if ($this->config
            ->get(EasyBreadcrumbConstants::USE_MENU_TITLE_AS_FALLBACK)) {

            // Try resolve the menu title from the route.
            $route_name = $route_match
              ->getRouteName();
            $route_parameters = $route_match
              ->getRawParameters()
              ->all();
            $menu_links = $this->menuLinkManager
              ->loadLinksByRoute($route_name, $route_parameters);
            if (empty($menu_links)) {
              if ($this->config
                ->get(EasyBreadcrumbConstants::USE_PAGE_TITLE_AS_MENU_TITLE_FALLBACK)) {
                $title = $this
                  ->getTitleString($route_request, $route_match, $replacedTitles);
                if (!empty($title) && array_key_exists($title, $replacedTitles)) {
                  $title = $replacedTitles[$title];
                }
                if (!empty($title) && $this->config
                  ->get(EasyBreadcrumbConstants::TRUNCATOR_MODE)) {
                  $title = $this
                    ->truncator($title);
                }
              }
            }
            else {
              $menu_link = reset($menu_links);
              $title = $this
                ->normalizeText($menu_link
                ->getTitle());
              if (array_key_exists($title, $replacedTitles)) {
                $title = $replacedTitles[$title];
              }
              if ($this->config
                ->get(EasyBreadcrumbConstants::TRUNCATOR_MODE)) {
                $title = $this
                  ->truncator($title);
              }
            }
          }

          // Fallback to using the raw path component as the title if the
          // route is missing a _title or _title_callback attribute.
          if (!isset($title)) {
            $title = $this
              ->normalizeText(end($path_elements));
            if (array_key_exists($title, $replacedTitles)) {
              $title = $replacedTitles[$title];
            }
            if ($this->config
              ->get(EasyBreadcrumbConstants::TRUNCATOR_MODE)) {
              $title = $this
                ->truncator($title);
            }
          }
        }

        // Add a linked breadcrumb unless it's the current page.
        if ($i == 0 && $this->config
          ->get(EasyBreadcrumbConstants::INCLUDE_TITLE_SEGMENT) && !$this->config
          ->get(EasyBreadcrumbConstants::TITLE_SEGMENT_AS_LINK)) {
          $title = $this
            ->truncator($title);
          $links[] = Link::createFromRoute($title, '<none>');
        }
        elseif ($route_match
          ->getRouteObject()) {
          $url = Url::fromRouteMatch($route_match);
          if ($this->config
            ->get(EasyBreadcrumbConstants::ABSOLUTE_PATHS)) {
            $url
              ->setOption('absolute', TRUE);
          }
          $title = $this
            ->truncator($title);
          $links[] = new Link($title, $url);
        }

        // Add all term parents.
        if ($i == 0 && $this->config
          ->get(EasyBreadcrumbConstants::TERM_HIERARCHY) && ($term = $route_match
          ->getParameter('taxonomy_term'))) {
          $parents = $this->entityTypeManager
            ->getStorage('taxonomy_term')
            ->loadAllParents($term
            ->id());

          // Unset current term.
          array_shift($parents);
          foreach ($parents as $parent) {
            $parent = $this->entityRepository
              ->getTranslationFromContext($parent);
            $links[] = $parent
              ->toLink();
          }
        }
        unset($title);
        $i++;
      }
    }
    elseif ($this->config
      ->get(EasyBreadcrumbConstants::INCLUDE_INVALID_PATHS) && empty($exclude[implode('/', $path_elements)])) {
      $title = $this
        ->normalizeText(end($path_elements));
      $this
        ->applyTitleReplacement($title, $replacedTitles);
      $links[] = Link::createFromRoute($title, '<none>');
      unset($title);
    }
    array_pop($path_elements);
  }

  // Add the home link, if desired.
  if ($this->config
    ->get(EasyBreadcrumbConstants::INCLUDE_HOME_SEGMENT)) {
    if ($path && '/' . $path != $front && $path != $curr_lang) {
      if (!$this->config
        ->get(EasyBreadcrumbConstants::USE_SITE_TITLE)) {
        $links[] = Link::createFromRoute($this
          ->normalizeText($this->config
          ->get(EasyBreadcrumbConstants::HOME_SEGMENT_TITLE)), '<front>');
      }
      else {
        $links[] = Link::createFromRoute($this->siteConfig
          ->get('name'), '<front>');
      }
    }
    if ($this->config
      ->get(EasyBreadcrumbConstants::HIDE_SINGLE_HOME_ITEM) && count($links) === 1) {
      return $breadcrumb
        ->setLinks([]);
    }
  }
  $links = array_reverse($links);
  if ($this->config
    ->get(EasyBreadcrumbConstants::REMOVE_REPEATED_SEGMENTS)) {
    $links = $this
      ->removeRepeatedSegments($links);
  }
  return $breadcrumb
    ->setLinks($links);
}