Commit b5447770 authored by Dries's avatar Dries

- Patch #464862 by JohnAlbin, sun, dereine | dvessel, Jacine, Zarabadoo: added...

- Patch #464862 by JohnAlbin, sun, dereine | dvessel, Jacine, Zarabadoo: added drupal_css_class() to clean class names and rename form_clean_id().
parent b5199f22
......@@ -3094,6 +3094,79 @@ function drupal_clear_css_cache() {
file_scan_directory('public://css', '/.*/', array('callback' => 'file_unmanaged_delete'));
}
/**
* Prepare a string for use as a valid CSS identifier (element, class or ID selector).
*
* 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 $identifier
* The CSS identifier to clean.
* @param $filter
* An array of string replacements to use on the identifier.
* @return
* The cleaned identifier.
*/
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);
return $identifier;
}
/**
* Prepare a string for use as a valid CSS 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 $class
* The class name to clean.
* @return
* The cleaned class name.
*/
function drupal_css_class($class) {
return drupal_clean_css_identifier(drupal_strtolower($class));
}
/**
* Prepare a string for use as a valid HTML ID and guarantee uniqueness.
*
* @param $id
* The ID to clean.
* @return
* The cleaned ID.
*/
function drupal_css_id($id) {
$seen_ids = &drupal_static(__FUNCTION__, array());
$id = drupal_clean_css_identifier(drupal_strtolower($id));
// Ensure IDs are unique. The first occurrence is held but left alone.
// Subsequent occurrences get a number appended to them. This incrementing
// will almost certainly break code that relies on explicit HTML IDs in forms
// that appear more than once on the page, but the alternative is outputting
// duplicate IDs, which would break JS code and XHTML validity anyways. For
// now, it's an acceptable stopgap solution.
if (isset($seen_ids[$id])) {
$id = $id . '-' . ++$seen_ids[$id];
}
else {
$seen_ids[$id] = 1;
}
return $id;
}
/**
* Add a JavaScript file, setting or inline code to the page.
*
......
......@@ -547,12 +547,12 @@ function drupal_process_form($form_id, &$form, &$form_state) {
if ($form_state['process_input']) {
drupal_validate_form($form_id, $form, $form_state);
// form_clean_id() maintains a cache of element IDs it has seen,
// drupal_css_id() maintains a cache of element IDs it has seen,
// so it can prevent duplicates. We want to be sure we reset that
// cache when a form is processed, so scenarios that result in
// the form being built behind the scenes and again for the
// browser don't increment all the element IDs needlessly.
drupal_static_reset('form_clean_id');
drupal_static_reset('drupal_css_id');
if ($form_state['submitted'] && !form_get_errors() && !$form_state['rebuild']) {
// Execute form submit handlers.
......@@ -636,7 +636,7 @@ function drupal_prepare_form($form_id, &$form, &$form_state) {
elseif (isset($user->uid) && $user->uid && !$form_state['programmed']) {
$form['#token'] = $form_id;
$form['form_token'] = array(
'#id' => form_clean_id('edit-' . $form_id . '-form-token'),
'#id' => drupal_css_id('edit-' . $form_id . '-form-token'),
'#type' => 'token',
'#default_value' => drupal_get_token($form['#token']),
);
......@@ -646,11 +646,11 @@ function drupal_prepare_form($form_id, &$form, &$form_state) {
$form['form_id'] = array(
'#type' => 'hidden',
'#value' => $form_id,
'#id' => form_clean_id("edit-$form_id"),
'#id' => drupal_css_id("edit-$form_id"),
);
}
if (!isset($form['#id'])) {
$form['#id'] = form_clean_id($form_id);
$form['#id'] = drupal_css_id($form_id);
}
$form += element_info('form');
......@@ -1046,7 +1046,7 @@ function form_builder($form_id, $element, &$form_state) {
}
if (!isset($element['#id'])) {
$element['#id'] = form_clean_id('edit-' . implode('-', $element['#parents']));
$element['#id'] = drupal_css_id('edit-' . implode('-', $element['#parents']));
}
// Handle input elements.
if (!empty($element['#input'])) {
......@@ -1919,7 +1919,7 @@ function form_process_radios($element) {
'#default_value' => isset($element['#default_value']) ? $element['#default_value'] : NULL,
'#attributes' => $element['#attributes'],
'#parents' => $element['#parents'],
'#id' => form_clean_id('edit-' . implode('-', $parents_for_id)),
'#id' => drupal_css_id('edit-' . implode('-', $parents_for_id)),
'#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
);
}
......@@ -2235,7 +2235,7 @@ function form_process_tableselect($element) {
'#default_value' => ($element['#default_value'] == $key) ? $key : NULL,
'#attributes' => $element['#attributes'],
'#parents' => $element['#parents'],
'#id' => form_clean_id('edit-' . implode('-', $parents_for_id)),
'#id' => drupal_css_id('edit-' . implode('-', $parents_for_id)),
'#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
);
}
......@@ -2683,42 +2683,6 @@ function _form_set_class(&$element, $class = array()) {
}
}
/**
* Prepare an HTML ID attribute string for a form item.
*
* Remove invalid characters and guarantee uniqueness.
*
* @param $id
* The ID to clean.
* @param $flush
* If set to TRUE, the function will flush and reset the static array
* which is built to test the uniqueness of element IDs. This is only
* used if a form has completed the validation process. This parameter
* should never be set to TRUE if this function is being called to
* assign an ID to the #ID element.
* @return
* The cleaned ID.
*/
function form_clean_id($id = NULL) {
$seen_ids = &drupal_static(__FUNCTION__, array());
$id = str_replace(array('][', '_', ' '), '-', $id);
// Ensure IDs are unique. The first occurrence is held but left alone.
// Subsequent occurrences get a number appended to them. This incrementing
// will almost certainly break code that relies on explicit HTML IDs in
// forms that appear more than once on the page, but the alternative is
// outputting duplicate IDs, which would break JS code and XHTML
// validity anyways. For now, it's an acceptable stopgap solution.
if (isset($seen_ids[$id])) {
$id = $id . '-' . $seen_ids[$id]++;
}
else {
$seen_ids[$id] = 1;
}
return $id;
}
/**
* @} End of "defgroup form_api".
*/
......
......@@ -2128,18 +2128,17 @@ function template_preprocess_html(&$variables) {
if ($suggestions = template_page_suggestions(arg(), 'page')) {
foreach ($suggestions as $suggestion) {
if ($suggestion != 'page-front') {
// Add current suggestion to page classes to make it possible to theme the page
// depending on the current page type (e.g. node, admin, user, etc.) as well as
// more specific data like node-12 or node-edit. To avoid illegal characters in
// the class, we're removing everything disallowed. We are not using 'a-z' as
// that might leave in certain international characters (e.g. German umlauts).
$variables['classes_array'][] = preg_replace('![^abcdefghijklmnopqrstuvwxyz0-9-_]+!s', '', form_clean_id(drupal_strtolower($suggestion)));
// Add current suggestion to page classes to make it possible to theme
// the page depending on the current page type (e.g. node, admin, user,
// etc.) as well as more specific data like node-12 or node-edit.
$variables['classes_array'][] = drupal_css_class($suggestion);
}
}
}
// If on an individual node page, add the node type to body classes.
if ($node = menu_get_object()) {
$variables['classes_array'][] = 'node-type-' . form_clean_id($node->type);
$variables['classes_array'][] = drupal_css_class('node-type-' . $node->type);
}
// RDFa allows annotation of XHTML pages with RDF data, while GRDDL provides
......@@ -2150,7 +2149,6 @@ function template_preprocess_html(&$variables) {
$variables['language'] = $GLOBALS['language'];
$variables['language']->dir = $GLOBALS['language']->direction ? 'rtl' : 'ltr';
// Add favicon.
if (theme_get_setting('toggle_favicon')) {
$favicon = theme_get_setting('favicon');
......
......@@ -797,7 +797,7 @@ function template_preprocess_block(&$variables) {
// Create the $content variable that templates expect.
$variables['content'] = $variables['elements']['#children'];
$variables['classes_array'][] = 'block-' . $variables['block']->module;
$variables['classes_array'][] = drupal_css_class('block-' . $variables['block']->module);
$variables['template_files'][] = 'block-' . $variables['block']->region;
$variables['template_files'][] = 'block-' . $variables['block']->module;
......
......@@ -746,7 +746,7 @@ function filter_form($selected_format = NULL, $weight = NULL, $parents = array('
drupal_add_js('misc/form.js');
drupal_add_css(drupal_get_path('module', 'filter') . '/filter.css');
$element_id = form_clean_id('edit-' . implode('-', $parents));
$element_id = drupal_css_id('edit-' . implode('-', $parents));
$form = array(
'#type' => 'fieldset',
......
......@@ -1195,7 +1195,7 @@ function template_preprocess_node(&$variables) {
}
// Gather node classes.
$variables['classes_array'][] = 'node-' . $node->type;
$variables['classes_array'][] = drupal_css_class('node-' . $node->type);
if ($variables['promote']) {
$variables['classes_array'][] = 'node-promoted';
}
......
......@@ -266,8 +266,8 @@ class ProfileTestAutocomplete extends ProfileTestCase {
$this->setProfileField($field, $field['value']);
// Set some html for what we want to see in the page output later.
$autocomplete_html = '<input class="autocomplete" type="hidden" id="' . form_clean_id('edit-' . $field['form_name'] . '-autocomplete') . '" value="' . url('profile/autocomplete/' . $field['fid'], array('absolute' => TRUE)) . '" disabled="disabled" />';
$field_html = '<input type="text" maxlength="255" name="' . $field['form_name'] . '" id="' . form_clean_id('edit-' . $field['form_name']) . '" size="60" value="' . $field['value'] . '" class="form-text form-autocomplete required" />';
$autocomplete_html = '<input class="autocomplete" type="hidden" id="' . drupal_css_id('edit-' . $field['form_name'] . '-autocomplete') . '" value="' . url('profile/autocomplete/' . $field['fid'], array('absolute' => TRUE)) . '" disabled="disabled" />';
$field_html = '<input type="text" maxlength="255" name="' . $field['form_name'] . '" id="' . drupal_css_id('edit-' . $field['form_name']) . '" size="60" value="' . $field['value'] . '" class="form-text form-autocomplete required" />';
// Check that autocompletion html is found on the user's profile edit page.
$this->drupalGet('user/' . $this->admin_user->uid . '/edit/' . $category);
......
......@@ -572,6 +572,59 @@ class CascadingStylesheetsTestCase extends DrupalWebTestCase {
}
}
/**
* Test for drupal_add_css().
*/
class DrupalCSSIdentifierTestCase extends DrupalUnitTestCase {
public static function getInfo() {
return array(
'name' => 'CSS identifiers',
'description' => 'Test the functions drupal_css_class() and drupal_css_id() for expected behavior',
'group' => 'System',
);
}
/**
* Tests that drupal_css_class() cleans the class name properly.
*/
function testDrupalCleanCSSIdentifier() {
// Verify that no valid ASCII characters are stripped from the class name.
$class = 'abcdefghijklmnopqrstuvwxyz_ABCDEFGHIJKLMNOPQRSTUVWXYZ-0123456789';
$this->assertIdentical(drupal_clean_css_identifier($class, array()), $class, t('Verify valid ASCII characters pass through.'));
// Verify that no valid UTF-8 characters are stripped from the class name.
$class = '¡¢£¤¥';
$this->assertIdentical(drupal_clean_css_identifier($class, array()), $class, t('Verify valid UTF-8 characters pass through.'));
// Verify that invalid characters (including non-breaking space) are stripped from the class name.
$this->assertIdentical(drupal_clean_css_identifier('invalid !"#$%&\'()*+,./:;<=>?@[\\]^`{|}~ class', array()), 'invalidclass', t('Strip invalid characters.'));
}
/**
* Tests that drupal_css_class() cleans the class name properly.
*/
function testDrupalCSSClass() {
// Verify Drupal coding standards are enforced.
$this->assertIdentical(drupal_css_class('CLASS NAME_[Ü]'), 'class-name--ü', t('Enforce Drupal coding standards.'));
}
/**
* Tests that drupal_css_id() cleans the id name properly.
*/
function testDrupalCSSId() {
// Verify Drupal coding standards are enforced.
$this->assertIdentical(drupal_css_id('ID NAME_[Ü]'), 'id-name--ü', t('Enforce Drupal coding standards.'));
// Reset the static cache so we can ensure the unique id count is at zero.
drupal_static_reset('drupal_css_id');
// Clean up IDs with invalid starting characters.
$this->assertIdentical(drupal_css_id('test-unique-id'), 'test-unique-id', t('Test the uniqueness of IDs #1.'));
$this->assertIdentical(drupal_css_id('test-unique-id'), 'test-unique-id-2', t('Test the uniqueness of IDs #2.'));
$this->assertIdentical(drupal_css_id('test-unique-id'), 'test-unique-id-3', t('Test the uniqueness of IDs #3.'));
}
}
/**
* Test drupal_http_request().
*/
......
......@@ -332,36 +332,6 @@ class FormsElementsTableSelectFunctionalTest extends DrupalWebTestCase {
}
/**
* Test the form_clean_id() for expected behavior.
*/
class FormsFormCleanIdFunctionalTest extends DrupalWebTestCase {
public static function getInfo() {
return array(
'name' => 'form_clean_id() test',
'description' => 'Test the function form_clean_id() for expected behavior',
'group' => 'Form API',
);
}
function setUp() {
parent::setUp('form_test');
}
/**
* Test the uniqueness of the form_clean_id() function.
*/
function testFormCleanId() {
$this->drupalGet('form_test/form_clean_id');
$this->assertNoUniqueText('form-test-form-clean-id-presence');
$this->assertUniqueText('form-test-form-clean-id-presence-1');
$this->assertUniqueText('form-test-form-clean-id-presence-2');
$this->assertNoUniqueText('Test Textfield');
}
}
/**
* Test using drupal_form_submit in a batch.
*/
......
......@@ -39,13 +39,6 @@ function form_test_menu() {
'type' => MENU_CALLBACK,
);
$items['form_test/form_clean_id'] = array(
'title' => 'form_clean_id test',
'page callback' => 'form_test_form_clean_id_page',
'access arguments' => array('access content'),
'type' => MENU_CALLBACK,
);
$items['form_test/drupal_form_submit_batch_api'] = array(
'title' => 'BatchAPI Drupal_form_submit tests',
'page callback' => 'form_test_drupal_form_submit_batch_api',
......@@ -72,28 +65,6 @@ function form_test_menu() {
return $items;
}
/**
* Generate a page with three forms, to test the clean_id generation.
*/
function form_test_form_clean_id_page() {
$build['form_test_test_form1'] = drupal_get_form('form_test_test_form');
$build['form_test_test_form2'] = drupal_get_form('form_test_test_form');
$build['form_test_test_form3'] = drupal_get_form('form_test_test_form');
return $build;
}
/**
* A simple form to test clean_id generation.
*/
function form_test_test_form($form, &$form_state) {
$form['input'] = array(
'#type' => 'item',
'#title' => 'Test Textfield',
'#markup' => form_clean_id('form_test_form_clean_id_presence'),
);
return $form;
}
/**
* Create a header and options array. Helper function for callbacks.
*/
......
......@@ -252,7 +252,7 @@ class ModuleVersionTestCase extends ModuleTestCase {
$n = count($dependencies);
for ($i = 0; $i < $n; $i++) {
$this->drupalGet('admin/config/modules');
$checkbox = $this->xpath('//input[@id="edit-modules-Testing-module-test-enable"]');
$checkbox = $this->xpath('//input[@id="edit-modules-testing-module-test-enable"]');
$this->assertEqual(!empty($checkbox[0]['disabled']), $i % 2, $dependencies[$i]);
}
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment