From 4f37cf02556c374ea4db81433d492d4dd2e3aa5d Mon Sep 17 00:00:00 2001
From: catch <catch@35733.no-reply.drupal.org>
Date: Tue, 4 Sep 2012 14:32:47 +0100
Subject: [PATCH] Issue #1700382 by Albert Volkman, cam8001: Replace remaining
 references to drupal_attributes() with new Attributes().

---
 core/includes/common.inc                      | 61 ++++--------------
 core/includes/form.inc                        | 64 ++++++++++---------
 core/includes/menu.inc                        |  4 +-
 core/includes/pager.inc                       |  4 +-
 core/includes/theme.inc                       | 39 +++++------
 core/lib/Drupal/Core/Template/Attribute.php   |  5 --
 core/modules/book/book.module                 |  3 +-
 core/modules/field/field.module               | 17 ++---
 core/modules/file/file.module                 |  3 +-
 core/modules/filter/filter.module             |  3 +-
 core/modules/node/node.module                 |  3 +-
 core/modules/rdf/rdf.module                   | 18 +++---
 .../Tests/Common/AttributesUnitTest.php       | 17 ++---
 core/modules/user/user.module                 |  7 +-
 14 files changed, 111 insertions(+), 137 deletions(-)

diff --git a/core/includes/common.inc b/core/includes/common.inc
index dece53967abc..39a30d2686ec 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -1246,8 +1246,9 @@ function flood_is_allowed($name, $threshold, $window = 3600, $identifier = NULL)
  * to being output to an HTML attribute value. It is often called as part of
  * check_url() or filter_xss(), but those functions return an HTML-encoded
  * string, so this function can be called independently when the output needs to
- * be a plain-text string for passing to t(), l(), drupal_attributes(), or
- * another function that will call check_plain() separately.
+ * be a plain-text string for passing to t(), l(),
+ * Drupal\Core\Template\Attribute, or another function that will call
+ * check_plain() separately.
  *
  * @param $uri
  *   A plain-text URI that might contain dangerous protocols.
