diff options
Diffstat (limited to 'includes/common.inc')
-rw-r--r-- | includes/common.inc | 77 |
1 files changed, 69 insertions, 8 deletions
diff --git a/includes/common.inc b/includes/common.inc index d63d056c2..91632bf92 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -3317,13 +3317,73 @@ function drupal_html_class($class) { /** * Prepare a string for use as a valid HTML ID and guarantee 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 $id * The ID to clean. + * * @return * The cleaned ID. */ function drupal_html_id($id) { - $seen_ids = &drupal_static(__FUNCTION__, array()); + // 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)) { + // 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'])) { + $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. + foreach ($_POST['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 = strtr(drupal_strtolower($id), array(' ' => '-', '_' => '-', '[' => '-', ']' => '')); // As defined in http://www.w3.org/TR/html4/types.html#type-name, HTML IDs can @@ -3334,14 +3394,15 @@ function drupal_html_id($id) { // characters as well. $id = preg_replace('/[^A-Za-z0-9\-_]/', '', $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. + // Removing multiple consecutive hyphens. + $id = preg_replace('/\-+/', '-', $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]; + $id = $id . '--' . ++$seen_ids[$id]; } else { $seen_ids[$id] = 1; |