summaryrefslogtreecommitdiff
path: root/includes/common.inc
diff options
context:
space:
mode:
Diffstat (limited to 'includes/common.inc')
-rw-r--r--includes/common.inc77
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;