@@ -1300,10 +1301,11 @@ function drupal_strip_dangerous_protocols($uri) {
  * @return
  *   A URI stripped of dangerous protocols and encoded for output to an HTML
  *   attribute value. Because it is already encoded, it should not be set as a
- *   value within a $attributes array passed to drupal_attributes(), because
- *   drupal_attributes() expects those values to be plain-text strings. To pass
- *   a filtered URI to drupal_attributes(), call
- *   drupal_strip_dangerous_protocols() instead.
+ *   value within a $attributes array passed to Drupal\Core\Template\Attribute,
+ *   because Drupal\Core\Template\Attribute expects those values to be
+ *   plain-text strings. To pass a filtered URI to
+ *   Drupal\Core\Template\Attribute, call drupal_strip_dangerous_protocols()
+ *   instead.
  *
  * @see drupal_strip_dangerous_protocols()
  */
@@ -1661,7 +1663,7 @@ function format_xml_elements($array) {
       if ($value['key']) {
         $output .= ' <' . $value['key'];
         if (isset($value['attributes']) && is_array($value['attributes'])) {
-          $output .= drupal_attributes($value['attributes']);
+          $output .= new Attribute($value['attributes']);
         }
 
         if (isset($value['value']) && $value['value'] != '') {
@@ -2242,46 +2244,6 @@ function drupal_http_header_attributes(array $attributes = array()) {
   return $attributes ? ' ' . implode('; ', $attributes) : '';
 }
 
-/**
- * Converts an associative array to an XML/HTML tag attribute string.
- *
- * Each array key and its value will be formatted into an attribute string.
- * If a value is itself an array, then its elements are concatenated to a single
- * space-delimited string (for example, a class attribute with multiple values).
- *
- * Attribute values are sanitized by running them through check_plain().
- * Attribute names are not automatically sanitized. When using user-supplied
- * attribute names, it is strongly recommended to allow only white-listed names,
- * since certain attributes carry security risks and can be abused.
- *
- * Examples of security aspects when using drupal_attributes:
- * @code
- *   // By running the value in the following statement through check_plain,
- *   // the malicious script is neutralized.
- *   drupal_attributes(array('title' => t('<script>steal_cookie();</script>')));
- *
- *   // The statement below demonstrates dangerous use of drupal_attributes, and
- *   // will return an onmouseout attribute with JavaScript code that, when used
- *   // as attribute in a tag, will cause users to be redirected to another site.
- *   //
- *   // In this case, the 'onmouseout' attribute should not be whitelisted --
- *   // you don't want users to have the ability to add this attribute or others
- *   // that take JavaScript commands.
- *   drupal_attributes(array('onmouseout' => 'window.location="http://malicious.com/";')));
- * @endcode
- *
- * @param $attributes
- *   An associative array of key-value pairs to be converted to attributes.
- *
- * @return
- *   A string ready for insertion in a tag (starts with a space).
- *
- * @ingroup sanitization
- */
-function drupal_attributes(array $attributes = array()) {
-  return new Attribute($attributes);
-}
-
 /**
  * Formats an internal or external URL link as an HTML anchor tag.
  *
@@ -2303,7 +2265,8 @@ function drupal_attributes(array $attributes = array()) {
  *   - 'attributes': An associative array of HTML attributes to apply to the
  *     anchor tag. If element 'class' is included, it must be an array; 'title'
  *     must be a string; other elements are more flexible, as they just need
- *     to work in a call to drupal_attributes($options['attributes']).
+ *     to work as an argument for the constructor of the class
+ *     Drupal\Core\Template\Attribute($options['attributes']).
  *   - 'html' (default FALSE): Whether $text is HTML or just plain-text. For
  *     example, to make an image tag into a link, this must be set to TRUE, or
  *     you will see the escaped HTML image tag. $text is not sanitized if
@@ -2370,7 +2333,7 @@ function l($text, $path, array $options = array()) {
   }
   // The result of url() is a plain-text URL. Because we are using it here
   // in an HTML argument context, we need to encode it properly.
-  return '<a href="' . check_plain(url($path, $options)) . '"' . drupal_attributes($options['attributes']) . '>' . ($options['html'] ? $text : check_plain($text)) . '</a>';
+  return '<a href="' . check_plain(url($path, $options)) . '"' . new Attribute($options['attributes']) . '>' . ($options['html'] ? $text : check_plain($text)) . '</a>';
 }
 
 /**
diff --git a/core/includes/form.inc b/core/includes/form.inc
index e2003453274f..29bce0019be8 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -6,6 +6,7 @@
  */
 
 use Drupal\Core\Utility\Color;
+use Drupal\Core\Template\Attribute;
 
 /**
  * @defgroup forms Form builder functions
@@ -2670,7 +2671,7 @@ function theme_select($variables) {
   element_set_attributes($element, array('id', 'name', 'size'));
   _form_set_class($element, array('form-select'));
 
-  return '<select' . drupal_attributes($element['#attributes']) . '>' . form_select_options($element) . '</select>';
+  return '<select' . new Attribute($element['#attributes']) . '>' . form_select_options($element) . '</select>';
 }
 
 /**
@@ -2784,7 +2785,7 @@ function theme_fieldset($variables) {
   element_set_attributes($element, array('id'));
   _form_set_class($element, array('form-wrapper'));
 
-  $output = '<fieldset' . drupal_attributes($element['#attributes']) . '>';
+  $output = '<fieldset' . new Attribute($element['#attributes']) . '>';
   if (!empty($element['#title'])) {
     // Always wrap fieldset legends in a SPAN for CSS positioning.
     $output .= '<legend><span class="fieldset-legend">' . $element['#title'] . '</span></legend>';
@@ -2806,7 +2807,8 @@ function theme_fieldset($variables) {
  * Returns HTML for a radio button form element.
  *
  * Note: The input "name" attribute needs to be sanitized before output, which
- *       is currently done by passing all attributes to drupal_attributes().
+ *       is currently done by initializing Drupal\Core\Template\Attribute with
+ *       all the attributes.
  *
  * @param $variables
  *   An associative array containing:
@@ -2826,7 +2828,7 @@ function theme_radio($variables) {
   }
   _form_set_class($element, array('form-radio'));
 
-  return '<input' . drupal_attributes($element['#attributes']) . ' />';
+  return '<input' . new Attribute($element['#attributes']) . ' />';
 }
 
 /**
@@ -2853,7 +2855,7 @@ function theme_radios($variables) {
   if (isset($element['#attributes']['title'])) {
     $attributes['title'] = $element['#attributes']['title'];
   }
-  return '<div' . drupal_attributes($attributes) . '>' . (!empty($element['#children']) ? $element['#children'] : '') . '</div>';
+  return '<div' . new Attribute($attributes) . '>' . (!empty($element['#children']) ? $element['#children'] : '') . '</div>';
 }
 
 /**
@@ -2932,7 +2934,7 @@ function theme_date($variables) {
   }
   $attributes['class'][] = 'container-inline';
 
-  return '<div' . drupal_attributes($attributes) . '>' . drupal_render_children($element) . '</div>';
+  return '<div' . new Attribute($attributes) . '>' . drupal_render_children($element) . '</div>';
 }
 
 /**
@@ -3054,8 +3056,8 @@ function form_process_radios($element) {
       $element[$key] += array(
         '#type' => 'radio',
         '#title' => $choice,
-        // The key is sanitized in drupal_attributes() during output from the
-        // theme function.
+        // The key is sanitized in Drupal\Core\Template\Attribute during output
+        // from the theme function.
         '#return_value' => $key,
         // Use default or FALSE. A value of FALSE means that the radio button is
         // not 'checked'.
@@ -3094,7 +3096,7 @@ function theme_checkbox($variables) {
   }
   _form_set_class($element, array('form-checkbox'));
 
-  return '<input' . drupal_attributes($element['#attributes']) . ' />';
+  return '<input' . new Attribute($element['#attributes']) . ' />';
 }
 
 /**
@@ -3120,7 +3122,7 @@ function theme_checkboxes($variables) {
   if (isset($element['#attributes']['title'])) {
     $attributes['title'] = $element['#attributes']['title'];
   }
-  return '<div' . drupal_attributes($attributes) . '>' . (!empty($element['#children']) ? $element['#children'] : '') . '</div>';
+  return '<div' . new Attribute($attributes) . '>' . (!empty($element['#children']) ? $element['#children'] : '') . '</div>';
 }
 
 /**
@@ -3338,7 +3340,7 @@ function theme_container($variables) {
     $element['#attributes']['class'][] = 'form-wrapper';
   }
 
-  return '<div' . drupal_attributes($element['#attributes']) . '>' . $element['#children'] . '</div>';
+  return '<div' . new Attribute($element['#attributes']) . '>' . $element['#children'] . '</div>';
 }
 
 /**
@@ -3914,7 +3916,7 @@ function theme_button($variables) {
     $element['#attributes']['class'][] = 'form-button-disabled';
   }
 
-  return '<input' . drupal_attributes($element['#attributes']) . ' />';
+  return '<input' . new Attribute($element['#attributes']) . ' />';
 }
 
 /**
@@ -3943,7 +3945,7 @@ function theme_image_button($variables) {
     $element['#attributes']['class'][] = 'form-button-disabled';
   }
 
-  return '<input' . drupal_attributes($element['#attributes']) . ' />';
+  return '<input' . new Attribute($element['#attributes']) . ' />';
 }
 
 /**
@@ -3960,7 +3962,7 @@ function theme_hidden($variables) {
   $element = $variables['element'];
   $element['#attributes']['type'] = 'hidden';
   element_set_attributes($element, array('name', 'value'));
-  return '<input' . drupal_attributes($element['#attributes']) . " />\n";
+  return '<input' . new Attribute($element['#attributes']) . " />\n";
 }
 
 /**
@@ -3980,7 +3982,7 @@ function theme_textfield($variables) {
   element_set_attributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder'));
   _form_set_class($element, array('form-text'));
 
-  return '<input' . drupal_attributes($element['#attributes']) . ' />' . drupal_render_children($element);
+  return '<input' . new Attribute($element['#attributes']) . ' />' . drupal_render_children($element);
 }
 
 /**
@@ -4000,7 +4002,7 @@ function theme_email($variables) {
   element_set_attributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder'));
   _form_set_class($element, array('form-email'));
 
-  return '<input' . drupal_attributes($element['#attributes']) . ' />' . drupal_render_children($element);
+  return '<input' . new Attribute($element['#attributes']) . ' />' . drupal_render_children($element);
 }
 
 /**
@@ -4034,7 +4036,7 @@ function theme_tel($variables) {
   element_set_attributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder'));
   _form_set_class($element, array('form-tel'));
 
-  return '<input' . drupal_attributes($element['#attributes']) . ' />' . drupal_render_children($element);
+  return '<input' . new Attribute($element['#attributes']) . ' />' . drupal_render_children($element);
 }
 
 /**
@@ -4055,7 +4057,7 @@ function theme_number($variables) {
   element_set_attributes($element, array('id', 'name', 'value', 'step', 'min', 'max', 'placeholder'));
   _form_set_class($element, array('form-number'));
 
-  $output = '<input' . drupal_attributes($element['#attributes']) . ' />';
+  $output = '<input' . new Attribute($element['#attributes']) . ' />';
 
   return $output;
 }
@@ -4078,7 +4080,7 @@ function theme_range($variables) {
   element_set_attributes($element, array('id', 'name', 'value', 'step', 'min', 'max'));
   _form_set_class($element, array('form-range'));
 
-  $output = '<input' . drupal_attributes($element['#attributes']) . ' />';
+  $output = '<input' . new Attribute($element['#attributes']) . ' />';
 
   return $output;
 }
@@ -4170,7 +4172,7 @@ function theme_url($variables) {
   element_set_attributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder'));
   _form_set_class($element, array('form-url'));
 
-  return '<input' . drupal_attributes($element['#attributes']) . ' />' . drupal_render_children($element);
+  return '<input' . new Attribute($element['#attributes']) . ' />' . drupal_render_children($element);
 }
 
 /**
@@ -4190,7 +4192,7 @@ function theme_search($variables) {
   element_set_attributes($element, array('id', 'name', 'value', 'size', 'maxlength', 'placeholder'));
   _form_set_class($element, array('form-search'));
 
-  return '<input' . drupal_attributes($element['#attributes']) . ' />' . drupal_render_children($element);
+  return '<input' . new Attribute($element['#attributes']) . ' />' . drupal_render_children($element);
 }
 
 /**
@@ -4245,7 +4247,7 @@ function theme_color($variables) {
   element_set_attributes($element, array('id', 'name', 'value'));
   _form_set_class($element, array('form-color'));
 
-  return '<input' . drupal_attributes($element['#attributes']) . ' />' . drupal_render_children($element);
+  return '<input' . new Attribute($element['#attributes']) . ' />' . drupal_render_children($element);
 }
 
 /**
@@ -4268,7 +4270,7 @@ function theme_form($variables) {
     $element['#attributes']['accept-charset'] = "UTF-8";
   }
   // Anonymous DIV to satisfy XHTML compliance.
-  return '<form' . drupal_attributes($element['#attributes']) . '><div>' . $element['#children'] . '</div></form>';
+  return '<form' . new Attribute($element['#attributes']) . '><div>' . $element['#children'] . '</div></form>';
 }
 
 /**
@@ -4296,8 +4298,8 @@ function theme_textarea($variables) {
     $element['#attributes']['class'][] = 'resize-' . $element['#resizable'];
   }
 
-  $output = '<div' . drupal_attributes($wrapper_attributes) . '>';
-  $output .= '<textarea' . drupal_attributes($element['#attributes']) . '>' . check_plain($element['#value']) . '</textarea>';
+  $output = '<div' . new Attribute($wrapper_attributes) . '>';
+  $output .= '<textarea' . new Attribute($element['#attributes']) . '>' . check_plain($element['#value']) . '</textarea>';
   $output .= '</div>';
   return $output;
 }
@@ -4319,7 +4321,7 @@ function theme_password($variables) {
   element_set_attributes($element, array('id', 'name', 'size', 'maxlength', 'placeholder'));
   _form_set_class($element, array('form-text'));
 
-  return '<input' . drupal_attributes($element['#attributes']) . ' />';
+  return '<input' . new Attribute($element['#attributes']) . ' />';
 }
 
 /**
@@ -4369,7 +4371,7 @@ function theme_file($variables) {
   element_set_attributes($element, array('id', 'name', 'size'));
   _form_set_class($element, array('form-file'));
 
-  return '<input' . drupal_attributes($element['#attributes']) . ' />';
+  return '<input' . new Attribute($element['#attributes']) . ' />';
 }
 
 /**
@@ -4445,7 +4447,7 @@ function theme_form_element($variables) {
   if (!empty($element['#attributes']['disabled'])) {
     $attributes['class'][] = 'form-disabled';
   }
-  $output = '<div' . drupal_attributes($attributes) . '>' . "\n";
+  $output = '<div' . new Attribute($attributes) . '>' . "\n";
 
   // If #title is not set, we don't display any label or required marker.
   if (!isset($element['#title'])) {
@@ -4478,7 +4480,7 @@ function theme_form_element($variables) {
     if (!empty($element['#id'])) {
       $attributes['id'] = $element['#id'] . '--description';
     }
-    $output .= '<div' . drupal_attributes($attributes) . '>' . $element['#description'] . "</div>\n";
+    $output .= '<div' . new Attribute($attributes) . '>' . $element['#description'] . "</div>\n";
   }
 
   $output .= "</div>\n";
@@ -4502,7 +4504,7 @@ function theme_form_required_marker($variables) {
     'class' => 'form-required',
     'title' => $t('This field is required.'),
   );
-  return '<abbr' . drupal_attributes($attributes) . '>*</abbr>';
+  return '<abbr' . new Attribute($attributes) . '>*</abbr>';
 }
 
 /**
@@ -4558,7 +4560,7 @@ function theme_form_element_label($variables) {
   }
 
   // The leading whitespace helps visually separate fields from inline labels.
-  return ' <label' . drupal_attributes($attributes) . '>' . $t('!title !required', array('!title' => $title, '!required' => $required)) . "</label>\n";
+  return ' <label' . new Attribute($attributes) . '>' . $t('!title !required', array('!title' => $title, '!required' => $required)) . "</label>\n";
 }
 
 /**
diff --git a/core/includes/menu.inc b/core/includes/menu.inc
index 420b09273627..8574dbe0180b 100644
--- a/core/includes/menu.inc
+++ b/core/includes/menu.inc
@@ -5,6 +5,8 @@
  * API for the Drupal menu system.
  */
 
+use Drupal\Core\Template\Attribute;
+
 /**
  * @defgroup menu Menu system
  * @{
@@ -1602,7 +1604,7 @@ function theme_menu_link(array $variables) {
     $sub_menu = drupal_render($element['#below']);
   }
   $output = l($element['#title'], $element['#href'], $element['#localized_options']);
-  return '<li' . drupal_attributes($element['#attributes']) . '>' . $output . $sub_menu . "</li>\n";
+  return '<li' . new Attribute($element['#attributes']) . '>' . $output . $sub_menu . "</li>\n";
 }
 
 /**
diff --git a/core/includes/pager.inc b/core/includes/pager.inc
index 40d9c01078fc..75dcf824c9b9 100644
--- a/core/includes/pager.inc
+++ b/core/includes/pager.inc
@@ -5,6 +5,8 @@
  * Functions to aid in presenting database results as a set of pages.
  */
 
+use Drupal\Core\Template\Attribute;
+
 /**
  * Returns the current page being requested for display within a pager.
  *
@@ -476,7 +478,7 @@ function theme_pager_link($variables) {
   //   possible to use l() here.
   // @see http://drupal.org/node/1410574
   $attributes['href'] = url(current_path(), array('query' => $query));
-  return '<a' . drupal_attributes($attributes) . '>' . check_plain($text) . '</a>';
+  return '<a' . new Attribute($attributes) . '>' . check_plain($text) . '</a>';
 }
 
 /**
diff --git a/core/includes/theme.inc b/core/includes/theme.inc
index 30457bff18a0..2d1d375e05c2 100644
--- a/core/includes/theme.inc
+++ b/core/includes/theme.inc
@@ -9,6 +9,7 @@
  */
 
 use Drupal\Core\Utility\ThemeRegistry;
+use Drupal\Core\Template\Attribute;
 
 /**
  * @defgroup content_flags Content markers
@@ -1503,7 +1504,7 @@ function template_preprocess_datetime(&$variables) {
  * @see http://www.w3.org/TR/html5-author/the-time-element.html#attr-time-datetime
  */
 function theme_datetime($variables) {
-  $output = '<time' . drupal_attributes($variables['attributes']) . '>';
+  $output = '<time' . new Attribute($variables['attributes']) . '>';
   $output .= !empty($variables['html']) ? $variables['text'] : check_plain($variables['text']);
   $output .= '</time>';
   return $output;
@@ -1569,7 +1570,7 @@ function theme_status_messages($variables) {
  * @see l()
  */
 function theme_link($variables) {
-  return '<a href="' . check_plain(url($variables['path'], $variables['options'])) . '"' . drupal_attributes($variables['options']['attributes']) . '>' . ($variables['options']['html'] ? $variables['text'] : check_plain($variables['text'])) . '</a>';
+  return '<a href="' . check_plain(url($variables['path'], $variables['options'])) . '"' . new Attribute($variables['options']['attributes']) . '>' . ($variables['options']['html'] ? $variables['text'] : check_plain($variables['text'])) . '</a>';
 }
 
 /**
@@ -1632,12 +1633,12 @@ function theme_links($variables) {
         $heading['attributes']['class'] = $heading['class'];
       }
 
-      $output .= '<' . $heading['level'] . drupal_attributes($heading['attributes']) . '>';
+      $output .= '<' . $heading['level'] . new Attribute($heading['attributes']) . '>';
       $output .= check_plain($heading['text']);
       $output .= '</' . $heading['level'] . '>';
     }
 
-    $output .= '<ul' . drupal_attributes($attributes) . '>';
+    $output .= '<ul' . new Attribute($attributes) . '>';
 
     $num_links = count($links);
     $i = 0;
@@ -1673,12 +1674,12 @@ function theme_links($variables) {
           'html' => FALSE,
           'attributes' => array(),
         );
-        $item = '<span' . drupal_attributes($link['attributes']) . '>';
+        $item = '<span' . new Attribute($link['attributes']) . '>';
         $item .= ($link['html'] ? $link['title'] : check_plain($link['title']));
         $item .= '</span>';
       }
 
-      $output .= '<li' . drupal_attributes(array('class' => $class)) . '>';
+      $output .= '<li' . new Attribute(array('class' => $class)) . '>';
       $output .= $item;
       $output .= '</li>';
     }
@@ -1722,7 +1723,7 @@ function theme_image($variables) {
     }
   }
 
-  return '<img' . drupal_attributes($attributes) . ' />';
+  return '<img' . new Attribute($attributes) . ' />';
 }
 
 /**
@@ -1838,7 +1839,7 @@ function theme_table($variables) {
     $attributes['class'][] = 'sticky-enabled';
   }
 
-  $output = '<table' . drupal_attributes($attributes) . ">\n";
+  $output = '<table' . new Attribute($attributes) . ">\n";
 
   if (isset($caption)) {
     $output .= '<caption>' . $caption . "</caption>\n";
@@ -1866,15 +1867,15 @@ function theme_table($variables) {
 
       // Build colgroup
       if (is_array($cols) && count($cols)) {
-        $output .= ' <colgroup' . drupal_attributes($attributes) . '>';
+        $output .= ' <colgroup' . new Attribute($attributes) . '>';
         $i = 0;
         foreach ($cols as $col) {
-          $output .= ' <col' . drupal_attributes($col) . ' />';
+          $output .= ' <col' . new Attribute($col) . ' />';
         }
         $output .= " </colgroup>\n";
       }
       else {
-        $output .= ' <colgroup' . drupal_attributes($attributes) . " />\n";
+        $output .= ' <colgroup' . new Attribute($attributes) . " />\n";
       }
     }
   }
@@ -1940,7 +1941,7 @@ function theme_table($variables) {
         }
 
         // Build row
-        $output .= ' <tr' . drupal_attributes($attributes) . '>';
+        $output .= ' <tr' . new Attribute($attributes) . '>';
         $i = 0;
         foreach ($cells as $cell) {
           $cell = tablesort_cell($cell, $header, $ts, $i++);
@@ -2018,7 +2019,7 @@ function theme_item_list($variables) {
 
   $output = '';
   if ($items) {
-    $output .= '<' . $type . drupal_attributes($list_attributes) . '>';
+    $output .= '<' . $type . new Attribute($list_attributes) . '>';
 
     $num_items = count($items);
     $i = 0;
@@ -2064,7 +2065,7 @@ function theme_item_list($variables) {
         $attributes['class'][] = 'last';
       }
 
-      $output .= '<li' . drupal_attributes($attributes) . '>' . $value . '</li>';
+      $output .= '<li' . new Attribute($attributes) . '>' . $value . '</li>';
     }
     $output .= "</$type>";
   }
@@ -2129,7 +2130,7 @@ function theme_feed_icon($variables) {
  */
 function theme_html_tag($variables) {
   $element = $variables['element'];
-  $attributes = isset($element['#attributes']) ? drupal_attributes($element['#attributes']) : '';
+  $attributes = isset($element['#attributes']) ? new Attribute($element['#attributes']) : '';
   if (!isset($element['#value'])) {
     return '<' . $element['#tag'] . $attributes . " />\n";
   }
@@ -2211,7 +2212,7 @@ function theme_meter($variables) {
     }
   }
 
-  $output = '<div' . drupal_attributes($attributes) . '>';
+  $output = '<div' . new Attribute($attributes) . '>';
   $output .= '  <div style="width: '. $variables['percentage'] .'%;" class="foreground"></div>';
   $output .= "</div>\n";
 
@@ -2266,7 +2267,7 @@ function _theme_table_cell($cell, $header = FALSE) {
     $header |= isset($cell['header']);
     unset($cell['data']);
     unset($cell['header']);
-    $attributes = drupal_attributes($cell);
+    $attributes = new Attribute($cell);
   }
   else {
     $data = $cell;
@@ -2322,7 +2323,7 @@ function template_preprocess(&$variables, $hook) {
     $default_variables = _template_preprocess_default_variables();
   }
   if (!isset($default_attributes)) {
-    $default_attributes = drupal_attributes(array('class' => array()));
+    $default_attributes = new Attribute(array('class' => array()));
   }
   $variables += $default_variables + array(
     'attributes' => clone $default_attributes,
@@ -2422,7 +2423,7 @@ function template_preprocess_html(&$variables) {
   }
 
   // Initializes attributes which are specific to the html and body elements.
-  $variables['html_attributes'] = drupal_attributes();
+  $variables['html_attributes'] = new Attribute;
 
   // HTML element attributes.
   $variables['html_attributes']['lang'] = $language_interface->langcode;
diff --git a/core/lib/Drupal/Core/Template/Attribute.php b/core/lib/Drupal/Core/Template/Attribute.php
index 3004c71a8101..b2196245fa17 100644
--- a/core/lib/Drupal/Core/Template/Attribute.php
+++ b/core/lib/Drupal/Core/Template/Attribute.php
@@ -31,11 +31,6 @@
  *  echo '<cat class="cat ' . $attributes['class'] . '" ' . $attributes . '>';
  *  // Produces <cat class="cat black-cat white-cat black-white-cat" id="socks">
  * @endcode
- *
- * Most of the time this object is not created directly, but
- * instantiated by drupal_attributes().
- *
- * @see drupal_attributes()
  */
 class Attribute implements ArrayAccess, IteratorAggregate {
 
diff --git a/core/modules/book/book.module b/core/modules/book/book.module
index 7e7ca52c4f7a..fd4fac9f2c22 100644
--- a/core/modules/book/book.module
+++ b/core/modules/book/book.module
@@ -6,6 +6,7 @@
  */
 
 use Drupal\node\Node;
+use Drupal\Core\Template\Attribute;
 
 /**
  * Implements hook_help().
@@ -1245,7 +1246,7 @@ function template_preprocess_book_export_html(&$variables) {
  */
 function template_process_book_export_html(&$variables) {
   // Flatten out html_attributes
-  $variables['html_attributes'] = drupal_attributes($variables['html_attributes']);
+  $variables['html_attributes'] = new Attribute($variables['html_attributes']);
 }
 
 /**
diff --git a/core/modules/field/field.module b/core/modules/field/field.module
index 8157971683cc..e75111e86cea 100644
--- a/core/modules/field/field.module
+++ b/core/modules/field/field.module
@@ -5,6 +5,7 @@
  */
 
 use Drupal\entity\EntityFieldQuery;
+use Drupal\Core\Template\Attribute;
 
 /*
  * Load all public Field API functions. Drupal currently has no
@@ -1107,17 +1108,17 @@ function template_process_field(&$variables, $hook) {
   static $default_attributes;
   // The default theme implementation is a function, so template_process() does
   // not automatically run, so we need to flatten the classes and attributes
-  // here. For best performance, only call drupal_attributes() when needed, and
-  // note that template_preprocess_field() does not initialize the
-  // *_attributes variables.
+  // here. For best performance, only instantiate Drupal\Core\Template\Attribute
+  // when needed, and note that template_preprocess_field() does not initialize
+  // the *_attributes variables.
   if (!isset($default_attributes)) {
-    $default_attributes = drupal_attributes();
+    $default_attributes = new Attribute;
   }
-  $variables['attributes'] = isset($variables['attributes']) ? drupal_attributes($variables['attributes']) : clone $default_attributes;
-  $variables['title_attributes'] = isset($variables['title_attributes']) ? drupal_attributes($variables['title_attributes']) : clone($default_attributes);
-  $variables['content_attributes'] = isset($variables['content_attributes']) ? drupal_attributes($variables['content_attributes']) : clone($default_attributes);
+  $variables['attributes'] = isset($variables['attributes']) ? new Attribute($variables['attributes']) : clone $default_attributes;
+  $variables['title_attributes'] = isset($variables['title_attributes']) ? new Attribute($variables['title_attributes']) : clone($default_attributes);
+  $variables['content_attributes'] = isset($variables['content_attributes']) ? new Attribute($variables['content_attributes']) : clone($default_attributes);
   foreach ($variables['items'] as $delta => $item) {
-    $variables['item_attributes'][$delta] = isset($variables['item_attributes'][$delta]) ? drupal_attributes($variables['item_attributes'][$delta]) : clone($default_attributes);
+    $variables['item_attributes'][$delta] = isset($variables['item_attributes'][$delta]) ? new Attribute($variables['item_attributes'][$delta]) : clone($default_attributes);
   }
 }
 
diff --git a/core/modules/file/file.module b/core/modules/file/file.module
index 3763ea00e8cd..60448639d156 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -7,6 +7,7 @@
 
 use Drupal\entity\EntityFieldQuery;
 use Drupal\file\File;
+use Drupal\Core\Template\Attribute;
 use Symfony\Component\HttpFoundation\JsonResponse;
 
 // Load all Field module hooks for File.
@@ -1352,7 +1353,7 @@ function theme_file_managed_file($variables) {
 
   // This wrapper is required to apply JS behaviors and CSS styling.
   $output = '';
-  $output .= '<div' . drupal_attributes($attributes) . '>';
+  $output .= '<div' . new Attribute($attributes) . '>';
   $output .= drupal_render_children($element);
   $output .= '</div>';
   return $output;
diff --git a/core/modules/filter/filter.module b/core/modules/filter/filter.module
index 1bafc6a35b49..6c54faebefd0 100644
--- a/core/modules/filter/filter.module
+++ b/core/modules/filter/filter.module
@@ -4,6 +4,7 @@
  * @file
  * Framework for handling the filtering of content.
  */
+use Drupal\Core\Template\Attribute;
 
 /**
  * Implements hook_cache_flush().
@@ -1201,7 +1202,7 @@ function theme_filter_guidelines($variables) {
   $format = $variables['format'];
   $attributes['class'][] = 'filter-guidelines-item';
   $attributes['class'][] = 'filter-guidelines-' . $format->format;
-  $output = '<div' . drupal_attributes($attributes) . '>';
+  $output = '<div' . new Attribute($attributes) . '>';
   $output .= '<h3>' . check_plain($format->name) . '</h3>';
   $output .= theme('filter_tips', array('tips' => _filter_tips($format->format, FALSE)));
   $output .= '</div>';
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 7ebb1dcee508..7d84b9dd1aac 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -13,6 +13,7 @@
 use Drupal\Core\Database\Query\AlterableInterface;
 use Drupal\Core\Database\Query\SelectExtender;
 use Drupal\Core\Database\Query\SelectInterface;
+use Drupal\Core\Template\Attribute;
 use Drupal\node\Node;
 use Drupal\file\File;
 use Drupal\entity\EntityInterface;
@@ -2465,7 +2466,7 @@ function node_feed($nids = FALSE, $channel = array()) {
   $channel = array_merge($channel_defaults, $channel);
 
   $output = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n";
-  $output .= "<rss version=\"" . $channel["version"] . "\" xml:base=\"" . $base_url . "\" " . drupal_attributes($namespaces) . ">\n";
+  $output .= "<rss version=\"" . $channel["version"] . "\" xml:base=\"" . $base_url . "\" " . new Attribute($namespaces) . ">\n";
   $output .= format_rss_channel($channel['title'], $channel['link'], $channel['description'], $items, $channel['language'], $channel_extras);
   $output .= "</rss>\n";
 
diff --git a/core/modules/rdf/rdf.module b/core/modules/rdf/rdf.module
index a61760c1b3ff..fda5d5796d62 100644
--- a/core/modules/rdf/rdf.module
+++ b/core/modules/rdf/rdf.module
@@ -5,6 +5,8 @@
  * Enables semantically enriched output for Drupal sites in the form of RDFa.
  */
 
+use Drupal\Core\Template\Attribute;
+
 /**
  * Implements hook_help().
  */
@@ -268,10 +270,10 @@ function rdf_mapping_delete($type, $bundle) {
 /**
  * Builds an array of RDFa attributes for a given mapping.
  *
- * This array will typically be passed through drupal_attributes() to create
- * the attributes variables that are available to template files. These include
- * $attributes, $title_attributes, $content_attributes and the field-specific
- * $item_attributes variables.
+ * This array will typically be passed through Drupal\Core\Template\Attribute
+ * to create the attributes variables that are available to template files.
+ * These include $attributes, $title_attributes, $content_attributes and the
+ * field-specific $item_attributes variables.
  *
  * @param $mapping
  *   An array containing a mandatory 'predicates' key and optional 'datatype',
@@ -289,7 +291,7 @@ function rdf_mapping_delete($type, $bundle) {
  *   function.
  *
  * @return
- *   RDFa attributes suitable for drupal_attributes().
+ *   RDFa attributes suitable for Drupal\Core\Template\Attribute.
  *
  * @see theme_rdf_template_variable_wrapper()
  */
@@ -815,7 +817,7 @@ function rdf_preprocess_image(&$variables) {
  * an extra wrapper element, you can override this function to not wrap that
  * variable and instead print the following inside your template file:
  * @code
- *   drupal_attributes($rdf_template_variable_attributes[$variable_name])
+ *   new Drupal\Core\Template\Attribute($rdf_template_variable_attributes[$variable_name])
  * @endcode
  *
  * @param $variables
@@ -853,7 +855,7 @@ function rdf_preprocess_image(&$variables) {
 function theme_rdf_template_variable_wrapper($variables) {
   $output = $variables['content'];
   if (!empty($output) && !empty($variables['attributes'])) {
-    $attributes = drupal_attributes($variables['attributes']);
+    $attributes = new Attribute($variables['attributes']);
     $output = $variables['inline'] ? "<span$attributes>$output</span>" : "<div$attributes>$output</div>";
   }
   return $output;
@@ -889,7 +891,7 @@ function theme_rdf_metadata($variables) {
     // be used, but for maximum browser compatibility, W3C recommends the
     // former when serving pages using the text/html media type, see
     // http://www.w3.org/TR/xhtml1/#C_3.
-    $output .= '<span' . drupal_attributes($attributes) . '></span>';
+    $output .= '<span' . new Attribute($attributes) . '></span>';
   }
   return $output;
 }
diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/AttributesUnitTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/AttributesUnitTest.php
index 30316252ace4..22ae4edeb16a 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Common/AttributesUnitTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Common/AttributesUnitTest.php
@@ -7,16 +7,17 @@
 
 namespace Drupal\system\Tests\Common;
 
+use Drupal\Core\Template\Attribute;
 use Drupal\simpletest\UnitTestBase;
 
 /**
- * Tests the drupal_attributes() functionality.
+ * Tests the Drupal\Core\Template\Attribute functionality.
  */
 class AttributesUnitTest extends UnitTestBase {
   public static function getInfo() {
     return array(
       'name' => 'HTML Attributes',
-      'description' => 'Tests the drupal_attributes() functionality.',
+      'description' => 'Tests the Drupal\Core\Template\Attribute functionality.',
       'group' => 'Common',
     );
   }
@@ -26,15 +27,15 @@ public static function getInfo() {
    */
   function testDrupalAttributes() {
     // Verify that special characters are HTML encoded.
-    $this->assertIdentical((string) drupal_attributes(array('title' => '&"\'<>')), ' title="&amp;&quot;&#039;&lt;&gt;"', t('HTML encode attribute values.'));
+    $this->assertIdentical((string) new Attribute(array('title' => '&"\'<>')), ' title="&amp;&quot;&#039;&lt;&gt;"', t('HTML encode attribute values.'));
 
     // Verify multi-value attributes are concatenated with spaces.
     $attributes = array('class' => array('first', 'last'));
-    $this->assertIdentical((string) drupal_attributes(array('class' => array('first', 'last'))), ' class="first last"', t('Concatenate multi-value attributes.'));
+    $this->assertIdentical((string) new Attribute(array('class' => array('first', 'last'))), ' class="first last"', t('Concatenate multi-value attributes.'));
 
     // Verify empty attribute values are rendered.
-    $this->assertIdentical((string) drupal_attributes(array('alt' => '')), ' alt=""', t('Empty attribute value #1.'));
-    $this->assertIdentical((string) drupal_attributes(array('alt' => NULL)), ' alt=""', t('Empty attribute value #2.'));
+    $this->assertIdentical((string) new Attribute(array('alt' => '')), ' alt=""', t('Empty attribute value #1.'));
+    $this->assertIdentical((string) new Attribute(array('alt' => NULL)), ' alt=""', t('Empty attribute value #2.'));
 
     // Verify multiple attributes are rendered.
     $attributes = array(
@@ -42,9 +43,9 @@ function testDrupalAttributes() {
       'class' => array('first', 'last'),
       'alt' => 'Alternate',
     );
-    $this->assertIdentical((string) drupal_attributes($attributes), ' id="id-test" class="first last" alt="Alternate"', t('Multiple attributes.'));
+    $this->assertIdentical((string) new Attribute($attributes), ' id="id-test" class="first last" alt="Alternate"', t('Multiple attributes.'));
 
     // Verify empty attributes array is rendered.
-    $this->assertIdentical((string) drupal_attributes(array()), '', t('Empty attributes array.'));
+    $this->assertIdentical((string) new Attribute(array()), '', t('Empty attributes array.'));
   }
 }
diff --git a/core/modules/user/user.module b/core/modules/user/user.module
index a526b20c3470..9dd47da3c531 100644
--- a/core/modules/user/user.module
+++ b/core/modules/user/user.module
@@ -3,6 +3,7 @@
 use Drupal\Core\Database\Query\SelectInterface;
 use Drupal\entity\EntityInterface;
 use Drupal\file\File;
+use Drupal\Core\Template\Attribute;
 use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
 
 /**
@@ -1059,8 +1060,8 @@ function template_process_username(&$variables) {
  *     other desired page to link to for more information about the user.
  *   - link_options: An array of options to pass to the l() function's $options
  *     parameter if linking the user's name to the user's page.
- *   - attributes: An array of attributes to pass to the
- *     drupal_attributes() function if not linking to the user's page.
+ *   - attributes: An array of attributes to instantiate the
+ *     Drupal\Core\Template\Attribute class if not linking to the user's page.
  *
  * @see template_preprocess_username()
  * @see template_process_username()
@@ -1076,7 +1077,7 @@ function theme_username($variables) {
     // Modules may have added important attributes so they must be included
     // in the output. Additional classes may be added as array elements like
     // $variables['attributes']['class'][] = 'myclass';
-    $output = '<span' . drupal_attributes($variables['attributes']) . '>' . $variables['name'] . $variables['extra'] . '</span>';
+    $output = '<span' . new Attribute($variables['attributes']) . '>' . $variables['name'] . $variables['extra'] . '</span>';
   }
   return $output;
 }
-- 
GitLab