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 '' . 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 @@ + 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/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 {