diff --git a/core/includes/common.inc b/core/includes/common.inc index 19e0d59..eea6289 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -2318,11 +2318,7 @@ function drupal_http_header_attributes(array $attributes = array()) { * @ingroup sanitization */ function drupal_attributes(array $attributes = array()) { - foreach ($attributes as $attribute => &$data) { - $data = implode(' ', (array) $data); - $data = $attribute . '="' . check_plain($data) . '"'; - } - return $attributes ? ' ' . implode(' ', $attributes) : ''; + return new Drupal\Core\Template\Attribute($attributes); } /** diff --git a/core/includes/theme.inc b/core/includes/theme.inc index 7d638f6..1f28f9a 100644 --- a/core/includes/theme.inc +++ b/core/includes/theme.inc @@ -2388,9 +2388,6 @@ function template_preprocess(&$variables, $hook) { // Tell all templates where they are located. $variables['directory'] = path_to_theme(); - // Initialize html class attribute for the current hook. - $variables['classes_array'] = array(drupal_html_class($hook)); - // Merge in variables that don't depend on hook and don't change during a // single page request. // Use the advanced drupal_static() pattern, since this is called very often. @@ -2405,7 +2402,14 @@ function template_preprocess(&$variables, $hook) { if (!isset($default_variables) || ($user !== $default_variables['user'])) { $default_variables = _template_preprocess_default_variables(); } - $variables += $default_variables; + $variables += $default_variables + array( + 'attributes' => drupal_attributes(array('class' => array())), + 'title_attributes' => drupal_attributes(array('class' => array())), + 'content_attributes' => drupal_attributes(array('class' => array())), + ); + + // Initialize html class attribute for the current hook. + $variables['attributes']['class'] = array(drupal_html_class($hook)); } /** @@ -2416,9 +2420,6 @@ function _template_preprocess_default_variables() { // Variables that don't depend on a database connection. $variables = array( - 'attributes_array' => array(), - 'title_attributes_array' => array(), - 'content_attributes_array' => array(), 'title_prefix' => array(), 'title_suffix' => array(), 'user' => $user, @@ -2450,25 +2451,6 @@ function _template_preprocess_default_variables() { } /** - * A default process function used to alter variables as late as possible. - * - * For more detailed information, see theme(). - * - */ -function template_process(&$variables, $hook) { - // Flatten out classes. - $variables['classes'] = implode(' ', $variables['classes_array']); - - // Flatten out attributes, title_attributes, and content_attributes. - // Because this function can be called very often, and often with empty - // attributes, optimize performance by only calling drupal_attributes() if - // necessary. - $variables['attributes'] = $variables['attributes_array'] ? drupal_attributes($variables['attributes_array']) : ''; - $variables['title_attributes'] = $variables['title_attributes_array'] ? drupal_attributes($variables['title_attributes_array']) : ''; - $variables['content_attributes'] = $variables['content_attributes_array'] ? drupal_attributes($variables['content_attributes_array']) : ''; -} - -/** * Preprocess variables for html.tpl.php * * @see system_elements() @@ -2480,22 +2462,24 @@ function template_preprocess_html(&$variables) { // Compile a list of classes that are going to be applied to the body element. // This allows advanced theming based on context (home page, node of certain type, etc.). // Add a class that tells us whether we're on the front page or not. - $variables['classes_array'][] = $variables['is_front'] ? 'front' : 'not-front'; + $variables['attributes']['class'][] = $variables['is_front'] ? 'front' : 'not-front'; // Add a class that tells us whether the page is viewed by an authenticated user or not. - $variables['classes_array'][] = $variables['logged_in'] ? 'logged-in' : 'not-logged-in'; + $variables['attributes']['class'][] = $variables['logged_in'] ? 'logged-in' : 'not-logged-in'; // Add information about the number of sidebars. if (!empty($variables['page']['sidebar_first']) && !empty($variables['page']['sidebar_second'])) { - $variables['classes_array'][] = 'two-sidebars'; + $variables['attributes']['class'][] = 'two-sidebars'; } elseif (!empty($variables['page']['sidebar_first'])) { - $variables['classes_array'][] = 'one-sidebar sidebar-first'; + $variables['attributes']['class'][] = 'one-sidebar'; + $variables['attributes']['class'][] = 'sidebar-first'; } elseif (!empty($variables['page']['sidebar_second'])) { - $variables['classes_array'][] = 'one-sidebar sidebar-second'; + $variables['attributes']['class'][] = 'one-sidebar'; + $variables['attributes']['class'][] = 'sidebar-second'; } else { - $variables['classes_array'][] = 'no-sidebars'; + $variables['attributes']['class'][] = 'no-sidebars'; } // Populate the body classes. @@ -2505,23 +2489,22 @@ function template_preprocess_html(&$variables) { // Add current suggestion to page classes to make it possible to theme // the page depending on the current page type (e.g. node, admin, user, // etc.) as well as more specific data like node-12 or node-edit. - $variables['classes_array'][] = drupal_html_class($suggestion); + $variables['attributes']['class'][] = drupal_html_class($suggestion); } } } // If on an individual node page, add the node type to body classes. if ($node = menu_get_object()) { - $variables['classes_array'][] = drupal_html_class('node-type-' . $node->type); + $variables['attributes']['class'][] = drupal_html_class('node-type-' . $node->type); } // Initializes attributes which are specific to the html and body elements. - $variables['html_attributes_array'] = array(); - $variables['body_attributes_array'] = array(); + $variables['html_attributes'] = array(); // HTML element attributes. - $variables['html_attributes_array']['lang'] = $language_interface->langcode; - $variables['html_attributes_array']['dir'] = $language_interface->direction ? 'rtl' : 'ltr'; + $variables['html_attributes']['lang'] = $language_interface->langcode; + $variables['html_attributes']['dir'] = $language_interface->direction ? 'rtl' : 'ltr'; // Add favicon. if (theme_get_setting('toggle_favicon')) { @@ -2655,9 +2638,8 @@ function template_process_page(&$variables) { * @see html.tpl.php */ function template_process_html(&$variables) { - // Flatten out html_attributes and body_attributes. - $variables['html_attributes'] = drupal_attributes($variables['html_attributes_array']); - $variables['body_attributes'] = drupal_attributes($variables['body_attributes_array']); + // Flatten out html_attributes and attributes. + $variables['html_attributes'] = drupal_attributes($variables['html_attributes']); // Render page_top and page_bottom into top level variables. $variables['page_top'] = drupal_render($variables['page']['page_top']); @@ -2676,8 +2658,8 @@ function template_process_html(&$variables) { * Generate an array of suggestions from path arguments. * * This is typically called for adding to the 'theme_hook_suggestions' or - * 'classes_array' variables from within preprocess functions, when wanting to - * base the additional suggestions on the path of the current page. + * 'attributes' class key variables from within preprocess functions, when + * wanting to base the additional suggestions on the path of the current page. * * @param $args * An array of path arguments, such as from function arg(). @@ -2692,7 +2674,7 @@ function template_process_html(&$variables) { * @return * An array of suggestions, suitable for adding to * $variables['theme_hook_suggestions'] within a preprocess function or to - * $variables['classes_array'] if the suggestions represent extra CSS classes. + * $variables['attributes']['class'] if the suggestions represent extra CSS classes. */ function theme_get_suggestions($args, $base, $delimiter = '__') { @@ -2819,18 +2801,19 @@ function template_preprocess_maintenance_page(&$variables) { $variables['title'] = drupal_get_title(); // Compile a list of classes that are going to be applied to the body element. - $variables['classes_array'][] = 'in-maintenance'; + $variables['attributes']['class'][] = 'in-maintenance'; if (isset($variables['db_is_active']) && !$variables['db_is_active']) { - $variables['classes_array'][] = 'db-offline'; + $variables['attributes']['class'][] = 'db-offline'; } if ($variables['layout'] == 'both') { - $variables['classes_array'][] = 'two-sidebars'; + $variables['attributes']['class'][] = 'two-sidebars'; } elseif ($variables['layout'] == 'none') { - $variables['classes_array'][] = 'no-sidebars'; + $variables['attributes']['class'][] = 'no-sidebars'; } else { - $variables['classes_array'][] = 'one-sidebar sidebar-' . $variables['layout']; + $variables['attributes']['class'][] = 'one-sidebar'; + $variables['attributes']['class'][] = 'sidebar-' . $variables['layout']; } // Dead databases will show error messages so supplying this template will @@ -2868,6 +2851,6 @@ function template_preprocess_region(&$variables) { $variables['content'] = $variables['elements']['#children']; $variables['region'] = $variables['elements']['#region']; - $variables['classes_array'][] = drupal_region_class($variables['region']); + $variables['attributes']['class'][] = drupal_region_class($variables['region']); $variables['theme_hook_suggestions'][] = 'region__' . $variables['region']; } diff --git a/core/lib/Drupal/Core/Template/Attribute.php b/core/lib/Drupal/Core/Template/Attribute.php new file mode 100644 index 0000000..2f666e9 --- /dev/null +++ b/core/lib/Drupal/Core/Template/Attribute.php @@ -0,0 +1,66 @@ + $value) { + $this->offsetSet($name, $value); + } + } + + function offsetGet($name) { + if (isset($this->storage[$name])) { + return $this->storage[$name]; + } + } + + function offsetSet($name, $value) { + if (!is_object($value) || $value instanceOf Twig_Markup) { + if (is_array($value)) { + $value = new AttributeArray($name, $value); + } + elseif (is_bool($value)) { + $value = new AttributeBoolean($name, $value); + } + else { + $value = new AttributeString($name, $value); + } + } + // The $name could be NULL. + if (isset($name)) { + $this->storage[$name] = $value; + } + else { + $this->storage[] = $value; + } + } + + function offsetUnset($name) { + unset($this->storage[$name]); + } + + function offsetExists($name) { + return isset($this->storage[$name]); + } + function __toString() { + $return = ''; + foreach ($this->storage as $name => $value) { + if (!$value->printed()) { + $rendered = is_object($value) ? $value->render() : (check_plain($name) . ' = "' . check_plain($value) . '"'); + if ($rendered) { + $return .= " $rendered"; + } + } + } + return $return; + } +} diff --git a/core/lib/Drupal/Core/Template/AttributeArray.php b/core/lib/Drupal/Core/Template/AttributeArray.php new file mode 100644 index 0000000..8655d9d --- /dev/null +++ b/core/lib/Drupal/Core/Template/AttributeArray.php @@ -0,0 +1,40 @@ +value[$offset]; + } + + function offsetSet($offset, $value) { + if (isset($offset)) { + $this->value[$offset] = $value; + } + else { + $this->value[] = $value; + } + } + + function offsetUnset($offset) { + unset($this->value[$offset]); + } + + function offsetExists($offset) { + return isset($this->value[$offset]); + } + function value() { + return $this->value; + } + + function __toString() { + $this->printed = TRUE; + return implode(' ', array_map('check_plain', $this->value)); + } +} diff --git a/core/lib/Drupal/Core/Template/AttributeBoolean.php b/core/lib/Drupal/Core/Template/AttributeBoolean.php new file mode 100644 index 0000000..eb1a3ce --- /dev/null +++ b/core/lib/Drupal/Core/Template/AttributeBoolean.php @@ -0,0 +1,19 @@ +__toString(); + } + function __toString() { + $this->printed = TRUE; + return $this->value === FALSE ? '' : check_plain($this->name); + } +} diff --git a/core/lib/Drupal/Core/Template/AttributeString.php b/core/lib/Drupal/Core/Template/AttributeString.php new file mode 100644 index 0000000..60ffecb --- /dev/null +++ b/core/lib/Drupal/Core/Template/AttributeString.php @@ -0,0 +1,15 @@ +printed = TRUE; + return check_plain($this->value); + } +} diff --git a/core/lib/Drupal/Core/Template/AttributeValue.php b/core/lib/Drupal/Core/Template/AttributeValue.php new file mode 100644 index 0000000..ae4c65d --- /dev/null +++ b/core/lib/Drupal/Core/Template/AttributeValue.php @@ -0,0 +1,26 @@ +name = $name; + $this->value = $value; + } + + function render() { + return check_plain($this->name) . '="' . $this . '"'; + } + + function printed() { + return $this->printed; + } + abstract function __toString(); +} diff --git a/core/modules/aggregator/aggregator.module b/core/modules/aggregator/aggregator.module index 0626f72..3e9850c 100644 --- a/core/modules/aggregator/aggregator.module +++ b/core/modules/aggregator/aggregator.module @@ -793,6 +793,6 @@ function _aggregator_items($count) { */ function aggregator_preprocess_block(&$variables) { if ($variables['block']->module == 'aggregator') { - $variables['attributes_array']['role'] = 'complementary'; + $variables['attributes']['role'] = 'complementary'; } } diff --git a/core/modules/block/block.module b/core/modules/block/block.module index 97f59e9..bc63939 100644 --- a/core/modules/block/block.module +++ b/core/modules/block/block.module @@ -998,10 +998,10 @@ function template_preprocess_block(&$variables) { // Create the $content variable that templates expect. $variables['content'] = $variables['elements']['#children']; - $variables['classes_array'][] = drupal_html_class('block-' . $variables['block']->module); + $variables['attributes']['class'][] = drupal_html_class('block-' . $variables['block']->module); // Add default class for block content. - $variables['content_attributes_array']['class'][] = 'content'; + $variables['content_attributes']['class'][] = 'content'; $variables['theme_hook_suggestions'][] = 'block__' . $variables['block']->region; $variables['theme_hook_suggestions'][] = 'block__' . $variables['block']->module; diff --git a/core/modules/block/block.test b/core/modules/block/block.test index e6b2e42..a9a6dcd 100644 --- a/core/modules/block/block.test +++ b/core/modules/block/block.test @@ -780,11 +780,11 @@ class BlockTemplateSuggestionsUnitTest extends UnitTestBase { $variables2['elements']['#block'] = $block2; $variables2['elements']['#children'] = ''; // Test adding a class to the block content. - $variables2['content_attributes_array']['class'][] = 'test-class'; + $variables2['content_attributes']['class'][] = 'test-class'; template_preprocess_block($variables2); $this->assertEqual($variables2['theme_hook_suggestions'], array('block__footer', 'block__block', 'block__block__hyphen_test'), t('Hyphens (-) in block delta were replaced by underscore (_)')); // Test that the default class and added class are available. - $this->assertEqual($variables2['content_attributes_array']['class'], array('test-class', 'content'), t('Default .content class added to block content_attributes_array')); + $this->assertEqual($variables2['content_attributes']['class'], array('test-class', 'content'), t('Default .content class added to block content_attributes')); } } diff --git a/core/modules/block/block.tpl.php b/core/modules/block/block.tpl.php index 78387a8..2b32f65 100644 --- a/core/modules/block/block.tpl.php +++ b/core/modules/block/block.tpl.php @@ -10,10 +10,9 @@ * - $block->module: Module that generated the block. * - $block->delta: An ID for the block, unique within each module. * - $block->region: The block region embedding the current block. - * - $classes: String of classes that can be used to style contextually through - * CSS. It can be manipulated through the variable $classes_array from - * preprocess functions. The default values can be one or more of the - * following: + * - $attributes: An object of HTML attributes that can be manipulated as an + * array and printed as a string. + * It includes the 'class' information, which includes: * - block: The current template type, i.e., "theming hook". * - block-[module]: The module generating the block. For example, the user * module is responsible for handling the default user navigation block. In @@ -26,8 +25,6 @@ * the template. * * Helper variables: - * - $classes_array: Array of html class attribute values. It is flattened - * into a string within the variable $classes. * - $block_zebra: Outputs 'odd' and 'even' dependent on each block region. * - $zebra: Same output as $block_zebra but independent of any block region. * - $block_id: Counter dependent on each block region. @@ -44,7 +41,7 @@ * @ingroup themeable */ ?> -