From 4d08d57418c663b59953ab0f84e1d874f25dd27d Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Tue, 18 Jun 2013 07:22:49 +0200
Subject: [PATCH] Issue #1947536 by andypost, donquixote, larowlan, splatio,
 kim.pepper, alexpott, Crell, tim.plunkett: Convert drupal_get_breadcrumb()
 and drupal_set_breadcrumb() to a service.

---
 core/core.services.yml                        |   2 +
 core/includes/common.inc                      |  18 +--
 core/includes/theme.inc                       |   2 +-
 .../Breadcrumb/BreadcrumbBuilderInterface.php |  27 ++++
 .../Core/Breadcrumb/BreadcrumbManager.php     |  92 +++++++++++++
 core/lib/Drupal/Core/CoreBundle.php           |   4 +
 .../RegisterBreadcrumbBuilderPass.php         |  33 +++++
 core/modules/forum/forum.module               |  23 ----
 core/modules/forum/forum.pages.inc            |  16 ---
 core/modules/forum/forum.services.yml         |   6 +
 .../Drupal/forum/ForumBreadcrumbBuilder.php   | 121 ++++++++++++++++
 .../menu_link/MenuLinkBreadcrumbBuilder.php   |  26 ++++
 core/modules/menu_link/menu_link.services.yml |   5 +
 .../Drupal/system/LegacyBreadcrumbBuilder.php |  37 +++++
 .../Plugin/Block/SystemBreadcrumbBlock.php    |  41 ++++++
 .../Drupal/system/Tests/Menu/MenuTestBase.php | 130 +++++++++++-------
 core/modules/system/system.services.yml       |   4 +
 .../views/lib/Drupal/views/ViewExecutable.php |   4 +-
 .../config/block.block.bartik.breadcrumbs.yml |  22 +++
 .../config/block.block.seven.breadcrumbs.yml  |  22 +++
 20 files changed, 530 insertions(+), 105 deletions(-)
 create mode 100644 core/lib/Drupal/Core/Breadcrumb/BreadcrumbBuilderInterface.php
 create mode 100644 core/lib/Drupal/Core/Breadcrumb/BreadcrumbManager.php
 create mode 100644 core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterBreadcrumbBuilderPass.php
 create mode 100644 core/modules/forum/forum.services.yml
 create mode 100644 core/modules/forum/lib/Drupal/forum/ForumBreadcrumbBuilder.php
 create mode 100644 core/modules/menu_link/lib/Drupal/menu_link/MenuLinkBreadcrumbBuilder.php
 create mode 100644 core/modules/menu_link/menu_link.services.yml
 create mode 100644 core/modules/system/lib/Drupal/system/LegacyBreadcrumbBuilder.php
 create mode 100644 core/modules/system/lib/Drupal/system/Plugin/Block/SystemBreadcrumbBlock.php
 create mode 100644 core/profiles/standard/config/block.block.bartik.breadcrumbs.yml
 create mode 100644 core/profiles/standard/config/block.block.seven.breadcrumbs.yml

diff --git a/core/core.services.yml b/core/core.services.yml
index c4d1c47b93d2..db46b5753f7e 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -441,6 +441,8 @@ services:
     class: Drupal\system\Plugin\ImageToolkitInterface
     factory_method: getDefaultToolkit
     factory_service: image.toolkit.manager
+  breadcrumb:
+    class: Drupal\Core\Breadcrumb\BreadcrumbManager
   token:
     class: Drupal\Core\Utility\Token
     arguments: ['@module_handler']
