Commit 9917b07b authored by alexpott's avatar alexpott

Issue #1799820 by jhedstrom, Floydm: Breadcrumb doesn't get localized when displaying parent terms

parent a77605d1
<?php
/**
* @file
* Contains \Drupal\system\Tests\Menu\AssertBreadcrumbTrait.
*/
namespace Drupal\system\Tests\Menu;
use Drupal\Component\Utility\String;
/**
* Provides test assertions for verifying breadcrumbs.
*/
trait AssertBreadcrumbTrait {
use AssertMenuActiveTrailTrait;
/**
* Assert that a given path shows certain breadcrumb links.
*
* @param string $goto
* (optional) A system path to pass to
* Drupal\simpletest\WebTestBase::drupalGet().
* @param array $trail
* An associative array whose keys are expected breadcrumb link paths and
* whose values are expected breadcrumb link texts (not sanitized).
* @param string $page_title
* (optional) A page title to additionally assert via
* Drupal\simpletest\WebTestBase::assertTitle(). Without site name suffix.
* @param array $tree
* (optional) 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 $last_active
* (optional) Whether the last link in $tree is expected to be active (TRUE)
* or just to be in the active trail (FALSE).
*/
protected function assertBreadcrumb($goto, array $trail, $page_title = NULL, array $tree = array(), $last_active = TRUE) {
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->getBreadcrumbParts();
$pass = TRUE;
// There may be more than one breadcrumb on the page. If $trail is empty
// this test would go into an infinite loop, so we need to check that too.
while ($trail && !empty($parts)) {
foreach ($trail as $path => $title) {
$url = _url($path);
$part = array_shift($parts);
$pass = ($pass && $part['href'] === $url && $part['text'] === String::checkPlain($title));
}
}
// No parts must be left, or an expected "Home" will always pass.
$pass = ($pass && empty($parts));
$this->assertTrue($pass, format_string('Breadcrumb %parts found on @path.', array(
'%parts' => implode(' » ', $trail),
'@path' => $this->getUrl(),
)));
}
/**
* Returns the breadcrumb contents of the current page in the internal browser.
*/
protected function getBreadcrumbParts() {
$parts = array();
$elements = $this->xpath('//nav[@class="breadcrumb"]/ol/li/a');
if (!empty($elements)) {
foreach ($elements as $element) {
$parts[] = array(
'text' => (string) $element,
'href' => (string) $element['href'],
'title' => (string) $element['title'],
);
}
}
return $parts;
}
}
<?php
/**
* @file
* Contains \Drupal\system\Tests\Menu\AssertMenuActiveTrailTrait.
*/
namespace Drupal\system\Tests\Menu;
/**
* Provides test assertions for verifying the active menu trail.
*/
trait AssertMenuActiveTrailTrait {
/**
* 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) {
$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 .= '//';
}
$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),
)));
}
}
......@@ -7,145 +7,10 @@
namespace Drupal\system\Tests\Menu;
use Drupal\Component\Utility\String;
use Drupal\simpletest\WebTestBase;
abstract class MenuTestBase extends WebTestBase {
/**
* Assert that a given path shows certain breadcrumb links.
*
* @param string $goto
* (optional) A system path to pass to
* Drupal\simpletest\WebTestBase::drupalGet().
* @param array $trail
* An associative array whose keys are expected breadcrumb link paths and
* whose values are expected breadcrumb link texts (not sanitized).
* @param string $page_title
* (optional) A page title to additionally assert via
* Drupal\simpletest\WebTestBase::assertTitle(). Without site name suffix.
* @param array $tree
* (optional) 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 $last_active
* (optional) Whether the last link in $tree is expected to be active (TRUE)
* or just to be in the active trail (FALSE).
*/
protected function assertBreadcrumb($goto, array $trail, $page_title = NULL, array $tree = array(), $last_active = TRUE) {
if (isset($goto)) {
$this->drupalGet($goto);
}
$this->assertBreadcrumbParts($trail);
use AssertBreadcrumbTrait;
// 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->getBreadcrumbParts();
$pass = TRUE;
// There may be more than one breadcrumb on the page. If $trail is empty
// this test would go into an infinite loop, so we need to check that too.
while ($trail && !empty($parts)) {
foreach ($trail as $path => $title) {
$url = _url($path);
$part = array_shift($parts);
$pass = ($pass && $part['href'] === $url && $part['text'] === String::checkPlain($title));
}
}
// No parts must be left, or an expected "Home" will always pass.
$pass = ($pass && empty($parts));
$this->assertTrue($pass, format_string('Breadcrumb %parts found on @path.', array(
'%parts' => implode(' » ', $trail),
'@path' => $this->getUrl(),
)));
}
/**
* 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) {
$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 .= '//';
}
$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 getBreadcrumbParts() {
$parts = array();
$elements = $this->xpath('//nav[@class="breadcrumb"]/ol/li/a');
if (!empty($elements)) {
foreach ($elements as $element) {
$parts[] = array(
'text' => (string) $element,
'href' => (string) $element['href'],
'title' => (string) $element['title'],
);
}
}
return $parts;
}
}
......@@ -8,6 +8,7 @@
namespace Drupal\taxonomy;
use Drupal\Core\Breadcrumb\BreadcrumbBuilderInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Link;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
......@@ -18,6 +19,31 @@
class TermBreadcrumbBuilder implements BreadcrumbBuilderInterface {
use StringTranslationTrait;
/**
* The entity manager.
*
* @var \Drupal\Core\Entity\EntityManagerInterface
*/
protected $entityManager;
/**
* The taxonomy storage.
*
* @var \Drupal\Core\Entity\EntityStorageInterface
*/
protected $termStorage;
/**
* Constructs the TermBreadcrumbBuilder.
*
* @param \Drupal\Core\Entity\EntityManagerInterface $entityManager
* The entity manager.
*/
public function __construct(EntityManagerInterface $entityManager) {
$this->entityManager = $entityManager;
$this->termStorage = $entityManager->getStorage('taxonomy_term');
}
/**
* {@inheritdoc}
*/
......@@ -35,8 +61,9 @@ public function build(RouteMatchInterface $route_match) {
// hard-coded presumption. Make this behavior configurable per
// vocabulary or term.
$breadcrumb = array();
while ($parents = taxonomy_term_load_parents($term->id())) {
while ($parents = $this->termStorage->loadParents($term->id())) {
$term = array_shift($parents);
$term = $this->entityManager->getTranslationFromContext($term);
$breadcrumb[] = Link::createFromRoute($term->getName(), 'entity.taxonomy_term.canonical', array('taxonomy_term' => $term->id()));
}
$breadcrumb[] = Link::createFromRoute($this->t('Home'), '<front>');
......
<?php
/**
* @file
* Contains \Drupal\taxonomy\Tests\TaxonomyTranslationTestTrait.
*/
namespace Drupal\taxonomy\Tests;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\language\Entity\ConfigurableLanguage;
/**
* Provides common testing base for translated taxonomy terms.
*/
trait TaxonomyTranslationTestTrait {
/**
* The vocabulary.
*
* @var \Drupal\taxonomy\Entity\Vocabulary;
*/
protected $vocabulary;
/**
* The field name for our taxonomy term field.
*
* @var string
*/
protected $termFieldName = 'field_tag';
/**
* The langcode of the source language.
*
* @var string
*/
protected $baseLangcode = 'en';
/**
* Target langcode for translation.
*
* @var string
*/
protected $translateToLangcode = 'hu';
/**
* The node to check the translated value on.
*
* @var \Drupal\node\Entity\Node
*/
protected $node;
/**
* Adds additional languages.
*/
protected function setupLanguages() {
ConfigurableLanguage::createFromLangcode($this->translateToLangcode)->save();
}
/**
* Enables translations where it needed.
*/
protected function enableTranslation() {
// Enable translation for the current entity type and ensure the change is
// picked up.
\Drupal::service('content_translation.manager')->setEnabled('node', 'article', TRUE);
\Drupal::service('content_translation.manager')->setEnabled('taxonomy_term', $this->vocabulary->id(), TRUE);
drupal_static_reset();
\Drupal::entityManager()->clearCachedDefinitions();
\Drupal::service('router.builder')->rebuild();
}
/**
* Adds term reference field for the article content type.
*/
protected function setUpTermReferenceField() {
entity_create('field_storage_config', array(
'field_name' => $this->termFieldName,
'entity_type' => 'node',
'type' => 'taxonomy_term_reference',
'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
'translatable' => FALSE,
'settings' => array(
'allowed_values' => array(
array(
'vocabulary' => $this->vocabulary->id(),
'parent' => 0,
),
),
),
))->save();
$field = entity_create('field_config', array(
'field_name' => $this->termFieldName,
'bundle' => 'article',
'entity_type' => 'node',
));
$field->save();
entity_get_form_display('node', 'article', 'default')
->setComponent($this->termFieldName, array(
'type' => 'taxonomy_autocomplete',
))
->save();
entity_get_display('node', 'article', 'default')
->setComponent($this->termFieldName, array(
'type' => 'taxonomy_term_reference_link',
))
->save();
}
}
<?php
/**
* @file
* Contains \Drupal\taxonomy\Tests\TermTranslationBreadcrumbTest.
*/
namespace Drupal\taxonomy\Tests;
use Drupal\system\Tests\Menu\AssertBreadcrumbTrait;
/**
* Tests for proper breadcrumb translation.
*
* @group taxonomy
*/
class TermTranslationBreadcrumbTest extends TaxonomyTestBase {
use AssertBreadcrumbTrait;
use TaxonomyTranslationTestTrait;
/**
* Term to translated term mapping.
*
* @var array
*/
protected $termTranslationMap = array(
'one' => 'translatedOne',
'two' => 'translatedTwo',
'three' => 'translatedThree',
);
/**
* Created terms.
*
* @var \Drupal\taxonomy\Entity\Term[]
*/
protected $terms = array();
/**
* {@inheritdoc}
*/
public static $modules = array('taxonomy', 'language', 'content_translation');
/**
* {@inheritdoc}
*/
protected function setUp() {
parent::setUp();
$this->setupLanguages();
$this->vocabulary = $this->createVocabulary();
$this->enableTranslation();
$this->setUpTerms();
$this->setUpTermReferenceField();
}
/**
* Test translated breadcrumbs.
*/
public function testTranslatedBreadcrumbs() {
// Ensure non-translated breadcrumb is correct.
$breadcrumb = array('' => 'Home');
foreach ($this->terms as $term) {
$breadcrumb[$term->getSystemPath()] = $term->label();
}
// The last item will not be in the breadcrumb.
array_pop($breadcrumb);
// Check the breadcrumb on the leaf term page.
$term = $this->getLeafTerm();
$this->assertBreadcrumb($term->getSystemPath(), $breadcrumb, $term->label());
// Construct the expected translated breadcrumb.
$breadcrumb = array($this->translateToLangcode => 'Home');
foreach ($this->terms as $term) {
$translated = $term->getTranslation($this->translateToLangcode);
$path = $this->translateToLangcode . '/' . $translated->getSystemPath();
$breadcrumb[$path] = $translated->label();
}
array_pop($breadcrumb);
// Check for the translated breadcrumb on the translated leaf term page.
$term = $this->getLeafTerm();
$translated = $term->getTranslation($this->translateToLangcode);
$path = $this->translateToLangcode . '/' . $translated->getSystemPath();
$this->assertBreadcrumb($path, $breadcrumb, $translated->label());
}
/**
* Setup translated terms in a hierarchy.
*/
protected function setUpTerms() {
$parent_vid = 0;
foreach ($this->termTranslationMap as $name => $translation) {
$term = $this->createTerm($this->vocabulary, array(
'name' => $name,
'langcode' => $this->baseLangcode,
'parent' => $parent_vid,
));
$term->addTranslation($this->translateToLangcode, array(
'name' => $translation,
));
$term->save();
// Each term is nested under the last.
$parent_vid = $term->id();
$this->terms[] = $term;
}
}
/**
* Get the final (leaf) term in the hierarchy.
*
* @return \Drupal\taxonomy\Entity\Term
* The final term in the hierarchy.
*/
protected function getLeafTerm() {
return $this->terms[count($this->termTranslationMap) - 1];
}
}
......@@ -7,9 +7,6 @@
namespace Drupal\taxonomy\Tests;
use Drupal\language\Entity\ConfigurableLanguage;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
/**
* Tests the translation of taxonomy terms field on nodes.
*
......@@ -17,12 +14,7 @@
*/
class TermTranslationFieldViewTest extends TaxonomyTestBase {
/**
* The vocabulary.
*
* @var \Drupal\taxonomy\Entity\Vocabulary;
*/
protected $vocabulary;
use TaxonomyTranslationTestTrait;
/**
* The term that should be translated.
......@@ -31,27 +23,6 @@ class TermTranslationFieldViewTest extends TaxonomyTestBase {
*/
protected $term;
/**
* The field name for our taxonomy term field.
*
* @var string
*/
protected $termFieldName = 'field_tag';
/**
* The langcode of the source language.
*
* @var string
*/
protected $baseLangcode = 'en';
/**
* Target langcode for translation.
*
* @var string
*/
protected $translateToLangcode = 'hu';
/**
* The tag in the source language.
*
......@@ -66,13 +37,6 @@ class TermTranslationFieldViewTest extends TaxonomyTestBase {
*/
protected $translatedTagName = 'TranslatedTagName';
/**
* The node to check the translated value on.
*
* @var \Drupal\node\Entity\Node
*/
protected $node;
/**
* Modules to enable.
*
......@@ -105,13 +69,6 @@ public function testTranslatedTaxonomyTermReferenceDisplay() {
$this->assertNoText($this->baseTagName);
}
/**
* Adds additional languages.
*/
protected function setupLanguages() {
ConfigurableLanguage::createFromLangcode($this->translateToLangcode)->save();
}
/**
* Creates a test subject node, with translation.
*/
......@@ -133,19 +90,6 @@ protected function setUpNode() {
$this->node = $node;
}
/**
* Enables translations where it needed.
*/
protected function enableTranslation() {
// Enable translation for the current entity type and ensure the change is
// picked up.
\Drupal::service('content_translation.manager')->setEnabled('node', 'article', TRUE);
\Drupal::service('content_translation.manager')->setEnabled('taxonomy_vocabulary', $this->vocabulary->id(), TRUE);
drupal_static_reset();
\Drupal::entityManager()->clearCachedDefinitions();
\Drupal::service('router.builder')->rebuild();
}
/**
* Creates a test subject term, with translation.
*/
......@@ -161,42 +105,4 @@ protected function setUpTerm() {
$this->term->save();
}
/**
* Adds term reference field for the article content type.
*/
protected function setUpTermReferenceField() {
entity_create('field_storage_config', array(
'field_name' => $this->termFieldName,
'entity_type' => 'node',