diff --git a/core/lib/Drupal/Core/Template/Attribute.php b/core/lib/Drupal/Core/Template/Attribute.php index ead5d05..a20b663 100644 --- a/core/lib/Drupal/Core/Template/Attribute.php +++ b/core/lib/Drupal/Core/Template/Attribute.php @@ -80,8 +80,18 @@ public function offsetSet($name, $value) { * An AttributeValueBase representation of the attribute's value. */ protected function createAttributeValue($name, $value) { - if (is_array($value)) { - $value = new AttributeArray($name, $value); + // If the value is already an AttributeValueBase object, return it + // straight away. + if ($value instanceOf AttributeValueBase) { + return $value; + } + // An array value or 'class' attribute name are forced to always be an + // AttributeArray value for consistency. + if (is_array($value) || $name == 'class') { + // Cast the value to an array if the value was passed in as a string. + // @todo Decide to fix all the broken instances of class as a string + // in core or cast them. + $value = new AttributeArray($name, (array) $value); } elseif (is_bool($value)) { $value = new AttributeBoolean($name, $value); diff --git a/core/lib/Drupal/Core/Template/AttributeArray.php b/core/lib/Drupal/Core/Template/AttributeArray.php index 95e0ef3..eb8b456 100644 --- a/core/lib/Drupal/Core/Template/AttributeArray.php +++ b/core/lib/Drupal/Core/Template/AttributeArray.php @@ -70,6 +70,44 @@ public function __toString() { } /** + * Adds the argument values by merging them on to the value array. + * + * @return $this + */ + public function add() { + $args = func_get_args(); + foreach ($args as $arg) { + // Merge the values passed in from the value array. + // The argument is cast to an array to support comma separated single + // values or one or more array arguments. + $this->value = array_merge($this->value, (array) $arg); + } + + // Filter out any empty values. + $this->value = array_filter($this->value); + return $this; + } + + /** + * Removes the argument values from the value array. + * + * @return $this + */ + public function remove() { + $args = func_get_args(); + foreach ($args as $arg) { + // Remove the values passed in from the value array. + // The argument is cast to an array to support comma separated single + // values or one or more array arguments. + $this->value = array_diff($this->value, (array) $arg); + } + + // Filter out any empty values. + $this->value = array_filter($this->value); + return $this; + } + + /** * Implements IteratorAggregate::getIterator(). */ public function getIterator() { diff --git a/core/lib/Drupal/Core/Template/TwigExtension.php b/core/lib/Drupal/Core/Template/TwigExtension.php index 4cb31ca..7b372f7 100644 --- a/core/lib/Drupal/Core/Template/TwigExtension.php +++ b/core/lib/Drupal/Core/Template/TwigExtension.php @@ -50,6 +50,9 @@ public function getFilters() { // Array filters. new \Twig_SimpleFilter('without', 'twig_without'), + + // CSS class and ID filters. + new \Twig_SimpleFilter('class', 'drupal_html_class'), ); } diff --git a/core/modules/node/node.module b/core/modules/node/node.module index 28c826f..b7870bf 100644 --- a/core/modules/node/node.module +++ b/core/modules/node/node.module @@ -667,25 +667,6 @@ function template_preprocess_node(&$variables) { // Add article ARIA role. $variables['attributes']['role'] = 'article'; - - // Gather node classes. - $variables['attributes']['class'][] = 'node'; - $variables['attributes']['class'][] = drupal_html_class('node--type-' . $node->bundle()); - if ($node->isPromoted()) { - $variables['attributes']['class'][] = 'node--promoted'; - } - if ($node->isSticky()) { - $variables['attributes']['class'][] = 'node--sticky'; - } - if (!$node->isPublished()) { - $variables['attributes']['class'][] = 'node--unpublished'; - } - if ($variables['view_mode']) { - $variables['attributes']['class'][] = drupal_html_class('node--view-mode-' . $variables['view_mode']); - } - if (isset($variables['preview'])) { - $variables['attributes']['class'][] = 'node--preview'; - } } /** diff --git a/core/modules/node/templates/node.html.twig b/core/modules/node/templates/node.html.twig index c80eecb..4294859 100644 --- a/core/modules/node/templates/node.html.twig +++ b/core/modules/node/templates/node.html.twig @@ -77,7 +77,17 @@ * @ingroup themeable */ #} - +{% set classes = [ + 'node', + 'node--type-' ~ node.bundle|class, + node.promoted ? 'node--promoted', + node.sticky ? 'node--sticky', + not node.published ? 'node--unpublished', + preview ? 'node--preview', + 'node--view-mode-' ~ view_mode|class +] +%} +
{{ title_prefix }} {% if not page %} diff --git a/core/modules/system/src/Tests/Theme/TwigFilterTest.php b/core/modules/system/src/Tests/Theme/TwigFilterTest.php index 9b514d7..c132f94 100644 --- a/core/modules/system/src/Tests/Theme/TwigFilterTest.php +++ b/core/modules/system/src/Tests/Theme/TwigFilterTest.php @@ -120,6 +120,10 @@ public function testTwigWithoutFilter() { 'expected' => '
All attributes again.
', 'message' => 'All attributes printed again.', ), + array( + 'expected' => '
ID and class.
', + 'message' => 'Class and ID filtered.', + ), ); foreach ($elements as $element) { diff --git a/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.filter.html.twig b/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.filter.html.twig index 28e5c15..a0979dd 100644 --- a/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.filter.html.twig +++ b/core/modules/system/tests/modules/twig_theme_test/templates/twig_theme_test.filter.html.twig @@ -20,3 +20,4 @@
Without string attribute.
Without either nor class attributes.
All attributes again.
+
diff --git a/core/tests/Drupal/Tests/Core/Template/AttributeTest.php b/core/tests/Drupal/Tests/Core/Template/AttributeTest.php index af5e332..e1a1a98 100644 --- a/core/tests/Drupal/Tests/Core/Template/AttributeTest.php +++ b/core/tests/Drupal/Tests/Core/Template/AttributeTest.php @@ -66,6 +66,56 @@ public function testRemove() { $this->assertFalse(isset($attribute['class'])); } + + /** + * Tests adding class attributes with the AttributeArray helper method. + */ + public function testAddClasses() { + $attribute = new Attribute(array('class' => array('example-class'))); + + // Add one class. + $attribute['class']->add('aa'); + $this->assertEquals(new AttributeArray('class', array('example-class', 'aa')), $attribute['class']); + + // Add multiple classes. + $attribute['class']->add('xx', 'yy'); + $this->assertEquals(new AttributeArray('class', array('example-class', 'aa', 'xx', 'yy')), $attribute['class']); + + // Add an array of classes. + $attribute['class']->add(array('red', 'green', 'blue')); + $this->assertEquals(new AttributeArray('class', array('example-class', 'aa', 'xx', 'yy', 'red', 'green', 'blue')), $attribute['class']); + + } + + /** + * Tests removing class attributes with the AttributeArray helper method. + */ + public function testRemoveClasses() { + $attribute = new Attribute(array('class' => array('example-class', 'aa', 'xx', 'yy', 'red', 'green', 'blue'))); + + // Remove one class. + $attribute['class']->remove('example-class'); + $this->assertFalse(in_array('example-class', $attribute['class']->value())); + + // Remove multiple classes. + $attribute['class']->remove('xx', 'yy'); + $this->assertFalse(in_array(array('xx', 'yy'), $attribute['class']->value())); + + // Remove an array of classes. + $attribute['class']->remove(array('red', 'green', 'blue')); + $this->assertFalse(in_array(array('red', 'green', 'blue'), $attribute['class']->value())); + } + + /** + * Tests removing class attributes with the AttributeArray helper method. + */ + public function testChainAddRemoveClasses() { + $attribute = new Attribute(array('class' => array('example-class', 'red', 'green', 'blue'))); + unset($attribute['class']); + $this->assertFalse(isset($attribute['class'])); + } + + /** * Tests iterating on the values of the attribute. */