diff --git a/core/includes/common.inc b/core/includes/common.inc
index 667807a831e3..78debc742b9a 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -288,6 +288,11 @@ function drupal_get_profile() {
  * @param $breadcrumb
  *   Array of links, starting with "home" and proceeding up to but not including
  *   the current page.
+ *
+ * @deprecated This will be removed in 8.0. Instead, register a new breadcrumb
+ *   builder service.
+ *
+ * @see Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface
  */
 function drupal_set_breadcrumb($breadcrumb = NULL) {
   $stored_breadcrumb = &drupal_static(__FUNCTION__);
@@ -298,19 +303,6 @@ function drupal_set_breadcrumb($breadcrumb = NULL) {
   return $stored_breadcrumb;
 }
 
-/**
- * Gets the breadcrumb trail for the current page.
- */
-function drupal_get_breadcrumb() {
-  $breadcrumb = drupal_set_breadcrumb();
-
-  if (!isset($breadcrumb)) {
-    $breadcrumb = menu_get_active_breadcrumb();
-  }
-
-  return $breadcrumb;
-}
-
 /**
  * Adds output to the HEAD tag of the HTML page.
  *
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 228065ac3433..741fe41f6850 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -2840,7 +2840,7 @@ function template_process_page(&$variables) {
     // @see menu_tree_page_data()
     $variables['breadcrumb'] = array(
       '#theme' => 'breadcrumb',
-      '#breadcrumb' => drupal_get_breadcrumb(),
+      '#breadcrumb' => \Drupal::service('breadcrumb')->build(\Drupal::service('request')->attributes->all()),
     );
   }
   if (!isset($variables['title'])) {
diff --git a/core/lib/Drupal/Core/Breadcrumb/BreadcrumbBuilderInterface.php b/core/lib/Drupal/Core/Breadcrumb/BreadcrumbBuilderInterface.php
new file mode 100644
index 000000000000..1fae4c6417d3
--- /dev/null
+++ b/core/lib/Drupal/Core/Breadcrumb/BreadcrumbBuilderInterface.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface.
+ */
+
+namespace Drupal\Core\Breadcrumb;
+
+/**
+ * Defines an interface for classes that build breadcrumbs.
+ */
+interface BreadcrumbBuilderInterface {
+
+  /**
+   * Builds the breadcrumb.
+   *
+   * @param array $attributes
+   *   Attributes representing the current page.
+   *
+   * @return array|null
+   *   A render array for the breadcrumbs or NULL to let other builders decide.
+   *   Returning empty array will suppress all breadcrumbs.
+   */
+  public function build(array $attributes);
+
+}
diff --git a/core/lib/Drupal/Core/Breadcrumb/BreadcrumbManager.php b/core/lib/Drupal/Core/Breadcrumb/BreadcrumbManager.php
new file mode 100644
index 000000000000..5396e9ee9acb
--- /dev/null
+++ b/core/lib/Drupal/Core/Breadcrumb/BreadcrumbManager.php
@@ -0,0 +1,92 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Breadcrumb\BreadcrumbManager.
+ */
+
+namespace Drupal\Core\Breadcrumb;
+
+/**
+ * Provides a breadcrumb manager.
+ *
+ * Holds an array of path processor objects and uses them to sequentially process
+ * a path, in order of processor priority.
+ */
+class BreadcrumbManager implements BreadcrumbBuilderInterface {
+
+  /**
+   * Holds arrays of breadcrumb builders, keyed by priority.
+   *
+   * @var array
+   */
+  protected $builders = array();
+
+  /**
+   * Holds the array of breadcrumb builders sorted by priority.
+   *
+   * Set to NULL if the array needs to be re-calculated.
+   *
+   * @var array|NULL
+   */
+  protected $sortedBuilders;
+
+  /**
+   * Adds another breadcrumb builder.
+   *
+   * @param \Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface $builder
+   *   The breadcrumb builder to add.
+   * @param int $priority
+   *   Priority of the breadcrumb builder.
+   */
+  public function addBuilder(BreadcrumbBuilderInterface $builder, $priority) {
+    $this->builders[$priority][] = $builder;
+    // Force the builders to be re-sorted.
+    $this->sortedBuilders = NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build(array $attributes) {
+    // Call the build method of registered breadcrumb builders,
+    // until one of them returns an array.
+    foreach ($this->getSortedBuilders() as $builder) {
+      $breadcrumb = $builder->build($attributes);
+      if (!isset($breadcrumb)) {
+        // The builder returned NULL, so we continue with the other builders.
+        continue;
+      }
+      elseif (is_array($breadcrumb)) {
+        // The builder returned an array of breadcrumb links.
+        return $breadcrumb;
+      }
+      else {
+        throw new \UnexpectedValueException(format_string('Invalid breadcrumb returned by !class::build().', array('!class' => get_class($builder))));
+      }
+    }
+
+    // Fall back to an empty breadcrumb.
+    return array();
+  }
+
+  /**
+   * Returns the sorted array of breadcrumb builders.
+   *
+   * @return array
+   *   An array of breadcrumb builder objects.
+   */
+  protected function getSortedBuilders() {
+    if (!isset($this->sortedBuilders)) {
+      // Sort the builders according to priority.
+      krsort($this->builders);
+      // Merge nested builders from $this->builders into $this->sortedBuilders.
+      $this->sortedBuilders = array();
+      foreach ($this->builders as $builders) {
+        $this->sortedBuilders = array_merge($this->sortedBuilders, $builders);
+      }
+    }
+    return $this->sortedBuilders;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/CoreBundle.php b/core/lib/Drupal/Core/CoreBundle.php
index 472105a0ae43..9af0c74400d6 100644
--- a/core/lib/Drupal/Core/CoreBundle.php
+++ b/core/lib/Drupal/Core/CoreBundle.php
@@ -17,6 +17,7 @@
 use Drupal\Core\DependencyInjection\Compiler\RegisterParamConvertersPass;
 use Drupal\Core\DependencyInjection\Compiler\RegisterServicesForDestructionPass;
 use Drupal\Core\DependencyInjection\Compiler\RegisterStringTranslatorsPass;
+use Drupal\Core\DependencyInjection\Compiler\RegisterBreadcrumbBuilderPass;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\DependencyInjection\Reference;
@@ -63,6 +64,9 @@ public function build(ContainerBuilder $container) {
     $container->addCompilerPass(new ListCacheBinsPass());
     // Add the compiler pass for appending string translators.
     $container->addCompilerPass(new RegisterStringTranslatorsPass());
+    // Add the compiler pass that will process the tagged breadcrumb builder
+    // services.
+    $container->addCompilerPass(new RegisterBreadcrumbBuilderPass());
   }
 
   /**
diff --git a/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterBreadcrumbBuilderPass.php b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterBreadcrumbBuilderPass.php
new file mode 100644
index 000000000000..1119989a0647
--- /dev/null
+++ b/core/lib/Drupal/Core/DependencyInjection/Compiler/RegisterBreadcrumbBuilderPass.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\DependencyInjection\Compiler\RegisterBreadcrumbBuilderPass.
+ */
+
+namespace Drupal\Core\DependencyInjection\Compiler;
+
+use Symfony\Component\DependencyInjection\Reference;
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
+
+/**
+ * Adds services to the breadcrumb_builder service.
+ */
+class RegisterBreadcrumbBuilderPass implements CompilerPassInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function process(ContainerBuilder $container) {
+    if (!$container->hasDefinition('breadcrumb')) {
+      return;
+    }
+    $manager = $container->getDefinition('breadcrumb');
+    foreach ($container->findTaggedServiceIds('breadcrumb_builder') as $id => $attributes) {
+      $priority = isset($attributes[0]['priority']) ? $attributes[0]['priority'] : 0;
+      $manager->addMethodCall('addBuilder', array(new Reference($id), $priority));
+    }
+  }
+
+}
diff --git a/core/modules/forum/forum.module b/core/modules/forum/forum.module
index 7cc950fc1f5e..36fffda2cc49 100644
--- a/core/modules/forum/forum.module
+++ b/core/modules/forum/forum.module
@@ -261,29 +261,6 @@ function _forum_node_check_node_type(EntityInterface $node) {
   return !empty($instance);
 }
 
-/**
- * Implements hook_node_view().
- */
-function forum_node_view(EntityInterface $node, EntityDisplay $display, $view_mode) {
-  $vid = config('forum.settings')->get('vocabulary');
-  $vocabulary = taxonomy_vocabulary_load($vid);
-  if (_forum_node_check_node_type($node)) {
-    if ($view_mode == 'full' && node_is_page($node)) {
-      // Breadcrumb navigation
-      $breadcrumb[] = l(t('Home'), NULL);
-      $breadcrumb[] = l($vocabulary->name, 'forum');
-      if ($parents = taxonomy_term_load_parents_all($node->forum_tid)) {
-        $parents = array_reverse($parents);
-        foreach ($parents as $parent) {
-          $breadcrumb[] = l($parent->label(), 'forum/' . $parent->id());
-        }
-      }
-      drupal_set_breadcrumb($breadcrumb);
-
-    }
-  }
-}
-
 /**
  * Implements hook_node_validate().
  *
diff --git a/core/modules/forum/forum.pages.inc b/core/modules/forum/forum.pages.inc
index 67b7f214b1a6..6278bf3d1a17 100644
--- a/core/modules/forum/forum.pages.inc
+++ b/core/modules/forum/forum.pages.inc
@@ -28,22 +28,6 @@ function forum_page($forum_term = NULL) {
     drupal_set_title($vocabulary->label());
   }
 
-  // Breadcrumb navigation.
-  $breadcrumb[] = l(t('Home'), NULL);
-  if ($forum_term->id()) {
-    // Parent of all forums is the vocabulary name.
-    $breadcrumb[] = l($vocabulary->label(), 'forum');
-  }
-  // Add all parent forums to breadcrumbs.
-  if ($forum_term->parents) {
-    foreach (array_reverse($forum_term->parents) as $parent) {
-      if ($parent->id() != $forum_term->id()) {
-        $breadcrumb[] = l($parent->label(), 'forum/' . $parent->id());
-      }
-    }
-  }
-  drupal_set_breadcrumb($breadcrumb);
-
   if ($forum_term->id() && array_search($forum_term->id(), $config->get('containers')) === FALSE) {
     // Add RSS feed for forums.
     drupal_add_feed('taxonomy/term/' . $forum_term->id() . '/feed', 'RSS - ' . $forum_term->label());
diff --git a/core/modules/forum/forum.services.yml b/core/modules/forum/forum.services.yml
new file mode 100644
index 000000000000..19033e856cfe
--- /dev/null
+++ b/core/modules/forum/forum.services.yml
@@ -0,0 +1,6 @@
+services:
+  forum.breadcrumb:
+    class: Drupal\forum\ForumBreadcrumbBuilder
+    arguments: ['@plugin.manager.entity', '@config.factory']
+    tags:
+      - { name: breadcrumb_builder, priority: 1001 }
diff --git a/core/modules/forum/lib/Drupal/forum/ForumBreadcrumbBuilder.php b/core/modules/forum/lib/Drupal/forum/ForumBreadcrumbBuilder.php
new file mode 100644
index 000000000000..3bbb0df01b1f
--- /dev/null
+++ b/core/modules/forum/lib/Drupal/forum/ForumBreadcrumbBuilder.php
@@ -0,0 +1,121 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\forum\ForumBreadcrumbBuilder.
+ */
+
+namespace Drupal\forum;
+
+use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
+use Drupal\Core\Config\ConfigFactory;
+use Drupal\Core\Entity\EntityManager;
+
+/**
+ * Class to define the forum breadcrumb builder.
+ */
+class ForumBreadcrumbBuilder implements BreadcrumbBuilderInterface {
+
+  /**
+   * Configuration object for this builder.
+   *
+   * @var \Drupal\Core\Config\Config
+   */
+  protected $config;
+
+  /**
+   * Stores the Entity manager.
+   *
+   * @var \Drupal\Core\Entity\EntityManager
+   */
+  protected $entityManager;
+
+  /**
+   * Constructs a new ForumBreadcrumbBuilder.
+   *
+   * @param \Drupal\Core\Entity\EntityManager
+   *   The entity manager.
+   * @param \Drupal\Core\Config\ConfigFactory $configFactory
+   *   The configuration factory.
+   */
+  public function __construct(EntityManager $entity_manager, ConfigFactory $configFactory) {
+    $this->entityManager = $entity_manager;
+    $this->config = $configFactory->get('forum.settings');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build(array $attributes) {
+
+    // @todo This only works for legacy routes. Once node/% and forum/% are
+    //   converted to the new router this code will need to be updated.
+    if (isset($attributes['drupal_menu_item'])) {
+      $item = $attributes['drupal_menu_item'];
+      switch ($item['path']) {
+
+        case 'node/%':
+          $node = $item['map'][1];
+          // Load the object in case of missing wildcard loaders.
+          $node = is_object($node) ? $node : node_load($node);
+          if (_forum_node_check_node_type($node)) {
+            $breadcrumb = $this->forumPostBreadcrumb($node);
+          }
+          break;
+
+        case 'forum/%':
+          $term = $item['map'][1];
+          // Load the object in case of missing wildcard loaders.
+          $term = is_object($term) ? $term : forum_forum_load($term);
+          $breadcrumb = $this->forumTermBreadcrumb($term);
+          break;
+      }
+    }
+
+    if (!empty($breadcrumb)) {
+      return $breadcrumb;
+    }
+  }
+
+  /**
+   * Builds the breadcrumb for a forum post page.
+   */
+  protected function forumPostBreadcrumb($node) {
+    $vocabularies = $this->entityManager->getStorageController('taxonomy_vocabulary')->load(array($this->config->get('vocabulary')));
+    $vocabulary = reset($vocabularies);
+
+    $breadcrumb[] = l(t('Home'), NULL);
+    $breadcrumb[] = l($vocabulary->label(), 'forum');
+    if ($parents = taxonomy_term_load_parents_all($node->forum_tid)) {
+      $parents = array_reverse($parents);
+      foreach ($parents as $parent) {
+        $breadcrumb[] = l($parent->label(), 'forum/' . $parent->id());
+      }
+    }
+    return $breadcrumb;
+  }
+
+  /**
+   * Builds the breadcrumb for a forum term page.
+   */
+  protected function forumTermBreadcrumb($term) {
+    $vocabularies = $this->entityManager->getStorageController('taxonomy_vocabulary')->load(array($this->config->get('vocabulary')));
+    $vocabulary = current($vocabularies);
+
+    $breadcrumb[] = l(t('Home'), NULL);
+    if ($term->tid) {
+      // Parent of all forums is the vocabulary name.
+      $breadcrumb[] = l($vocabulary->label(), 'forum');
+    }
+    // Add all parent forums to breadcrumbs.
+    if ($term->parents) {
+      foreach (array_reverse($term->parents) as $parent) {
+        if ($parent->id() != $term->id()) {
+          $breadcrumb[] = l($parent->label(), 'forum/' . $parent->id());
+        }
+      }
+    }
+    return $breadcrumb;
+  }
+
+}
diff --git a/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkBreadcrumbBuilder.php b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkBreadcrumbBuilder.php
new file mode 100644
index 000000000000..acece08000d8
--- /dev/null
+++ b/core/modules/menu_link/lib/Drupal/menu_link/MenuLinkBreadcrumbBuilder.php
@@ -0,0 +1,26 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_link\MenuLinkBreadcrumbBuilder.
+ */
+
+namespace Drupal\menu_link;
+
+use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
+
+/**
+ * Class to define the menu_link breadcrumb builder.
+ */
+class MenuLinkBreadcrumbBuilder implements BreadcrumbBuilderInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build(array $attributes) {
+    // @todo Rewrite the implementation.
+    // Currently the result always array.
+    return menu_get_active_breadcrumb();
+  }
+
+}
diff --git a/core/modules/menu_link/menu_link.services.yml b/core/modules/menu_link/menu_link.services.yml
new file mode 100644
index 000000000000..a428476c4c7d
--- /dev/null
+++ b/core/modules/menu_link/menu_link.services.yml
@@ -0,0 +1,5 @@
+services:
+  menu_link.breadcrumb:
+    class: Drupal\menu_link\MenuLinkBreadcrumbBuilder
+    tags:
+      - { name: breadcrumb_builder, priority: 0 }
diff --git a/core/modules/system/lib/Drupal/system/LegacyBreadcrumbBuilder.php b/core/modules/system/lib/Drupal/system/LegacyBreadcrumbBuilder.php
new file mode 100644
index 000000000000..3e5cc7137f96
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/LegacyBreadcrumbBuilder.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\LegacyBreadcrumbBuilder.
+ */
+
+namespace Drupal\system;
+
+use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
+
+/**
+ * Class to define the legacy breadcrumb builder.
+ *
+ * @deprecated This will be removed in 8.0. Instead, register a new breadcrumb
+ *   builder service.
+ *
+ * @see \Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface
+ *
+ * This breadcrumb builder implements legacy support for the
+ * drupal_set_breadcrumb() mechanic.
+ * Remove this once drupal_set_breadcrumb() has been eliminated.
+ */
+class LegacyBreadcrumbBuilder implements BreadcrumbBuilderInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build(array $attributes) {
+    $breadcrumb = drupal_set_breadcrumb();
+    if (is_array($breadcrumb)) {
+      // $breadcrumb is expected to be an array of rendered breadcrumb links.
+      return $breadcrumb;
+    }
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Plugin/Block/SystemBreadcrumbBlock.php b/core/modules/system/lib/Drupal/system/Plugin/Block/SystemBreadcrumbBlock.php
new file mode 100644
index 000000000000..cb0b9086111e
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Plugin/Block/SystemBreadcrumbBlock.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Plugin\Block\SystemBreadcrumbBlock.
+ */
+
+namespace Drupal\system\Plugin\Block;
+
+use Drupal\block\BlockBase;
+use Drupal\Component\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * Provides a block to display the breadcrumbs.
+ *
+ * @Plugin(
+ *   id = "system_breadcrumb_block",
+ *   admin_label = @Translation("Breadcrumbs"),
+ *   module = "system"
+ * )
+ */
+class SystemBreadcrumbBlock extends BlockBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function build() {
+    $breadcrumb_manager = \Drupal::service('breadcrumb');
+    $request = \Drupal::service('request');
+    $breadcrumb = $breadcrumb_manager->build($request->attributes->all());
+    if (!empty($breadcrumb)) {
+      // $breadcrumb is expected to be an array of rendered breadcrumb links.
+      return array(
+        '#theme' => 'breadcrumb',
+        '#breadcrumb' => $breadcrumb,
+      );
+    }
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Menu/MenuTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Menu/MenuTestBase.php
index eb7fbafffb74..7d23727d564a 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Menu/MenuTestBase.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Menu/MenuTestBase.php
@@ -10,6 +10,7 @@
 use Drupal\simpletest\WebTestBase;
 
 abstract class MenuTestBase extends WebTestBase {
+
   /**
    * Assert that a given path shows certain breadcrumb links.
    *
@@ -34,13 +35,37 @@ protected function assertBreadcrumb($goto, array $trail, $page_title = NULL, arr
     if (isset($goto)) {
       $this->drupalGet($goto);
     }
+    $this->assertBreadcrumbParts($trail);
+
+    // Additionally assert page title, if given.
+    if (isset($page_title)) {
+      $this->assertTitle(strtr('@title | Drupal', array('@title' => $page_title)));
+    }
+
+    // Additionally assert active trail in a menu tree output, if given.
+    if ($tree) {
+      $this->assertMenuActiveTrail($tree, $last_active);
+    }
+  }
+
+  /**
+   * Assert that a trail exists in the internal browser.
+   *
+   * @param array $trail
+   *   An associative array whose keys are expected breadcrumb link paths and
+   *   whose values are expected breadcrumb link texts (not sanitized).
+   */
+  protected function assertBreadcrumbParts($trail) {
     // Compare paths with actual breadcrumb.
-    $parts = $this->getParts();
+    $parts = $this->getBreadcrumbParts();
     $pass = TRUE;
-    foreach ($trail as $path => $title) {
-      $url = url($path);
-      $part = array_shift($parts);
-      $pass = ($pass && $part['href'] === $url && $part['text'] === check_plain($title));
+    // There may be more than one breadcrumb on the page.
+    while (!empty($parts)) {
+      foreach ($trail as $path => $title) {
+        $url = url($path);
+        $part = array_shift($parts);
+        $pass = ($pass && $part['href'] === $url && $part['text'] === check_plain($title));
+      }
     }
     // No parts must be left, or an expected "Home" will always pass.
     $pass = ($pass && empty($parts));
@@ -49,60 +74,65 @@ protected function assertBreadcrumb($goto, array $trail, $page_title = NULL, arr
       '%parts' => implode(' » ', $trail),
       '@path' => $this->getUrl(),
     )));
+  }
 
-    // Additionally assert page title, if given.
-    if (isset($page_title)) {
-      $this->assertTitle(strtr('@title | Drupal', array('@title' => $page_title)));
-    }
-
-    // Additionally assert active trail in a menu tree output, if given.
+  /**
+   * Assert that active trail exists in a menu tree output.
+   *
+   * @param array $tree
+   *   An associative array whose keys are link paths and whose
+   *   values are link titles (not sanitized) of an expected active trail in a
+   *   menu tree output on the page.
+   * @param bool $last_active
+   *   Whether the last link in $tree is expected to be active (TRUE)
+   *   or just to be in the active trail (FALSE).
+   */
+  protected function assertMenuActiveTrail($tree, $last_active) {
+    end($tree);
+    $active_link_path = key($tree);
+    $active_link_title = array_pop($tree);
+    $xpath = '';
     if ($tree) {
-      end($tree);
-      $active_link_path = key($tree);
-      $active_link_title = array_pop($tree);
-      $xpath = '';
-      if ($tree) {
-        $i = 0;
-        foreach ($tree as $link_path => $link_title) {
-          $part_xpath = (!$i ? '//' : '/following-sibling::ul/descendant::');
-          $part_xpath .= 'li[contains(@class, :class)]/a[contains(@href, :href) and contains(text(), :title)]';
-          $part_args = array(
-            ':class' => 'active-trail',
-            ':href' => url($link_path),
-            ':title' => $link_title,
-          );
-          $xpath .= $this->buildXPathQuery($part_xpath, $part_args);
-          $i++;
-        }
-        $elements = $this->xpath($xpath);
-        $this->assertTrue(!empty($elements), 'Active trail to current page was found in menu tree.');
-
-        // Append prefix for active link asserted below.
-        $xpath .= '/following-sibling::ul/descendant::';
-      }
-      else {
-        $xpath .= '//';
+      $i = 0;
+      foreach ($tree as $link_path => $link_title) {
+        $part_xpath = (!$i ? '//' : '/following-sibling::ul/descendant::');
+        $part_xpath .= 'li[contains(@class, :class)]/a[contains(@href, :href) and contains(text(), :title)]';
+        $part_args = array(
+          ':class' => 'active-trail',
+          ':href' => url($link_path),
+          ':title' => $link_title,
+        );
+        $xpath .= $this->buildXPathQuery($part_xpath, $part_args);
+        $i++;
       }
-      $xpath_last_active = ($last_active ? 'and contains(@class, :class-active)' : '');
-      $xpath .= 'li[contains(@class, :class-trail)]/a[contains(@href, :href) ' . $xpath_last_active . 'and contains(text(), :title)]';
-      $args = array(
-        ':class-trail' => 'active-trail',
-        ':class-active' => 'active',
-        ':href' => url($active_link_path),
-        ':title' => $active_link_title,
-      );
-      $elements = $this->xpath($xpath, $args);
-      $this->assertTrue(!empty($elements), format_string('Active link %title was found in menu tree, including active trail links %tree.', array(
-        '%title' => $active_link_title,
-        '%tree' => implode(' » ', $tree),
-      )));
+      $elements = $this->xpath($xpath);
+      $this->assertTrue(!empty($elements), 'Active trail to current page was found in menu tree.');
+
+      // Append prefix for active link asserted below.
+      $xpath .= '/following-sibling::ul/descendant::';
     }
+    else {
+      $xpath .= '//';
+    }
+    $xpath_last_active = ($last_active ? 'and contains(@class, :class-active)' : '');
+    $xpath .= 'li[contains(@class, :class-trail)]/a[contains(@href, :href) ' . $xpath_last_active . 'and contains(text(), :title)]';
+    $args = array(
+      ':class-trail' => 'active-trail',
+      ':class-active' => 'active',
+      ':href' => url($active_link_path),
+      ':title' => $active_link_title,
+    );
+    $elements = $this->xpath($xpath, $args);
+    $this->assertTrue(!empty($elements), format_string('Active link %title was found in menu tree, including active trail links %tree.', array(
+      '%title' => $active_link_title,
+      '%tree' => implode(' » ', $tree),
+    )));
   }
 
   /**
    * Returns the breadcrumb contents of the current page in the internal browser.
    */
-  protected function getParts() {
+  protected function getBreadcrumbParts() {
     $parts = array();
     $elements = $this->xpath('//nav[@class="breadcrumb"]/ol/li/a');
     if (!empty($elements)) {
diff --git a/core/modules/system/system.services.yml b/core/modules/system/system.services.yml
index 8f0d232de165..00e7821dc5a7 100644
--- a/core/modules/system/system.services.yml
+++ b/core/modules/system/system.services.yml
@@ -9,3 +9,7 @@ services:
   system.manager:
     class: Drupal\system\SystemManager
     arguments: ['@module_handler', '@database']
+  system.breadcrumb.legacy:
+    class: Drupal\system\LegacyBreadcrumbBuilder
+    tags:
+      - {name: breadcrumb_builder, priority: 500}
diff --git a/core/modules/views/lib/Drupal/views/ViewExecutable.php b/core/modules/views/lib/Drupal/views/ViewExecutable.php
index 76acf8a95e73..6176dc0f2694 100644
--- a/core/modules/views/lib/Drupal/views/ViewExecutable.php
+++ b/core/modules/views/lib/Drupal/views/ViewExecutable.php
@@ -1670,8 +1670,8 @@ public function getBreadcrumb($set = FALSE) {
       }
 
       if ($set) {
-        if ($base) {
-          $breadcrumb = array_merge(drupal_get_breadcrumb(), $breadcrumb);
+        if ($base && $current_breadcrumbs = drupal_set_breadcrumb()) {
+          $breadcrumb = array_merge($current_breadcrumbs, $breadcrumb);
         }
         drupal_set_breadcrumb($breadcrumb);
       }
diff --git a/core/profiles/standard/config/block.block.bartik.breadcrumbs.yml b/core/profiles/standard/config/block.block.bartik.breadcrumbs.yml
new file mode 100644
index 000000000000..db92b966d05e
--- /dev/null
+++ b/core/profiles/standard/config/block.block.bartik.breadcrumbs.yml
@@ -0,0 +1,22 @@
+id: bartik.breadcrumbs
+weight: '-5'
+status: '0'
+langcode: en
+region: '-1'
+plugin: system_breadcrumb_block
+settings:
+  label: Breadcrumbs
+  module: system
+  label_display: '0'
+  cache: '-1'
+visibility:
+  path:
+    visibility: '0'
+    pages: ''
+  role:
+    roles: {  }
+  node_type:
+    types:
+      article: '0'
+      page: '0'
+  visibility__active_tab: edit-visibility-path
diff --git a/core/profiles/standard/config/block.block.seven.breadcrumbs.yml b/core/profiles/standard/config/block.block.seven.breadcrumbs.yml
new file mode 100644
index 000000000000..6ca89621b838
--- /dev/null
+++ b/core/profiles/standard/config/block.block.seven.breadcrumbs.yml
@@ -0,0 +1,22 @@
+id: seven.breadcrumbs
+weight: '-2'
+status: '0'
+langcode: en
+region: '-1'
+plugin: system_breadcrumb_block
+settings:
+  label: Breadcrumbs
+  module: system
+  label_display: '0'
+  cache: '-1'
+visibility:
+  path:
+    visibility: '0'
+    pages: ''
+  role:
+    roles: {  }
+  node_type:
+    types:
+      article: '0'
+      page: '0'
+  visibility__active_tab: edit-visibility-path
-- 
GitLab