From f484299c2914bbda554369ab65ec155d5216b5e4 Mon Sep 17 00:00:00 2001
From: niklas <niklas@1089248.no-reply.drupal.org>
Date: Sat, 5 May 2012 21:07:17 +0200
Subject: [PATCH 1/2] - #1445224 by Niklas Fiekas: Added new HTML5 FAPI
 element: color.

---
 core/includes/common.inc                           |    3 +
 core/includes/form.inc                             |   41 +++++
 core/lib/Drupal/Core/Utility/Color.php             |  179 ++++++++++++++++++++
 core/modules/simpletest/drupal_web_test_case.php   |    1 +
 core/modules/system/system.module                  |    8 +
 core/modules/system/tests/common.test              |   61 +++++++
 core/modules/system/tests/form.test                |   37 ++++-
 .../tests/modules/form_test/form_test.module       |   42 +++++
 core/themes/bartik/css/style.css                   |    1 +
 core/themes/seven/style.css                        |    3 +
 10 files changed, 375 insertions(+), 1 deletions(-)
 create mode 100644 core/lib/Drupal/Core/Utility/Color.php

diff --git a/core/includes/common.inc b/core/includes/common.inc
index 14154af..845b38b 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -7019,6 +7019,9 @@ function drupal_common_theme() {
     'range' => array(
       'render element' => 'element',
     ),
+    'color' => array(
+      'render element' => 'element',
+    ),
     'form' => array(
       'render element' => 'element',
     ),
diff --git a/core/includes/form.inc b/core/includes/form.inc
index c387384..5bdaf90 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -5,6 +5,8 @@
  * Functions for form and batch generation and processing.
  */
 
+use Drupal\Core\Utility\Color;
+
 /**
  * @defgroup forms Form builder functions
  * @{
@@ -4086,6 +4088,45 @@ function form_validate_url(&$element, &$form_state) {
 }
 
 /**
+ * Form element validation handler for #type 'color'.
+ */
+function form_validate_color(&$element, &$form_state) {
+  // Empty means black.
+  $value = trim($element['#value']);
+  if ($value === '') {
+    $value = '#000000';
+  }
+
+  // Try to parse the value.
+  if ($parsed = Color::parseHex($value)) {
+    // Set a normalized value.
+    form_set_value($element, $parsed->__toString(), $form_state);
+  }
+  else {
+    form_error($element, t('%name must be a valid color.', array('%name' => empty($element['#title']) ? $element['#parents'][0] : $element['#title'])));
+  }
+}
+
+/**
+ * Returns HTML for a color form element.
+ *
+ * @param $variables
+ *   An associative array containing:
+ *   - element: An associative array containing the properties of the element.
+ *     Properties used: #title, #value, #description, #attributes.
+ *
+ * @ingroup themeable
+ */
+function theme_color($variables) {
+  $element = $variables['element'];
+  $element['#attributes']['type'] = 'color';
+  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);
+}
+
+/**
  * Returns HTML for a form.
  *
  * @param $variables
diff --git a/core/lib/Drupal/Core/Utility/Color.php b/core/lib/Drupal/Core/Utility/Color.php
new file mode 100644
index 0000000..472d223
--- /dev/null
+++ b/core/lib/Drupal/Core/Utility/Color.php
@@ -0,0 +1,179 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Utility\Color.
+ */
+
+namespace Drupal\Core\Utility;
+
+/**
+ * Represents a color.
+ */
+class Color {
+
+  /**
+   * The red component of the color as an integer between 0 and 255.
+   *
+   * @var int
+   */
+  protected $red;
+
+  /**
+   * The green component of the color as an integer between 0 and 255.
+   *
+   * @var int
+   */
+  protected $green;
+
+  /**
+   * The blue component of the color as an integer between 0 and 255.
+   *
+   * @var int
+   */
+  protected $blue;
+
+  /**
+   * The alpha component of the color as an integer between 0 and 127, where
+   * 0 is opaque and 127 is fully transparent.
+   *
+   * @var int
+   */
+  protected $alpha;
+
+  /**
+   * Constructs a Color object.
+   *
+   * @param int $red
+   *   The red component of the color as an integer between 0 and 255.
+   * @param int $green
+   *   The green component of the color as an integer between 0 and 255.
+   * @param int $blue
+   *   The blue component of the color as an integer between 0 and 255.
+   * @param int $alpha
+   *   Optional. The alpha component of the color as an integer between 0 and
+   *   127, where 0 is opaque and 127 is fully transparent. Defaults to 0.
+   *
+   * @throws \InvalidArgumentException
+   *   When one of the components has an invalid value.
+   */
+  public function __construct($red, $green, $blue, $alpha = 0) {
+    if ($red < 0 || $green < 0 || $blue < 0 || $alpha < 0) {
+      throw new \InvalidArgumentException('Color components must be positive integers.');
+    }
+
+    if ($red > 255 || $green > 255 || $blue > 255) {
+      throw new \InvalidArgumentException('Color components must be between 0 and 255.');
+    }
+
+    if ($alpha > 127) {
+      throw new \InvalidArgumentException('Alpha component must be between 0 and 127.');
+    }
+
+    $this->red = (int) $red;
+    $this->green = (int) $green;
+    $this->blue = (int) $blue;
+    $this->alpha = (int) $alpha;
+  }
+
+  /**
+   * Implements PHP magic __toString method to convert the color to a string.
+   *
+   * @return string
+   *   A hexadecimal representation of the color like '#aabbcc' or '#aabbcc55'.
+   */
+  public function __toString() {
+    $result = '#';
+
+    foreach (array('red', 'green', 'blue', 'alpha') as $component) {
+      if ($component != 'alpha' || $this->{$component}) {
+        $result .= str_pad(dechex($this->{$component}), 2, '0', STR_PAD_LEFT);
+      }
+    }
+
+    return $result;
+  }
+
+  /**
+   * Gets the red component of the color.
+   *
+   * @return int
+   *   The red component of the color as an integer between 0 and 255.
+   */
+  public function getRed() {
+    return $this->red;
+  }
+
+  /**
+   * Gets the green component of the color.
+   *
+   * @return int
+   *   The green component of the color as an integer between 0 and 255.
+   */
+  public function getGreen() {
+    return $this->green;
+  }
+
+  /**
+   * Gets the blue component of the color.
+   *
+   * @return int
+   *   The blue component of the color as an integer between 0 and 255.
+   */
+  public function getBlue() {
+    return $this->blue;
+  }
+
+  /**
+   * Gets the alpha component of the color.
+   *
+   * @return int
+   *   The alpha component of the color as an integer between 0 and 127, where
+   *   0 is opaque and 127 is fully transparent.
+   */
+  public function getAlpha() {
+    return $this->alpha;
+  }
+
+  /**
+   * Gets the decimal alpha component of the color.
+   *
+   * @return float
+   *   The alpha component of the color as a float between 0 and 1, where 0 is
+   *   fully transparent and 1 is opaque.
+   */
+  public function getDecimalAlpha() {
+    return 1 - $alpha / 127.0;
+  }
+
+  /**
+   * Parses a hexadecimal color string like '#abc' or '#aabbcc'.
+   *
+   * @param string $hex
+   *   The hexadecimal colorstring to parse.
+   * @param bool $allow_alpha
+   *   Optional. Whether or not to allow an alpha component. Defaults to FALSE.
+   *
+   * @return false|Drupal\Core\Utility\Color
+   *   The color object representation of the string or FALSE, if the string is
+   *   invalid.
+   */
+  public static function parseHex($hex, $allow_alpha = FALSE) {
+    $hex = ltrim($hex, '#');
+
+    // Expand shorthands like 'abc' to 'aabbcc'.
+    if (strlen($hex) < 4) {
+      $hex = preg_replace('|([0-9a-f])|i', '\1\1', $hex);
+    }
+
+    if (!preg_match('/^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-7][0-9a-f])?$/i', $hex, $rgba)) {
+      return FALSE;
+    }
+
+    if (!$allow_alpha && isset($rgba[4])) {
+      return FALSE;
+    }
+
+    return new Color(hexdec($rgba[1]), hexdec($rgba[2]), hexdec($rgba[3]), isset($rgba[4]) ? hexdec($rgba[4]) : 0);
+  }
+}
diff --git a/core/modules/simpletest/drupal_web_test_case.php b/core/modules/simpletest/drupal_web_test_case.php
index 23fa526..6d563f5 100644
--- a/core/modules/simpletest/drupal_web_test_case.php
+++ b/core/modules/simpletest/drupal_web_test_case.php
@@ -2321,6 +2321,7 @@ class DrupalWebTestCase extends DrupalTestCase {
           case 'url':
           case 'number':
           case 'range':
+          case 'color':
           case 'hidden':
           case 'password':
           case 'email':
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 69ee2af..8d5f09e 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -425,6 +425,14 @@ function system_element_info() {
     '#theme' => 'range',
     '#theme_wrappers' => array('form_element'),
   );
+  $types['color'] = array(
+    '#input' => TRUE,
+    '#default_value' => '#000000',
+    '#process' => array('ajax_process_form'),
+    '#element_validate' => array('form_validate_color'),
+    '#theme' => 'color',
+    '#theme_wrappers' => array('form_element'),
+  );
   $types['machine_name'] = array(
     '#input' => TRUE,
     '#default_value' => NULL,
diff --git a/core/modules/system/tests/common.test b/core/modules/system/tests/common.test
index 46b379e..e20f874 100644
--- a/core/modules/system/tests/common.test
+++ b/core/modules/system/tests/common.test
@@ -2050,6 +2050,67 @@ class CommonValidNumberStepUnitTestCase extends DrupalUnitTestCase {
 }
 
 /**
+ * Tests color conversion functions.
+ */
+class CommonColorConversionTestCase extends DrupalUnitTestCase {
+  public static function getInfo() {
+    return array(
+      'name' => 'Color conversion',
+      'description' => 'Tests color conversion by drupal_hex_to_rgba() and drupal_rgba_to_hex()',
+      'group' => 'Common',
+    );
+  }
+
+  /**
+   * Tests drupal_hex_to_rgba().
+   */
+  function testDrupalHexToRGBA() {
+    // Test shorthand conversion without alpha.
+    $this->assertEqual(drupal_hex_to_rgba('#000'), array('red' => 0, 'green' => 0, 'blue' => 0, 'alpha' => 0));
+    $this->assertEqual(drupal_hex_to_rgba('#fff'), array('red' => 255, 'green' => 255, 'blue' => 255, 'alpha' => 0));
+    $this->assertEqual(drupal_hex_to_rgba('#abc'), array('red' => 170, 'green' => 187, 'blue' => 204, 'alpha' => 0));
+    $this->assertEqual(drupal_hex_to_rgba('cba'), array('red' => 204, 'green' => 187, 'blue' => 170, 'alpha' => 0));
+
+    // Test shorthand conversion with alpha.
+    $this->assertEqual(drupal_hex_to_rgba('#0007', TRUE), array('red' => 0, 'green' => 0, 'blue' => 0, 'alpha' => 119));
+    $this->assertEqual(drupal_hex_to_rgba('#7001', TRUE), array('red' => 119, 'green' => 0, 'blue' => 0, 'alpha' => 17));
+    $this->assertEqual(drupal_hex_to_rgba('2000', TRUE), array('red' => 34, 'green' => 0, 'blue' => 0, 'alpha' => 0));
+
+    // Test conversion without alpha.
+    $this->assertEqual(drupal_hex_to_rgba('#000000'), array('red' => 0, 'green' => 0, 'blue' => 0, 'alpha' => 0));
+    $this->assertEqual(drupal_hex_to_rgba('#010203'), array('red' => 1, 'green' => 2, 'blue' => 3, 'alpha' => 0));
+
+    // Test too high alpha values.
+    $this->assertIdentical(drupal_hex_to_rgba('#0008', TRUE), FALSE);
+    $this->assertIdentical(drupal_hex_to_rgba('#a00f', TRUE), FALSE);
+    $this->assertIdentical(drupal_hex_to_rgba('#aa00aaf1', TRUE), FALSE);
+
+    // Test alpha values are invalid if they are not allowed.
+    $this->assertIdentical(drupal_hex_to_rgba('#1111'), FALSE);
+    $this->assertIdentical(drupal_hex_to_rgba('#22334455'), FALSE);
+    $this->assertIdentical(drupal_hex_to_rgba('#0000'), FALSE);
+
+    // Test bogus input.
+    $this->assertIdentical(drupal_hex_to_rgba('#foo'), FALSE);
+    $this->assertIdentical(drupal_hex_to_rgba('123456789'), FALSE);
+  }
+
+  /**
+   * Tests drupal_rgba_to_hex().
+   */
+  function testDrupalRGBAToHex() {
+    // Test conversion without alpha.
+    $this->assertEqual(drupal_rgba_to_hex(array('red' => 7)), '#070000');
+    $this->assertEqual(drupal_rgba_to_hex(array('green' => 255)), '#00ff00');
+    $this->assertEqual(drupal_rgba_to_hex(array('red' => 1, 'green' => 2, 'blue' => 3)), '#010203');
+
+    // Test conversion with alpha.
+    $this->assertEqual(drupal_rgba_to_hex(array('alpha' => 10)), '#0000000a');
+    $this->assertEqual(drupal_rgba_to_hex(array('green' => 15, 'alpha' => 3)), '#000f0003');
+  }
+}
+
+/**
  * Tests writing of data records with drupal_write_record().
  */
 class CommonDrupalWriteRecordTestCase extends DrupalWebTestCase {
diff --git a/core/modules/system/tests/form.test b/core/modules/system/tests/form.test
index b64fdd7..3e31dc7 100644
--- a/core/modules/system/tests/form.test
+++ b/core/modules/system/tests/form.test
@@ -366,6 +366,41 @@ class FormsTestCase extends DrupalWebTestCase {
   }
 
   /**
+   * Tests validation of #type 'color' elements.
+   */
+  function testColorValidation() {
+    // Keys are inputs, values are expected results.
+    $values = array(
+      '' => '#000000',
+      '#000' => '#000000',
+      'AAA' => '#aaaaaa',
+      '#af0DEE' => '#af0dee',
+      '#99ccBc' => '#99ccbc',
+      '#aabbcc' => '#aabbcc',
+      '123456' => '#123456',
+    );
+
+    // Tests that valid values are properly normalized.
+    foreach ($values as $input => $expected) {
+      $edit = array(
+        'color' => $input,
+      );
+      $result = json_decode($this->drupalPost('form-test/color', $edit, 'Submit'));
+      $this->assertEqual($result->color, $expected);
+    }
+
+    // Tests invalid values are rejected.
+    $values = array('#0008', '#1234', '#fffffg', '#abcdef22', '17', '#uaa');
+    foreach ($values as $input) {
+      $edit = array(
+        'color' => $input,
+      );
+      $this->drupalPost('form-test/color', $edit, 'Submit');
+      $this->assertRaw(t('%name must be a valid color.', array('%name' => 'Color')));
+    }
+  }
+
+  /**
    * Test handling of disabled elements.
    *
    * @see _form_test_disabled_elements()
@@ -408,7 +443,7 @@ class FormsTestCase extends DrupalWebTestCase {
 
     // All the elements should be marked as disabled, including the ones below
     // the disabled container.
-    $this->assertEqual(count($disabled_elements), 39, 'The correct elements have the disabled property in the HTML code.');
+    $this->assertEqual(count($disabled_elements), 40, 'The correct elements have the disabled property in the HTML code.');
 
     $this->drupalPost(NULL, $edit, t('Submit'));
     $returned_values['hijacked'] = drupal_json_decode($this->content);
diff --git a/core/modules/system/tests/modules/form_test/form_test.module b/core/modules/system/tests/modules/form_test/form_test.module
index f42b5f5..141c6d0 100644
--- a/core/modules/system/tests/modules/form_test/form_test.module
+++ b/core/modules/system/tests/modules/form_test/form_test.module
@@ -145,6 +145,12 @@ function form_test_menu() {
     'page arguments' => array('form_test_number', 'range'),
     'access callback' => TRUE,
   );
+  $items['form-test/color'] = array(
+    'title' => 'Color',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('form_test_color'),
+    'access callback' => TRUE,
+  );
   $items['form-test/checkboxes-radios'] = array(
     'title' => t('Checkboxes, Radios'),
     'page callback' => 'drupal_get_form',
@@ -1277,6 +1283,32 @@ function form_test_number($form, &$form_state, $element = 'number') {
 }
 
 /**
+ * Form constructor for testing #type 'color' elements.
+ *
+ * @see form_test_color_submit()
+ * @ingroup forms
+ */
+function form_test_color($form, &$form_state) {
+  $form['color'] = array(
+    '#type' => 'color',
+    '#title' => 'Color',
+  );
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => 'Submit',
+  );
+  return $form;
+}
+
+/**
+ * Form submission handler for form_test_color().
+ */
+function form_test_color_submit($form, &$form_state) {
+  drupal_json_output($form_state['values']);
+  exit;
+}
+
+/**
  * Builds a form to test the placeholder attribute.
  */
 function form_test_placeholder_test($form, &$form_state) {
@@ -1496,6 +1528,16 @@ function _form_test_disabled_elements($form, &$form_state) {
     );
   }
 
+  // Color.
+  $form['color'] = array(
+    '#type' => 'color',
+    '#title' => 'color',
+    '#disabled' => TRUE,
+    '#default_value' => '#0000ff',
+    '#test_hijack_value' => '#ff0000',
+    '#disabled' => TRUE,
+  );
+
   // Date.
   $form['date'] = array(
     '#type' => 'date',
diff --git a/core/themes/bartik/css/style.css b/core/themes/bartik/css/style.css
index 7ab3d4e..3563b44 100644
--- a/core/themes/bartik/css/style.css
+++ b/core/themes/bartik/css/style.css
@@ -1194,6 +1194,7 @@ input.form-email,
 input.form-url,
 input.form-search,
 input.form-number,
+input.form-color,
 textarea.form-textarea,
 select.form-select {
   border: 1px solid #ccc;
diff --git a/core/themes/seven/style.css b/core/themes/seven/style.css
index a3d6773..9e807d5 100644
--- a/core/themes/seven/style.css
+++ b/core/themes/seven/style.css
@@ -612,6 +612,7 @@ div.teaser-checkbox .form-item,
 .form-disabled input.form-url,
 .form-disabled input.form-search,
 .form-disabled input.form-number,
+.form-disabled input.form-color,
 .form-disabled input.form-file,
 .form-disabled textarea.form-textarea,
 .form-disabled select.form-select {
@@ -703,6 +704,7 @@ input.form-email,
 input.form-url,
 input.form-search,
 input.form-number,
+input.form-color,
 input.form-file,
 textarea.form-textarea,
 select.form-select {
@@ -721,6 +723,7 @@ input.form-email:focus,
 input.form-url:focus,
 input.form-search:focus,
 input.form-number:focus,
+input.form-color:focus,
 input.form-file:focus,
 textarea.form-textarea:focus,
 select.form-select:focus {
-- 
1.7.6.msysgit.0


From 9946d60e6965da8448ff8ae0c9d736669b4bd3bd Mon Sep 17 00:00:00 2001
From: sun <sun@unleashedmind.com>
Date: Sun, 6 May 2012 01:59:16 +0200
Subject: [PATCH 2/2] Refactored Color class methods; added massive test
 coverage.

---
 core/includes/form.inc                 |    7 +-
 core/lib/Drupal/Core/Utility/Color.php |  224 ++++++++++++--------------------
 core/modules/system/system.module      |    1 -
 core/modules/system/tests/common.test  |  167 ++++++++++++++++++------
 4 files changed, 219 insertions(+), 180 deletions(-)

diff --git a/core/includes/form.inc b/core/includes/form.inc
index 5bdaf90..45bf8fe 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -4092,18 +4092,21 @@ function form_validate_url(&$element, &$form_state) {
  */
 function form_validate_color(&$element, &$form_state) {
   // Empty means black.
+  // @todo This default looks suspicious/bogus.
   $value = trim($element['#value']);
   if ($value === '') {
     $value = '#000000';
   }
 
   // Try to parse the value.
+  // @todo Catch exceptions.
   if ($parsed = Color::parseHex($value)) {
+    // @todo Leave to value consumers...? (this destroys the original value)
     // Set a normalized value.
-    form_set_value($element, $parsed->__toString(), $form_state);
+    form_set_value($element, $parsed, $form_state);
   }
   else {
-    form_error($element, t('%name must be a valid color.', array('%name' => empty($element['#title']) ? $element['#parents'][0] : $element['#title'])));
+    form_error($element, t('The %name color %color is not valid.', array('%name' => empty($element['#title']) ? $element['#parents'][0] : $element['#title'], '%color' => $value)));
   }
 }
 
diff --git a/core/lib/Drupal/Core/Utility/Color.php b/core/lib/Drupal/Core/Utility/Color.php
index 472d223..6e1b1a9 100644
--- a/core/lib/Drupal/Core/Utility/Color.php
+++ b/core/lib/Drupal/Core/Utility/Color.php
@@ -8,172 +8,120 @@
 namespace Drupal\Core\Utility;
 
 /**
- * Represents a color.
+ * Performs color conversions.
  */
 class Color {
 
   /**
-   * The red component of the color as an integer between 0 and 255.
+   * Validates whether a hexadecimal color value is syntatically correct.
    *
-   * @var int
-   */
-  protected $red;
-
-  /**
-   * The green component of the color as an integer between 0 and 255.
-   *
-   * @var int
-   */
-  protected $green;
-
-  /**
-   * The blue component of the color as an integer between 0 and 255.
+   * @param $hex
+   *   The hexadecimal string to validate. May contain a leading '#'. May use
+   *   the shorthand notation (e.g., '123'). May contain a 4th value for the
+   *   alpha channel (ranging from 00 to FF).
    *
-   * @var int
+   * @return bool
+   *   TRUE if $hex is valid or FALSE if it is not.
    */
-  protected $blue;
+  public static function validateHex($hex) {
+    // Must be a string.
+    $valid = is_string($hex);
+    // Hash prefix is optional.
+    $hex = ltrim($hex, '#');
+    // Must be either RGB, RGBA, RRGGBB, or RRGGBBAA.
+    // RGBA and RRGGBBAA notations are not in the official HTML and CSS
+    // specifications, but supported by a range of implementations and only a
+    // pragmatic extension to the spec.
+    $length = drupal_strlen($hex);
+    $valid = $valid && ($length === 3 || $length === 4 || $length === 6 || $length === 8);
+    // Must be a valid hex value.
+    $valid = $valid && ctype_xdigit($hex);
+    return $valid;
+  }
 
   /**
-   * The alpha component of the color as an integer between 0 and 127, where
-   * 0 is opaque and 127 is fully transparent.
+   * Parses a hexadecimal color string like '#abc' or '#aabbcc'.
    *
-   * @var int
-   */
-  protected $alpha;
-
-  /**
-   * Constructs a Color object.
+   * @param string $hex
+   *   The hexadecimal color string to parse.
+   * @param bool $php_alpha
+   *   (optional) Whether to return the alpha channel value suitable for PHP
+   *   image functions (0 being opaque, 127 being transparent). Defaults to
+   *   FALSE, in which case alpha values range between 0.0 and 1.0.
    *
-   * @param int $red
-   *   The red component of the color as an integer between 0 and 255.
-   * @param int $green
-   *   The green component of the color as an integer between 0 and 255.
-   * @param int $blue
-   *   The blue component of the color as an integer between 0 and 255.
-   * @param int $alpha
-   *   Optional. The alpha component of the color as an integer between 0 and
-   *   127, where 0 is opaque and 127 is fully transparent. Defaults to 0.
+   * @return array|false
+   *   An array containing the values for 'red', 'green', 'blue', and 'alpha'.
    *
-   * @throws \InvalidArgumentException
-   *   When one of the components has an invalid value.
+   * @throws InvalidArgumentException
    */
-  public function __construct($red, $green, $blue, $alpha = 0) {
-    if ($red < 0 || $green < 0 || $blue < 0 || $alpha < 0) {
-      throw new \InvalidArgumentException('Color components must be positive integers.');
-    }
-
-    if ($red > 255 || $green > 255 || $blue > 255) {
-      throw new \InvalidArgumentException('Color components must be between 0 and 255.');
+  public static function hex2rgba($hex, $php_alpha = FALSE) {
+    if (!self::validateHex($hex)) {
+      throw new \InvalidArgumentException("'$hex' is not a valid hex value.");
     }
+    // Ignore '#' prefixes.
+    $hex = ltrim($hex, '#');
 
-    if ($alpha > 127) {
-      throw new \InvalidArgumentException('Alpha component must be between 0 and 127.');
+    // Convert shorhands like '#abc' to '#aabbcc'.
+    if (strlen($hex) <= 4) {
+      $hex = preg_replace('/([0-9a-z])/i', '\1\1', $hex);
     }
 
-    $this->red = (int) $red;
-    $this->green = (int) $green;
-    $this->blue = (int) $blue;
-    $this->alpha = (int) $alpha;
-  }
+    // Parse out the components.
+    preg_match('/^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})?$/i', $hex, $rgba);
 
-  /**
-   * Implements PHP magic __toString method to convert the color to a string.
-   *
-   * @return string
-   *   A hexadecimal representation of the color like '#aabbcc' or '#aabbcc55'.
-   */
-  public function __toString() {
-    $result = '#';
+    // Fill in default alpha channel value (opaque).
+    $rgba += array(4 => 'ff');
 
-    foreach (array('red', 'green', 'blue', 'alpha') as $component) {
-      if ($component != 'alpha' || $this->{$component}) {
-        $result .= str_pad(dechex($this->{$component}), 2, '0', STR_PAD_LEFT);
-      }
+    // Calculate alpha channel return value.
+    if ($php_alpha) {
+      // Hex ranges from 00 to FF, whereas 00 is transparent and FF is opaque.
+      // PHP ranges from 0 to 127, whereas 0 is opaque and 127 is transparent.
+      $rgba[4] = (int) round((255 - hexdec($rgba[4])) * 127 / 255);
+    }
+    else {
+      // Hex ranges from 00 to FF. HTML/CSS ranges from 0.0 to 1.0.
+      // Precision accounts for the minimum hex value #01 == 0.003921 == 0.004.
+      $rgba[4] = (float) round(hexdec($rgba[4]) / 255, 3);
     }
 
-    return $result;
-  }
-
-  /**
-   * Gets the red component of the color.
-   *
-   * @return int
-   *   The red component of the color as an integer between 0 and 255.
-   */
-  public function getRed() {
-    return $this->red;
-  }
-
-  /**
-   * Gets the green component of the color.
-   *
-   * @return int
-   *   The green component of the color as an integer between 0 and 255.
-   */
-  public function getGreen() {
-    return $this->green;
-  }
-
-  /**
-   * Gets the blue component of the color.
-   *
-   * @return int
-   *   The blue component of the color as an integer between 0 and 255.
-   */
-  public function getBlue() {
-    return $this->blue;
-  }
-
-  /**
-   * Gets the alpha component of the color.
-   *
-   * @return int
-   *   The alpha component of the color as an integer between 0 and 127, where
-   *   0 is opaque and 127 is fully transparent.
-   */
-  public function getAlpha() {
-    return $this->alpha;
+    return array(
+      'red' => (int) hexdec($rgba[1]),
+      'green' => (int) hexdec($rgba[2]),
+      'blue' => (int) hexdec($rgba[3]),
+      'alpha' => $rgba[4],
+    );
   }
 
-  /**
-   * Gets the decimal alpha component of the color.
-   *
-   * @return float
-   *   The alpha component of the color as a float between 0 and 1, where 0 is
-   *   fully transparent and 1 is opaque.
-   */
-  public function getDecimalAlpha() {
-    return 1 - $alpha / 127.0;
-  }
-
-  /**
-   * Parses a hexadecimal color string like '#abc' or '#aabbcc'.
-   *
-   * @param string $hex
-   *   The hexadecimal colorstring to parse.
-   * @param bool $allow_alpha
-   *   Optional. Whether or not to allow an alpha component. Defaults to FALSE.
-   *
-   * @return false|Drupal\Core\Utility\Color
-   *   The color object representation of the string or FALSE, if the string is
-   *   invalid.
-   */
-  public static function parseHex($hex, $allow_alpha = FALSE) {
-    $hex = ltrim($hex, '#');
-
-    // Expand shorthands like 'abc' to 'aabbcc'.
-    if (strlen($hex) < 4) {
-      $hex = preg_replace('|([0-9a-f])|i', '\1\1', $hex);
+  public static function rgba2hex($input, $php_alpha = FALSE) {
+    // Remove named array keys if input comes from Color::hex2rgba().
+    if (is_array($input)) {
+      $rgba = array_values($input);
+    }
+    // Parse string input in CSS notation ('10, 20, 30, 1.0').
+    elseif (is_string($input)) {
+      preg_match('/(\d+), ?(\d+), ?(\d+)(?:, ?([\d\.]+))?/', $input, $rgba);
+      array_shift($rgba);
     }
 
-    if (!preg_match('/^([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})([0-7][0-9a-f])?$/i', $hex, $rgba)) {
-      return FALSE;
+    $result = '#';
+    for ($i = 0; $i < 3; $i++) {
+      $result .= str_pad(dechex($rgba[$i]), 2, '0', STR_PAD_LEFT);
     }
 
-    if (!$allow_alpha && isset($rgba[4])) {
-      return FALSE;
+    // Only add an alpha channel value, if one was contained in the input.
+    if (isset($rgba[3])) {
+      if ($php_alpha) {
+        // PHP ranges from 0 to 127, whereas 0 is opaque and 127 is transparent.
+        // Hex ranges from 00 to FF, whereas 00 is transparent and FF is opaque.
+        $rgba[3] = dechex(255 - round($rgba[3] * 255 / 127));
+      }
+      else {
+        // HTML/CSS ranges from 0.0 to 1.0, Hex from 00 to FF.
+        $rgba[3] = dechex($rgba[3] * 255);
+      }
+      $result .= str_pad($rgba[3], 2, '0', STR_PAD_LEFT);
     }
 
-    return new Color(hexdec($rgba[1]), hexdec($rgba[2]), hexdec($rgba[3]), isset($rgba[4]) ? hexdec($rgba[4]) : 0);
+    return $result;
   }
 }
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 8d5f09e..af5ab2d 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -427,7 +427,6 @@ function system_element_info() {
   );
   $types['color'] = array(
     '#input' => TRUE,
-    '#default_value' => '#000000',
     '#process' => array('ajax_process_form'),
     '#element_validate' => array('form_validate_color'),
     '#theme' => 'color',
diff --git a/core/modules/system/tests/common.test b/core/modules/system/tests/common.test
index e20f874..de4caf6 100644
--- a/core/modules/system/tests/common.test
+++ b/core/modules/system/tests/common.test
@@ -5,6 +5,8 @@
  * Tests for common.inc functionality.
  */
 
+use Drupal\Core\Utility\Color;
+
 /**
  * Tests for URL generation functions.
  */
@@ -2056,57 +2058,144 @@ class CommonColorConversionTestCase extends DrupalUnitTestCase {
   public static function getInfo() {
     return array(
       'name' => 'Color conversion',
-      'description' => 'Tests color conversion by drupal_hex_to_rgba() and drupal_rgba_to_hex()',
+      'description' => 'Tests Color utility class conversions.',
       'group' => 'Common',
     );
   }
 
   /**
-   * Tests drupal_hex_to_rgba().
+   * Tests Color::hex2rgba().
    */
-  function testDrupalHexToRGBA() {
-    // Test shorthand conversion without alpha.
-    $this->assertEqual(drupal_hex_to_rgba('#000'), array('red' => 0, 'green' => 0, 'blue' => 0, 'alpha' => 0));
-    $this->assertEqual(drupal_hex_to_rgba('#fff'), array('red' => 255, 'green' => 255, 'blue' => 255, 'alpha' => 0));
-    $this->assertEqual(drupal_hex_to_rgba('#abc'), array('red' => 170, 'green' => 187, 'blue' => 204, 'alpha' => 0));
-    $this->assertEqual(drupal_hex_to_rgba('cba'), array('red' => 204, 'green' => 187, 'blue' => 170, 'alpha' => 0));
-
-    // Test shorthand conversion with alpha.
-    $this->assertEqual(drupal_hex_to_rgba('#0007', TRUE), array('red' => 0, 'green' => 0, 'blue' => 0, 'alpha' => 119));
-    $this->assertEqual(drupal_hex_to_rgba('#7001', TRUE), array('red' => 119, 'green' => 0, 'blue' => 0, 'alpha' => 17));
-    $this->assertEqual(drupal_hex_to_rgba('2000', TRUE), array('red' => 34, 'green' => 0, 'blue' => 0, 'alpha' => 0));
-
-    // Test conversion without alpha.
-    $this->assertEqual(drupal_hex_to_rgba('#000000'), array('red' => 0, 'green' => 0, 'blue' => 0, 'alpha' => 0));
-    $this->assertEqual(drupal_hex_to_rgba('#010203'), array('red' => 1, 'green' => 2, 'blue' => 3, 'alpha' => 0));
-
-    // Test too high alpha values.
-    $this->assertIdentical(drupal_hex_to_rgba('#0008', TRUE), FALSE);
-    $this->assertIdentical(drupal_hex_to_rgba('#a00f', TRUE), FALSE);
-    $this->assertIdentical(drupal_hex_to_rgba('#aa00aaf1', TRUE), FALSE);
+  function testHexToRGBA() {
+    // Any invalid arguments should throw an exception.
+    $values = array('', '-1', '1', '12', '12345', '1234567', '123456789', '123456789a', 'foo');
+    // Duplicate all invalid value tests with additional '#' prefix.
+    // The '#' prefix inherently turns the data type into a string.
+    foreach ($values as $value) {
+      $values[] = '#' . $value;
+    }
+    // Add invalid data types (hex value must be a string).
+    $values = array_merge($values, array(
+      1, 12, 1234, 12345, 123456, 1234567, 12345678, 123456789, 123456789,
+      -1, PHP_INT_MAX, PHP_INT_MAX + 1, -PHP_INT_MAX,
+      0x0, 0x010,
+    ));
 
-    // Test alpha values are invalid if they are not allowed.
-    $this->assertIdentical(drupal_hex_to_rgba('#1111'), FALSE);
-    $this->assertIdentical(drupal_hex_to_rgba('#22334455'), FALSE);
-    $this->assertIdentical(drupal_hex_to_rgba('#0000'), FALSE);
+    foreach ($values as $test) {
+      $this->assertFalse(Color::validateHex($test), var_export($test, TRUE) . ' is invalid.');
+      try {
+        Color::hex2rgba($test);
+        $this->fail('Color::hex2rgba(' . var_export($test, TRUE) . ') did not throw an exception.');
+      }
+      // @todo InvalidArgumentException should be sufficient here, but the
+      //   exception handler tries to log the exception in the database, which
+      //   triggers a registry lookup, but the {registry} table does not exist
+      //   in unit tests. We therefore have to catch all exceptions.
+      // @see http://drupal.org/node/1563620
+      catch (Exception $e) {
+        $this->pass('Color::hex2rgba(' . var_export($test, TRUE) . ') threw an exception.');
+      }
+    }
 
-    // Test bogus input.
-    $this->assertIdentical(drupal_hex_to_rgba('#foo'), FALSE);
-    $this->assertIdentical(drupal_hex_to_rgba('123456789'), FALSE);
+    // PHP automatically casts a numeric array key into an integer.
+    // Since hex values may consist of 0-9 only, they need to be defined as
+    // array values.
+    $tests = array(
+      // Shorthands without alpha.
+      array('hex' => '#000', 'rgba' => array('red' => 0, 'green' => 0, 'blue' => 0, 'alpha' => 1.0, 'php_alpha' => 0)),
+      array('hex' => '#fff', 'rgba' => array('red' => 255, 'green' => 255, 'blue' => 255, 'alpha' => 1.0, 'php_alpha' => 0)),
+      array('hex' => '#abc', 'rgba' => array('red' => 170, 'green' => 187, 'blue' => 204, 'alpha' => 1.0, 'php_alpha' => 0)),
+      array('hex' => 'cba', 'rgba' => array('red' => 204, 'green' => 187, 'blue' => 170, 'alpha' => 1.0, 'php_alpha' => 0)),
+      // Shorthands with alpha.
+      array('hex' => '#0000', 'rgba' => array('red' => 0, 'green' => 0, 'blue' => 0, 'alpha' => 0.0, 'php_alpha' => 127)),
+      array('hex' => '#ffff', 'rgba' => array('red' => 255, 'green' => 255, 'blue' => 255, 'alpha' => 1.0, 'php_alpha' => 0)),
+      array('hex' => '#7777', 'rgba' => array('red' => 119, 'green' => 119, 'blue' => 119, 'alpha' => 0.467, 'php_alpha' => 68)),
+      array('hex' => '#1111', 'rgba' => array('red' => 17, 'green' => 17, 'blue' => 17, 'alpha' => 0.067, 'php_alpha' => 119)),
+      array('hex' => '2000', 'rgba' => array('red' => 34, 'green' => 0, 'blue' => 0, 'alpha' => 0.0, 'php_alpha' => 127)),
+      // Full without alpha.
+      array('hex' => '#000000', 'rgba' => array('red' => 0, 'green' => 0, 'blue' => 0, 'alpha' => 1.0, 'php_alpha' => 0)),
+      array('hex' => '#ffffff', 'rgba' => array('red' => 255, 'green' => 255, 'blue' => 255, 'alpha' => 1.0, 'php_alpha' => 0)),
+      array('hex' => '#010203', 'rgba' => array('red' => 1, 'green' => 2, 'blue' => 3, 'alpha' => 1.0, 'php_alpha' => 0)),
+      // Full with alpha.
+      array('hex' => '#00000000', 'rgba' => array('red' => 0, 'green' => 0, 'blue' => 0, 'alpha' => 0.0, 'php_alpha' => 127)),
+      array('hex' => '#ffffffff', 'rgba' => array('red' => 255, 'green' => 255, 'blue' => 255, 'alpha' => 1.0, 'php_alpha' => 0)),
+      array('hex' => '#77777777', 'rgba' => array('red' => 119, 'green' => 119, 'blue' => 119, 'alpha' => 0.467, 'php_alpha' => 68)),
+      array('hex' => '#01020304', 'rgba' => array('red' => 1, 'green' => 2, 'blue' => 3, 'alpha' => 0.016, 'php_alpha' => 125)),
+    );
+    foreach ($tests as $test) {
+      $hex = $test['hex'];
+      $expected = $test['rgba'];
+      $expected_php_alpha = $expected['php_alpha'];
+      unset($expected['php_alpha']);
+      try {
+        $result = Color::hex2rgba($hex);
+        $this->assertIdentical($result, $expected);
+        // Assert the PHP specific alpha channel return value.
+        // 127 is transparent, 0 is opaque.
+        // @see imagecolorallocatealpha()
+        $result = Color::hex2rgba($hex, TRUE);
+        $expected['alpha'] = $expected_php_alpha;
+        $this->assertIdentical($result, $expected);
+      }
+      catch (Exception $e) {
+        $this->fail($e->getMessage(), 'Exception');
+      }
+    }
   }
 
   /**
-   * Tests drupal_rgba_to_hex().
+   * Tests Color::rgba2hex().
    */
-  function testDrupalRGBAToHex() {
-    // Test conversion without alpha.
-    $this->assertEqual(drupal_rgba_to_hex(array('red' => 7)), '#070000');
-    $this->assertEqual(drupal_rgba_to_hex(array('green' => 255)), '#00ff00');
-    $this->assertEqual(drupal_rgba_to_hex(array('red' => 1, 'green' => 2, 'blue' => 3)), '#010203');
-
-    // Test conversion with alpha.
-    $this->assertEqual(drupal_rgba_to_hex(array('alpha' => 10)), '#0000000a');
-    $this->assertEqual(drupal_rgba_to_hex(array('green' => 15, 'alpha' => 3)), '#000f0003');
+  function testRGBAToHex() {
+    $tests = array(
+      '#00000000' => array('red' => 0, 'green' => 0, 'blue' => 0, 'alpha' => 0.0, 'php_alpha' => 127),
+      '#ffffffff' => array('red' => 255, 'green' => 255, 'blue' => 255, 'alpha' => 1.0, 'php_alpha' => 0),
+      '#77777778' => array('red' => 119, 'green' => 119, 'blue' => 119, 'alpha' => 0.471, 'php_alpha' => 67),
+      '#01020304' => array('red' => 1, 'green' => 2, 'blue' => 3, 'alpha' => 0.016, 'php_alpha' => 125),
+    );
+    // Input using named RGBA array (e.g., as returned by Color::hex2rgba()).
+    foreach ($tests as $expected => $rgba) {
+      unset($rgba['php_alpha']);
+      $this->assertIdentical(Color::rgba2hex($rgba), $expected);
+    }
+    // Input using named RGB array.
+    foreach ($tests as $expected => $rgba) {
+      unset($rgba['alpha'], $rgba['php_alpha']);
+      $expected = substr($expected, 0, 7);
+      $this->assertIdentical(Color::rgba2hex($rgba), $expected);
+    }
+    // Input using indexed RGBA array (e.g.: array(10, 10, 10, 0.0)).
+    foreach ($tests as $expected => $rgba) {
+      unset($rgba['php_alpha']);
+      $rgba = array_values($rgba);
+      $this->assertIdentical(Color::rgba2hex($rgba), $expected);
+    }
+    // Input using indexed RGB array.
+    foreach ($tests as $expected => $rgba) {
+      unset($rgba['alpha'], $rgba['php_alpha']);
+      $expected = substr($expected, 0, 7);
+      $rgba = array_values($rgba);
+      $this->assertIdentical(Color::rgba2hex($rgba), $expected);
+    }
+    // Input using CSS RGBA string notation (e.g.: 10, 10, 10, 0.0).
+    foreach ($tests as $expected => $rgba) {
+      unset($rgba['php_alpha']);
+      $rgba = implode(', ', $rgba);
+      $this->assertIdentical(Color::rgba2hex($rgba), $expected);
+    }
+    // Input using CSS RGB string notation (e.g.: 10, 10, 10).
+    foreach ($tests as $expected => $rgba) {
+      unset($rgba['alpha'], $rgba['php_alpha']);
+      $expected = substr($expected, 0, 7);
+      $rgba = implode(', ', $rgba);
+      $this->assertIdentical(Color::rgba2hex($rgba), $expected);
+    }
+    // Input using PHP alpha channel value.
+    foreach ($tests as $expected => $rgba) {
+      $rgba['alpha'] = $rgba['php_alpha'];
+      unset($rgba['php_alpha']);
+      $this->assertIdentical(Color::rgba2hex($rgba, TRUE), $expected);
+    }
   }
 }
 
-- 
1.7.6.msysgit.0

