diff --git a/core/includes/common.inc b/core/includes/common.inc index fb370b8..137899f 100644 --- a/core/includes/common.inc +++ b/core/includes/common.inc @@ -12,6 +12,7 @@ use Drupal\Component\Serialization\Yaml; use Drupal\Component\Serialization\Exception\InvalidDataTypeException; use Drupal\Component\Utility\Crypt; +use Drupal\Component\Utility\Html; use Drupal\Component\Utility\Number; use Drupal\Component\Utility\SortArray; use Drupal\Component\Utility\String; @@ -1529,25 +1530,12 @@ function drupal_delete_file_if_stale($uri) { * * @return * The cleaned identifier. + * + * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. + * Use \Drupal\Component\Utility\Html::cleanCssIdentifier() */ function drupal_clean_css_identifier($identifier, $filter = array(' ' => '-', '_' => '-', '__' => '__', '/' => '-', '[' => '-', ']' => '')) { - // By default, we filter using Drupal's coding standards. - $identifier = strtr($identifier, $filter); - - // Valid characters in a CSS identifier are: - // - the hyphen (U+002D) - // - a-z (U+0030 - U+0039) - // - A-Z (U+0041 - U+005A) - // - the underscore (U+005F) - // - 0-9 (U+0061 - U+007A) - // - ISO 10646 characters U+00A1 and higher - // We strip out any character not in the above list. - $identifier = preg_replace('/[^\x{002D}\x{0030}-\x{0039}\x{0041}-\x{005A}\x{005F}\x{0061}-\x{007A}\x{00A1}-\x{FFFF}]/u', '', $identifier); - - // Identifiers cannot start with a digit, two hyphens, or a hyphen followed by a digit. - $identifier = preg_replace(array('/^[0-9]/', '/^(-[0-9])|^(--)/'), array('_', '__') , $identifier); - - return $identifier; + return Html::cleanCssIdentifier($identifier, $filter); } /** @@ -1561,16 +1549,12 @@ function drupal_clean_css_identifier($identifier, $filter = array(' ' => '-', '_ * * @return * The cleaned class name. + * + * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. + * Use \Drupal\Component\Utility\Html::getClass() */ function drupal_html_class($class) { - // The output of this function will never change, so this uses a normal - // static instead of drupal_static(). - static $classes = array(); - - if (!isset($classes[$class])) { - $classes[$class] = drupal_clean_css_identifier(drupal_strtolower($class)); - } - return $classes[$class]; + return Html::getClass($class); } /** @@ -1599,66 +1583,12 @@ function drupal_html_class($class) { * * @return * The cleaned ID. + * + * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. + * Use \Drupal\Component\Utility\Html::getUniqueId() */ function drupal_html_id($id) { - // If this is an Ajax request, then content returned by this page request will - // be merged with content already on the base page. The HTML IDs must be - // unique for the fully merged content. Therefore, initialize $seen_ids to - // take into account IDs that are already in use on the base page. - $seen_ids_init = &drupal_static(__FUNCTION__ . ':init'); - if (!isset($seen_ids_init)) { - $ajax_html_ids = \Drupal::request()->request->get('ajax_html_ids'); - // Ideally, Drupal would provide an API to persist state information about - // prior page requests in the database, and we'd be able to add this - // function's $seen_ids static variable to that state information in order - // to have it properly initialized for this page request. However, no such - // page state API exists, so instead, ajax.js adds all of the in-use HTML - // IDs to the POST data of Ajax submissions. Direct use of $_POST is - // normally not recommended as it could open up security risks, but because - // the raw POST data is cast to a number before being returned by this - // function, this usage is safe. - if (empty($ajax_html_ids)) { - $seen_ids_init = array(); - } - else { - // This function ensures uniqueness by appending a counter to the base id - // requested by the calling function after the first occurrence of that - // requested id. $_POST['ajax_html_ids'] contains the ids as they were - // returned by this function, potentially with the appended counter, so - // we parse that to reconstruct the $seen_ids array. - $ajax_html_ids = explode(' ', $ajax_html_ids); - foreach ($ajax_html_ids as $seen_id) { - // We rely on '--' being used solely for separating a base id from the - // counter, which this function ensures when returning an id. - $parts = explode('--', $seen_id, 2); - if (!empty($parts[1]) && is_numeric($parts[1])) { - list($seen_id, $i) = $parts; - } - else { - $i = 1; - } - if (!isset($seen_ids_init[$seen_id]) || ($i > $seen_ids_init[$seen_id])) { - $seen_ids_init[$seen_id] = $i; - } - } - } - } - $seen_ids = &drupal_static(__FUNCTION__, $seen_ids_init); - - $id = drupal_clean_id_identifier($id); - // Ensure IDs are unique by appending a counter after the first occurrence. - // The counter needs to be appended with a delimiter that does not exist in - // the base ID. Requiring a unique delimiter helps ensure that we really do - // return unique IDs and also helps us re-create the $seen_ids array during - // Ajax requests. - if (isset($seen_ids[$id])) { - $id = $id . '--' . ++$seen_ids[$id]; - } - else { - $seen_ids[$id] = 1; - } - - return $id; + return Html::getUniqueId($id); } /** @@ -1674,21 +1604,12 @@ function drupal_html_id($id) { * The cleaned ID. * * @see drupal_html_id() + * + * @deprecated in Drupal 8.x-dev, will be removed before Drupal 8.0. + * Use \Drupal\Component\Utility\Html::getId() */ function drupal_clean_id_identifier($id) { - $id = strtr(drupal_strtolower($id), array(' ' => '-', '_' => '-', '[' => '-', ']' => '')); - - // As defined in http://www.w3.org/TR/html4/types.html#type-name, HTML IDs can - // only contain letters, digits ([0-9]), hyphens ("-"), underscores ("_"), - // colons (":"), and periods ("."). We strip out any character not in that - // list. Note that the CSS spec doesn't allow colons or periods in identifiers - // (http://www.w3.org/TR/CSS21/syndata.html#characters), so we strip those two - // characters as well. - $id = preg_replace('/[^A-Za-z0-9\-_]/', '', $id); - - // Removing multiple consecutive hyphens. - $id = preg_replace('/\-+/', '-', $id); - return $id; + return Html::getId($id); } /** diff --git a/core/lib/Drupal/Component/Utility/Html.php b/core/lib/Drupal/Component/Utility/Html.php index 2b5455f..04ce629 100644 --- a/core/lib/Drupal/Component/Utility/Html.php +++ b/core/lib/Drupal/Component/Utility/Html.php @@ -15,6 +15,212 @@ class Html { /** + * An array of previously cleaned HTML classes. + * + * @var array + */ + protected static $classes = array(); + + /** + * An array of the initial IDs used in one request. + * + * @var array + */ + protected static $seenIdsInit; + + /** + * An array of IDs, including incremented versions when an ID is duplicated. + * @var array + */ + protected static $seenIds; + + /** + * Prepares a string for use as a valid class name. + * + * Do not pass one string containing multiple classes as they will be + * incorrectly concatenated with dashes, i.e. "one two" will become "one-two". + * + * @param string $class + * The class name to clean. + * + * @return string + * The cleaned class name. + */ + public static function getClass($class) { + if (!isset(static::$classes[$class])) { + static::$classes[$class] = static::cleanCssIdentifier(Unicode::strtolower($class)); + } + return static::$classes[$class]; + } + + /** + * Prepares a string for use as a CSS identifier (element, class, or ID name). + * + * http://www.w3.org/TR/CSS21/syndata.html#characters shows the syntax for + * valid CSS identifiers (including element names, classes, and IDs in + * selectors.) + * + * @param string $identifier + * The identifier to clean. + * @param array $filter + * An array of string replacements to use on the identifier. + * + * @return string + * The cleaned identifier. + */ + public static function cleanCssIdentifier($identifier, array $filter = array( + ' ' => '-', + '_' => '-', + '__' => '__', + '/' => '-', + '[' => '-', + ']' => '' + )) { + $identifier = strtr($identifier, $filter); + // Valid characters in a CSS identifier are: + // - the hyphen (U+002D) + // - a-z (U+0030 - U+0039) + // - A-Z (U+0041 - U+005A) + // - the underscore (U+005F) + // - 0-9 (U+0061 - U+007A) + // - ISO 10646 characters U+00A1 and higher + // We strip out any character not in the above list. + $identifier = preg_replace('/[^\x{002D}\x{0030}-\x{0039}\x{0041}-\x{005A}\x{005F}\x{0061}-\x{007A}\x{00A1}-\x{FFFF}]/u', '', $identifier); + // Identifiers cannot start with a digit, two hyphens, or a hyphen followed by a digit. + $identifier = preg_replace(array( + '/^[0-9]/', + '/^(-[0-9])|^(--)/' + ), array('_', '__'), $identifier); + return $identifier; + } + + /** + * Prepares a string for use as a valid HTML ID and guarantees uniqueness. + * + * This function ensures that each passed HTML ID value only exists once on + * the page. By tracking the already returned ids, this function enables + * forms, blocks, and other content to be output multiple times on the same + * page, without breaking (X)HTML validation. + * + * For already existing IDs, a counter is appended to the ID string. + * Therefore, JavaScript and CSS code should not rely on any value that was + * generated by this function and instead should rely on manually added CSS + * classes or similarly reliable constructs. + * + * Two consecutive hyphens separate the counter from the original ID. To + * manage uniqueness across multiple Ajax requests on the same page, Ajax + * requests POST an array of all IDs currently present on the page, which are + * used to prime this function's cache upon first invocation. + * + * To allow reverse-parsing of IDs submitted via Ajax, any multiple + * consecutive hyphens in the originally passed $id are replaced with a + * single hyphen. + * + * @param string $id + * The ID to clean. + * + * @return string + * The cleaned ID. + */ + public static function getUniqueId($id) { + // If this is an Ajax request, then content returned by this page request + // will be merged with content already on the base page. The HTML IDs must + // be unique for the fully merged content. Therefore, initialize $seen_ids + // to take into account IDs that are already in use on the base page. + if (!isset(static::$seenIdsInit)) { + // Ideally, Drupal would provide an API to persist state information about + // prior page requests in the database, and we'd be able to add this + // function's $seen_ids static variable to that state information in order + // to have it properly initialized for this page request. However, no such + // page state API exists, so instead, ajax.js adds all of the in-use HTML + // IDs to the POST data of Ajax submissions. Direct use of $_POST is + // normally not recommended as it could open up security risks, but + // because the raw POST data is cast to a number before being returned by + // this function, this usage is safe. + if (empty($_POST['ajax_html_ids'])) { + static::$seenIdsInit = array(); + } + else { + // This function ensures uniqueness by appending a counter to the base + // id requested by the calling function after the first occurrence of + // that requested id. $_POST['ajax_html_ids'] contains the ids as they + // were returned by this function, potentially with the appended + // counter, so we parse that to reconstruct the $seen_ids array. + $ajax_html_ids = explode(' ', $_POST['ajax_html_ids']); + foreach ($ajax_html_ids as $seen_id) { + // We rely on '--' being used solely for separating a base id from the + // counter, which this function ensures when returning an id. + $parts = explode('--', $seen_id, 2); + if (!empty($parts[1]) && is_numeric($parts[1])) { + list($seen_id, $i) = $parts; + } + else { + $i = 1; + } + if (!isset(static::$seenIdsInit[$seen_id]) || ($i > static::$seenIdsInit[$seen_id])) { + static::$seenIdsInit[$seen_id] = $i; + } + } + } + } + if (!isset(static::$seenIds)) { + static::$seenIds = static::$seenIdsInit; + } + + $id = static::getId($id); + + // Ensure IDs are unique by appending a counter after the first occurrence. + // The counter needs to be appended with a delimiter that does not exist in + // the base ID. Requiring a unique delimiter helps ensure that we really do + // return unique IDs and also helps us re-create the $seen_ids array during + // Ajax requests. + if (isset(static::$seenIds[$id])) { + $id = $id . '--' . ++static::$seenIds[$id]; + } + else { + static::$seenIds[$id] = 1; + } + return $id; + } + + /** + * Prepares a string for use as a valid HTML ID. + * + * Only use this function when you want to intentionally skip the uniqueness + * guarantee of self::getUniqueId(). + * + * @param string $id + * The ID to clean. + * + * @return string + * The cleaned ID. + * + * @see self::getUniqueId() + */ + public static function getId($id) { + $id = strtr(Unicode::strtolower($id), array(' ' => '-', '_' => '-', '[' => '-', ']' => '')); + + // As defined in http://www.w3.org/TR/html4/types.html#type-name, HTML IDs can + // only contain letters, digits ([0-9]), hyphens ("-"), underscores ("_"), + // colons (":"), and periods ("."). We strip out any character not in that + // list. Note that the CSS spec doesn't allow colons or periods in identifiers + // (http://www.w3.org/TR/CSS21/syndata.html#characters), so we strip those two + // characters as well. + $id = preg_replace('/[^A-Za-z0-9\-_]/', '', $id); + + // Removing multiple consecutive hyphens. + $id = preg_replace('/\-+/', '-', $id); + return $id; + } + + /** + * Resets the list of seen IDs. + */ + public static function resetSeenIds() { + static::$seenIds = NULL; + } + + /** * Normalizes an HTML snippet. * * This function is essentially \DOMDocument::normalizeDocument(), but diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php index 3efa1f2..e299395 100644 --- a/core/lib/Drupal/Core/Form/FormBuilder.php +++ b/core/lib/Drupal/Core/Form/FormBuilder.php @@ -8,6 +8,7 @@ namespace Drupal\Core\Form; use Drupal\Component\Utility\Crypt; +use Drupal\Component\Utility\Html; use Drupal\Component\Utility\NestedArray; use Drupal\Component\Utility\UrlHelper; use Drupal\Core\Access\CsrfTokenGenerator; @@ -479,10 +480,10 @@ public function retrieveForm($form_id, &$form_state) { // Assign a default CSS class name based on $form_id. // This happens here and not in self::prepareForm() in order to allow the // form constructor function to override or remove the default class. - $form['#attributes']['class'][] = $this->drupalHtmlClass($form_id); + $form['#attributes']['class'][] = Html::getClass($form_id); // Same for the base form ID, if any. if (isset($form_state['build_info']['base_form_id'])) { - $form['#attributes']['class'][] = $this->drupalHtmlClass($form_state['build_info']['base_form_id']); + $form['#attributes']['class'][] = Html::getClass($form_state['build_info']['base_form_id']); } // We need to pass $form_state by reference in order for forms to modify it, @@ -552,7 +553,7 @@ public function processForm($form_id, &$form, &$form_state) { // element IDs needlessly. if (!$this->getAnyErrors()) { // In case of errors, do not break HTML IDs of other forms. - $this->drupalStaticReset('drupal_html_id'); + Html::resetSeenIds(); } // @todo Move into a dedicated class in https://drupal.org/node/2257835. @@ -698,7 +699,7 @@ public function prepareForm($form_id, &$form, &$form_state) { else { $form['#token'] = $form_id; $form['form_token'] = array( - '#id' => $this->drupalHtmlId('edit-' . $form_id . '-form-token'), + '#id' => Html::getUniqueId('edit-' . $form_id . '-form-token'), '#type' => 'token', '#default_value' => $this->csrfToken->get($form['#token']), // Form processing and validation requires this value, so ensure the @@ -713,7 +714,7 @@ public function prepareForm($form_id, &$form, &$form_state) { $form['form_id'] = array( '#type' => 'hidden', '#value' => $form_id, - '#id' => $this->drupalHtmlId("edit-$form_id"), + '#id' => Html::getUniqueId("edit-$form_id"), // Form processing and validation requires this value, so ensure the // submitted form value appears literally, regardless of custom #tree // and #parents being set elsewhere. @@ -721,7 +722,7 @@ public function prepareForm($form_id, &$form, &$form_state) { ); } if (!isset($form['#id'])) { - $form['#id'] = $this->drupalHtmlId($form_id); + $form['#id'] = Html::getUniqueId($form_id); } $form += $this->getElementInfo('form'); @@ -1000,7 +1001,7 @@ public function doBuildForm($form_id, &$element, &$form_state) { } if (!isset($element['#id'])) { - $element['#id'] = $this->drupalHtmlId('edit-' . implode('-', $element['#parents'])); + $element['#id'] = Html::getUniqueId('edit-' . implode('-', $element['#parents'])); } // Add the aria-describedby attribute to associate the form control with its @@ -1388,31 +1389,6 @@ protected function drupalInstallationAttempted() { } /** - * Wraps drupal_html_class(). - * - * @return string - */ - protected function drupalHtmlClass($class) { - return drupal_html_class($class); - } - - /** - * Wraps drupal_html_id(). - * - * @return string - */ - protected function drupalHtmlId($id) { - return drupal_html_id($id); - } - - /** - * Wraps drupal_static_reset(). - */ - protected function drupalStaticReset($name = NULL) { - drupal_static_reset($name); - } - - /** * Gets the current active user. * * @return \Drupal\Core\Session\AccountInterface diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockViewBuilderTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockViewBuilderTest.php index feb5471..43f723f 100644 --- a/core/modules/block/lib/Drupal/block/Tests/BlockViewBuilderTest.php +++ b/core/modules/block/lib/Drupal/block/Tests/BlockViewBuilderTest.php @@ -7,6 +7,7 @@ namespace Drupal\block\Tests; +use Drupal\Component\Utility\Html; use Drupal\Component\Utility\NestedArray; use Drupal\Core\Cache\UrlCacheContext; use Drupal\simpletest\DrupalUnitTestBase; @@ -102,7 +103,7 @@ public function testBasicRendering() { $this->assertEqual(drupal_render($output), $expected_output); // Reset the HTML IDs so that the next render is not affected. - drupal_static_reset('drupal_html_id'); + Html::resetSeenIds(); // Test the rendering of a block with a given title. $entity = $this->controller->create(array( diff --git a/core/modules/system/lib/Drupal/system/Tests/Common/HtmlIdentifierUnitTest.php b/core/modules/system/lib/Drupal/system/Tests/Common/HtmlIdentifierUnitTest.php deleted file mode 100644 index ec9bbb4..0000000 --- a/core/modules/system/lib/Drupal/system/Tests/Common/HtmlIdentifierUnitTest.php +++ /dev/null @@ -1,97 +0,0 @@ - 'HTML identifiers', - 'description' => 'Test the functions drupal_html_class(), drupal_html_id() and drupal_clean_css_identifier() for expected behavior', - 'group' => 'Common', - ); - } - - /** - * {@inheritdoc} - */ - public function setUp() { - parent::setUp(); - - $container = \Drupal::getContainer(); - $request = new Request(); - $container->set('request', $request); - \Drupal::setContainer($container); - } - - /** - * Tests that drupal_clean_css_identifier() cleans the identifier properly. - */ - function testDrupalCleanCSSIdentifier() { - // Verify that no valid ASCII characters are stripped from the identifier. - $identifier = 'abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789'; - $this->assertIdentical(drupal_clean_css_identifier($identifier, array()), $identifier, 'Verify valid ASCII characters pass through.'); - - // Verify that valid UTF-8 characters are not stripped from the identifier. - $identifier = '¡¢£¤¥'; - $this->assertIdentical(drupal_clean_css_identifier($identifier, array()), $identifier, 'Verify valid UTF-8 characters pass through.'); - - // Verify that invalid characters (including non-breaking space) are stripped from the identifier. - $this->assertIdentical(drupal_clean_css_identifier('invalid !"#$%&\'()*+,./:;<=>?@[\\]^`{|}~ identifier', array()), 'invalididentifier', 'Strip invalid characters.'); - - // Verify that double underscores are not stripped from the identifier. - $identifier = 'css__identifier__with__double__underscores'; - $this->assertIdentical(drupal_clean_css_identifier($identifier), $identifier, 'Verify double underscores pass through.'); - - // Verify that an identifier starting with a digit is replaced. - $this->assertIdentical(drupal_clean_css_identifier('1cssidentifier', array()), '_cssidentifier', 'Verify identifier starting with a digit is replaced.'); - - // Verify that an identifier starting with a hyphen followed by a digit is - // replaced. - $this->assertIdentical(drupal_clean_css_identifier('-1cssidentifier', array()), '__cssidentifier', 'Verify identifier starting with a hyphen followed by a digit is replaced.'); - - // Verify that an identifier starting with two hyphens is replaced. - $this->assertIdentical(drupal_clean_css_identifier('--cssidentifier', array()), '__cssidentifier', 'Verify identifier starting with two hyphens is replaced.'); - } - - /** - * Tests that drupal_html_class() cleans the class name properly. - */ - function testDrupalHTMLClass() { - // Verify Drupal coding standards are enforced. - $this->assertIdentical(drupal_html_class('CLASS NAME_[Ü]'), 'class-name--ü', 'Enforce Drupal coding standards.'); - } - - /** - * Tests that drupal_html_id() cleans the ID properly. - */ - function testDrupalHTMLId() { - // Verify that letters, digits, and hyphens are not stripped from the ID. - $id = 'abcdefghijklmnopqrstuvwxyz-0123456789'; - $this->assertIdentical(drupal_html_id($id), $id, 'Verify valid characters pass through.'); - - // Verify that invalid characters are stripped from the ID. - $this->assertIdentical(drupal_html_id('invalid,./:@\\^`{Üidentifier'), 'invalididentifier', 'Strip invalid characters.'); - - // Verify Drupal coding standards are enforced. - $this->assertIdentical(drupal_html_id('ID NAME_[1]'), 'id-name-1', 'Enforce Drupal coding standards.'); - - // Reset the static cache so we can ensure the unique id count is at zero. - drupal_static_reset('drupal_html_id'); - - // Clean up IDs with invalid starting characters. - $this->assertIdentical(drupal_html_id('test-unique-id'), 'test-unique-id', 'Test the uniqueness of IDs #1.'); - $this->assertIdentical(drupal_html_id('test-unique-id'), 'test-unique-id--2', 'Test the uniqueness of IDs #2.'); - $this->assertIdentical(drupal_html_id('test-unique-id'), 'test-unique-id--3', 'Test the uniqueness of IDs #3.'); - } -} diff --git a/core/modules/views_ui/lib/Drupal/views_ui/Form/Ajax/ViewsFormBase.php b/core/modules/views_ui/lib/Drupal/views_ui/Form/Ajax/ViewsFormBase.php index c6cb25f..261f805 100644 --- a/core/modules/views_ui/lib/Drupal/views_ui/Form/Ajax/ViewsFormBase.php +++ b/core/modules/views_ui/lib/Drupal/views_ui/Form/Ajax/ViewsFormBase.php @@ -7,6 +7,7 @@ namespace Drupal\views_ui\Form\Ajax; +use Drupal\Component\Utility\Html; use Drupal\Core\Form\FormBase; use Drupal\views\ViewStorageInterface; use Drupal\views\Ajax; @@ -94,8 +95,7 @@ public function getForm(ViewStorageInterface $view, $display_id, $js) { // Reset the cache of IDs. Drupal rather aggressively prevents ID // duplication but this causes it to remember IDs that are no longer even // being used. - $seen_ids_init = &drupal_static('drupal_html_id:init'); - $seen_ids_init = array(); + Html::resetSeenIds(); // check to see if this is the top form of the stack. If it is, pop // it off; if it isn't, the user clicked somewhere else and the stack is diff --git a/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php b/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php index 0aabe85..02dd002 100644 --- a/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php +++ b/core/modules/views_ui/lib/Drupal/views_ui/ViewUI.php @@ -7,6 +7,7 @@ namespace Drupal\views_ui; +use Drupal\Component\Utility\Html; use Drupal\Component\Utility\String; use Drupal\Component\Utility\Timer; use Drupal\Component\Utility\Xss; @@ -431,8 +432,7 @@ public function addFormToStack($key, $display_id, $type, $id = NULL, $top = FALS // Reset the cache of IDs. Drupal rather aggressively prevents ID // duplication but this causes it to remember IDs that are no longer even // being used. - $seen_ids_init = &drupal_static('drupal_html_id:init'); - $seen_ids_init = array(); + Html::resetSeenIds(); if (empty($this->stack)) { $this->stack = array(); diff --git a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php index 916ad1d..8f3f203 100644 --- a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php +++ b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php @@ -363,7 +363,7 @@ public function testGetFormWithString() { $form = $this->formBuilder->getForm($form_id); $this->assertFormElement($expected_form, $form, 'test'); - $this->assertSame($form_id, $form['#id']); + $this->assertArrayHasKey('#id', $form); } /** @@ -377,7 +377,7 @@ public function testGetFormWithObject() { $form = $this->formBuilder->getForm($form_arg); $this->assertFormElement($expected_form, $form, 'test'); - $this->assertSame($form_id, $form['#id']); + $this->assertArrayHasKey('#id', $form); } /** @@ -392,7 +392,7 @@ public function testGetFormWithClassString() { $form = $this->formBuilder->getForm($form_id); $this->assertFormElement($expected_form, $form, 'test'); - $this->assertSame('test_form', $form['#id']); + $this->assertSame('test-form', $form['#id']); } /** @@ -404,7 +404,7 @@ public function testBuildFormWithString() { $form = $this->formBuilder->getForm($form_id); $this->assertFormElement($expected_form, $form, 'test'); - $this->assertSame($form_id, $form['#id']); + $this->assertSame('test-form-id', $form['#id']); } /** @@ -419,7 +419,7 @@ public function testBuildFormWithClassString() { $form = $this->formBuilder->buildForm($form_id, $form_state); $this->assertFormElement($expected_form, $form, 'test'); - $this->assertSame('test_form', $form['#id']); + $this->assertSame('test-form', $form['#id']); } /** @@ -435,7 +435,7 @@ public function testBuildFormWithObject() { $form = $this->formBuilder->buildForm($form_arg, $form_state); $this->assertFormElement($expected_form, $form, 'test'); $this->assertSame($form_id, $form_state['build_info']['form_id']); - $this->assertSame($form_id, $form['#id']); + $this->assertArrayHasKey('#id', $form); } /** @@ -594,11 +594,11 @@ public function testUniqueHtmlId() { $form_state = array(); $form = $this->simulateFormSubmission($form_id, $form_arg, $form_state); - $this->assertSame($form_id, $form['#id']); + $this->assertSame('test-form-id', $form['#id']); $form_state = array(); $form = $this->simulateFormSubmission($form_id, $form_arg, $form_state); - $this->assertSame("$form_id--2", $form['#id']); + $this->assertSame('test-form-id--2', $form['#id']); } } diff --git a/core/tests/Drupal/Tests/Core/Form/FormTestBase.php b/core/tests/Drupal/Tests/Core/Form/FormTestBase.php index 1a8b61f..a8ea087 100644 --- a/core/tests/Drupal/Tests/Core/Form/FormTestBase.php +++ b/core/tests/Drupal/Tests/Core/Form/FormTestBase.php @@ -7,6 +7,7 @@ namespace Drupal\Tests\Core\Form { +use Drupal\Component\Utility\Html; use Drupal\Core\Form\FormBuilder; use Drupal\Core\Form\FormInterface; use Drupal\Core\Session\AccountInterface; @@ -136,7 +137,7 @@ public function setUp() { * {@inheritdoc} */ protected function tearDown() { - $this->formBuilder->drupalStaticReset(); + Html::resetSeenIds(); } /** @@ -286,33 +287,6 @@ protected function drupalInstallationAttempted() { /** * {@inheritdoc} */ - protected function drupalHtmlClass($class) { - return $class; - } - - /** - * {@inheritdoc} - */ - protected function drupalHtmlId($id) { - if (isset(static::$seenIds[$id])) { - $id = $id . '--' . ++static::$seenIds[$id]; - } - else { - static::$seenIds[$id] = 1; - } - return $id; - } - - /** - * {@inheritdoc} - */ - public function drupalStaticReset($name = NULL) { - static::$seenIds = array(); - } - - /** - * {@inheritdoc} - */ protected function &batchGet() { $batch = array(); return $batch; diff --git a/core/tests/Drupal/Tests/Core/Utility/HtmlTest.php b/core/tests/Drupal/Tests/Core/Utility/HtmlTest.php new file mode 100644 index 0000000..5ebd0bc --- /dev/null +++ b/core/tests/Drupal/Tests/Core/Utility/HtmlTest.php @@ -0,0 +1,176 @@ + 'HTML identifiers', + 'description' => 'Test the methods Html::getClass(), Html::getUniqueId() and Html::cleanCssIdentifier() for expected behavior.', + 'group' => 'Common', + ); + } + + /** + * Tests the Html::cleanCssIdentifier() method. + * + * @param string $expected + * The expected result. + * @param string $source + * The string being transformed to an ID. + * @param array|null $filter + * (optional) An array of string replacements to use on the identifier. If + * NULL, no filter will be passed and a default will be used. + * + * @dataProvider providerTestCleanCssIdentifier + * + * @covers ::cleanCssIdentifier + */ + public function testCleanCssIdentifier($expected, $source, $filter = NULL) { + if ($filter !== NULL) { + $this->assertSame($expected, Html::cleanCssIdentifier($source, $filter)); + } + else { + $this->assertSame($expected, Html::cleanCssIdentifier($source)); + } + } + + /** + * Provides test data for testCleanCssIdentifier(). + * + * @return array + * Test data. + */ + public function providerTestCleanCssIdentifier() { + $id1 = 'abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789'; + $id2 = '¡¢£¤¥'; + $id3 = 'css__identifier__with__double__underscores'; + return array( + // Verify that no valid ASCII characters are stripped from the identifier. + array($id1, $id1, array()), + // Verify that valid UTF-8 characters are not stripped from the identifier. + array($id2, $id2, array()), + // Verify that invalid characters (including non-breaking space) are stripped from the identifier. + array($id3, $id3), + // Verify that double underscores are not stripped from the identifier. + array('invalididentifier', 'invalid !"#$%&\'()*+,./:;<=>?@[\\]^`{|}~ identifier', array()), + // Verify that an identifier starting with a digit is replaced. + array('_cssidentifier', '1cssidentifier', array()), + // Verify that an identifier starting with a hyphen followed by a digit is + // replaced. + array('__cssidentifier', '-1cssidentifier', array()), + // Verify that an identifier starting with two hyphens is replaced. + array('__cssidentifier', '--cssidentifier', array()) + ); + } + + /** + * Tests that Html::getClass() cleans the class name properly. + * + * @coversDefaultClass ::getClass + */ + public function testHtmlClass() { + // Verify Drupal coding standards are enforced. + $this->assertSame(Html::getClass('CLASS NAME_[Ü]'), 'class-name--ü', 'Enforce Drupal coding standards.'); + } + + /** + * Tests the Html::getUniqueId() method. + * + * @param string $expected + * The expected result. + * @param string $source + * The string being transformed to an ID. + * @param bool $reset + * (optional) If TRUE, reset the list of seen IDs. Defaults to FALSE. + * + * @dataProvider providerTestHtmlGetUniqueId + * + * @covers ::getUniqueId + */ + public function testHtmlGetUniqueId($expected, $source, $reset = FALSE) { + if ($reset) { + Html::resetSeenIds(); + } + $this->assertSame($expected, Html::getUniqueId($source)); + } + + /** + * Provides test data for testHtmlGetId(). + * + * @return array + * Test data. + */ + public function providerTestHtmlGetUniqueId() { + $id = 'abcdefghijklmnopqrstuvwxyz-0123456789'; + return array( + // Verify that letters, digits, and hyphens are not stripped from the ID. + array($id, $id), + // Verify that invalid characters are stripped from the ID. + array('invalididentifier', 'invalid,./:@\\^`{Üidentifier'), + // Verify Drupal coding standards are enforced. + array('id-name-1', 'ID NAME_[1]'), + // Verify that a repeated ID is made unique. + array('test-unique-id', 'test-unique-id', TRUE), + array('test-unique-id--2', 'test-unique-id'), + array('test-unique-id--3', 'test-unique-id'), + ); + } + + /** + * Tests the Html::getUniqueId() method. + * + * @param string $expected + * The expected result. + * @param string $source + * The string being transformed to an ID. + * + * @dataProvider providerTestHtmlGetId + * + * @covers ::getId + */ + public function testHtmlGetId($expected, $source) { + $this->assertSame($expected, Html::getId($source)); + } + + /** + * Provides test data for testHtmlGetId(). + * + * @return array + * Test data. + */ + public function providerTestHtmlGetId() { + $id = 'abcdefghijklmnopqrstuvwxyz-0123456789'; + return array( + // Verify that letters, digits, and hyphens are not stripped from the ID. + array($id, $id), + // Verify that invalid characters are stripped from the ID. + array('invalididentifier', 'invalid,./:@\\^`{Üidentifier'), + // Verify Drupal coding standards are enforced. + array('id-name-1', 'ID NAME_[1]'), + // Verify that a repeated ID is made unique. + array('test-unique-id', 'test-unique-id'), + array('test-unique-id', 'test-unique-id'), + ); + } + +}