diff options
Diffstat (limited to 'includes')
-rw-r--r-- | includes/common.inc | 327 | ||||
-rw-r--r-- | includes/form.inc | 2 | ||||
-rw-r--r-- | includes/pager.inc | 25 | ||||
-rw-r--r-- | includes/tablesort.inc | 32 |
4 files changed, 252 insertions, 134 deletions
diff --git a/includes/common.inc b/includes/common.inc index f3701e50c..9a0d63310 100644 --- a/includes/common.inc +++ b/includes/common.inc @@ -348,36 +348,86 @@ function drupal_get_feeds($delimiter = "\n") { */ /** - * Parse an array into a valid urlencoded query string. + * Process a URL query parameter array to remove unwanted elements. * * @param $query - * The array to be processed e.g. $_GET. + * (optional) An array to be processed. Defaults to $_GET. * @param $exclude - * The array filled with keys to be excluded. Use parent[child] to exclude - * nested items. + * (optional) A list of $query array keys to remove. Use "parent[child]" to + * exclude nested items. Defaults to array('q'). * @param $parent - * Should not be passed, only used in recursive calls. + * Internal use only. Used to build the $query array key for nested items. + * * @return - * An urlencoded string which can be appended to/as the URL query string. + * An array containing query parameters, which can be used for url(). */ -function drupal_query_string_encode($query, $exclude = array(), $parent = '') { - $params = array(); +function drupal_get_query_parameters(array $query = NULL, array $exclude = array('q'), $parent = '') { + // Set defaults, if none given. + if (!isset($query)) { + $query = $_GET; + } + // If $exclude is empty, there is nothing to filter. + if (empty($exclude)) { + return $query; + } + elseif (!$parent) { + $exclude = array_flip($exclude); + } + $params = array(); foreach ($query as $key => $value) { - $key = rawurlencode($key); - if ($parent) { - $key = $parent . '[' . $key . ']'; + $string_key = ($parent ? $parent . '[' . $key . ']' : $key); + if (isset($exclude[$string_key])) { + continue; } - if (in_array($key, $exclude)) { - continue; + if (is_array($value)) { + $params[$key] = drupal_get_query_parameters($value, $exclude, $string_key); + } + else { + $params[$key] = $value; } + } + + return $params; +} + +/** + * Parse an array into a valid, rawurlencoded query string. + * + * This differs from http_build_query() as we need to rawurlencode() (instead of + * urlencode()) all query parameters. + * + * @param $query + * The query parameter array to be processed, e.g. $_GET. + * @param $parent + * Internal use only. Used to build the $query array key for nested items. + * + * @return + * A rawurlencoded string which can be used as or appended to the URL query + * string. + * + * @see drupal_get_query_parameters() + * @ingroup php_wrappers + */ +function drupal_http_build_query(array $query, $parent = '') { + $params = array(); + + foreach ($query as $key => $value) { + $key = ($parent ? $parent . '[' . rawurlencode($key) . ']' : rawurlencode($key)); + // Recurse into children. if (is_array($value)) { - $params[] = drupal_query_string_encode($value, $exclude, $key); + $params[] = drupal_http_build_query($value, $key); + } + // If a query parameter value is NULL, only append its key. + elseif (!isset($value)) { + $params[] = $key; } else { - $params[] = $key . '=' . rawurlencode($value); + // For better readability of paths in query strings, we decode slashes. + // @see drupal_encode_path() + $params[] = $key . '=' . str_replace('%2F', '/', rawurlencode($value)); } } @@ -385,7 +435,7 @@ function drupal_query_string_encode($query, $exclude = array(), $parent = '') { } /** - * Prepare a destination query string for use in combination with drupal_goto(). + * Prepare a 'destination' URL query parameter for use in combination with drupal_goto(). * * Used to direct the user back to the referring page after completing a form. * By default the current URL is returned. If a destination exists in the @@ -395,17 +445,126 @@ function drupal_query_string_encode($query, $exclude = array(), $parent = '') { * @see drupal_goto() */ function drupal_get_destination() { + $destination = &drupal_static(__FUNCTION__); + + if (isset($destination)) { + return $destination; + } + if (isset($_GET['destination'])) { - return 'destination=' . urlencode($_GET['destination']); + $destination = array('destination' => $_GET['destination']); } else { - // Use $_GET here to retrieve the original path in source form. - $path = isset($_GET['q']) ? $_GET['q'] : ''; - $query = drupal_query_string_encode($_GET, array('q')); + $path = $_GET['q']; + $query = drupal_http_build_query(drupal_get_query_parameters()); if ($query != '') { $path .= '?' . $query; } - return 'destination=' . urlencode($path); + $destination = array('destination' => $path); + } + return $destination; +} + +/** + * Wrapper around parse_url() to parse a given URL into an associative array, suitable for url(). + * + * The returned array contains a 'path' that may be passed separately to url(). + * For example: + * @code + * $options = drupal_parse_url($_GET['destination']); + * $my_url = url($options['path'], $options); + * $my_link = l('Example link', $options['path'], $options); + * @endcode + * + * This is required, because url() does not support relative URLs containing a + * query string or fragment in its $path argument. Instead, any query string + * needs to be parsed into an associative query parameter array in + * $options['query'] and the fragment into $options['fragment']. + * + * @param $url + * The URL string to parse, f.e. $_GET['destination']. + * + * @return + * An associative array containing the keys: + * - 'path': The path of the URL. If the given $url is external, this includes + * the scheme and host. + * - 'query': An array of query parameters of $url, if existent. + * - 'fragment': The fragment of $url, if existent. + * + * @see url() + * @see drupal_goto() + * @ingroup php_wrappers + */ +function drupal_parse_url($url) { + $options = array( + 'path' => NULL, + 'query' => array(), + 'fragment' => '', + ); + + // External URLs: not using parse_url() here, so we do not have to rebuild + // the scheme, host, and path without having any use for it. + if (strpos($url, '://') !== FALSE) { + // Split off everything before the query string into 'path'. + $parts = explode('?', $url); + $options['path'] = $parts[0]; + // If there is a query string, transform it into keyed query parameters. + if (isset($parts[1])) { + $query_parts = explode('#', $parts[1]); + parse_str($query_parts[0], $options['query']); + // Take over the fragment, if there is any. + if (isset($query_parts[1])) { + $options['fragment'] = $query_parts[1]; + } + } + } + // Internal URLs. + else { + $parts = parse_url($url); + $options['path'] = $parts['path']; + if (isset($parts['query'])) { + parse_str($parts['query'], $options['query']); + } + if (isset($parts['fragment'])) { + $options['fragment'] = $parts['fragment']; + } + } + + return $options; +} + +/** + * Encode a path for usage in a URL. + * + * Wrapper around rawurlencode() which avoids Apache quirks. Should be used when + * placing arbitrary data into the path component of an URL. + * + * Do not use this function to pass a path to url(). url() properly handles + * and encodes paths internally. + * This function should only be used on paths, not on query string arguments. + * Otherwise, unwanted double encoding will occur. + * + * Notes: + * - For esthetic reasons, we do not escape slashes. This also avoids a 'feature' + * in Apache where it 404s on any path containing '%2F'. + * - mod_rewrite unescapes %-encoded ampersands, hashes, and slashes when clean + * URLs are used, which are interpreted as delimiters by PHP. These + * characters are double escaped so PHP will still see the encoded version. + * - With clean URLs, Apache changes '//' to '/', so every second slash is + * double escaped. + * + * @param $path + * The URL path component to encode. + */ +function drupal_encode_path($path) { + if (!empty($GLOBALS['conf']['clean_url'])) { + return str_replace(array('%2F', '%26', '%23', '//'), + array('/', '%2526', '%2523', '/%252F'), + rawurlencode($path) + ); + } + else { + return str_replace('%2F', '/', rawurlencode($path)); } } @@ -448,9 +607,9 @@ function drupal_get_destination() { * supported. * @see drupal_get_destination() */ -function drupal_goto($path = '', $query = NULL, $fragment = NULL, $http_response_code = 302) { +function drupal_goto($path = '', array $query = array(), $fragment = NULL, $http_response_code = 302) { if (isset($_GET['destination'])) { - extract(parse_url(urldecode($_GET['destination']))); + extract(drupal_parse_url(urldecode($_GET['destination']))); } $args = array( @@ -2163,42 +2322,35 @@ function _format_date_callback(array $matches = NULL, $new_langcode = NULL) { */ /** - * Generate a URL from a Drupal menu path. Will also pass-through existing URLs. + * Generate a URL. * * @param $path - * The Drupal path being linked to, such as "admin/content", or an - * existing URL like "http://drupal.org/". The special path - * '<front>' may also be given and will generate the site's base URL. + * The Drupal path being linked to, such as "admin/content", or an existing + * URL like "http://drupal.org/". The special path '<front>' may also be given + * and will generate the site's base URL. * @param $options * An associative array of additional options, with the following keys: - * - 'query' - * A URL-encoded query string to append to the link, or an array of query - * key/value-pairs without any URL-encoding. - * - 'fragment' - * A fragment identifier (or named anchor) to append to the link. - * Do not include the '#' character. - * - 'absolute' (default FALSE) - * Whether to force the output to be an absolute link (beginning with - * http:). Useful for links that will be displayed outside the site, such - * as in an RSS feed. - * - 'alias' (default FALSE) - * Whether the given path is an alias already. - * - 'external' - * Whether the given path is an external URL. - * - 'language' - * An optional language object. Used to build the URL to link to and - * look up the proper alias for the link. - * - 'https' - * Whether this URL should point to a secure location. If not specified, - * the current scheme is used, so the user stays on http or https - * respectively. TRUE enforces HTTPS and FALSE enforces HTTP, but HTTPS - * can only be enforced when the variable 'https' is set to TRUE. - * - 'base_url' - * Only used internally, to modify the base URL when a language dependent - * URL requires so. - * - 'prefix' - * Only used internally, to modify the path when a language dependent URL - * requires so. + * - 'query': An array of query key/value-pairs (without any URL-encoding) to + * append to the link. + * - 'fragment': A fragment identifier (or named anchor) to append to the + * link. Do not include the leading '#' character. + * - 'absolute': Defaults to FALSE. Whether to force the output to be an + * absolute link (beginning with http:). Useful for links that will be + * displayed outside the site, such as in a RSS feed. + * - 'alias': Defaults to FALSE. Whether the given path is a URL alias + * already. + * - 'external': Whether the given path is an external URL. + * - 'language': An optional language object. Used to build the URL to link to + * and look up the proper alias for the link. + * - 'https': Whether this URL should point to a secure location. If not + * specified, the current scheme is used, so the user stays on http or https + * respectively. TRUE enforces HTTPS and FALSE enforces HTTP, but HTTPS can + * only be enforced when the variable 'https' is set to TRUE. + * - 'base_url': Only used internally, to modify the base URL when a language + * dependent URL requires so. + * - 'prefix': Only used internally, to modify the path when a language + * dependent URL requires so. + * * @return * A string containing a URL to the given path. * @@ -2209,7 +2361,7 @@ function url($path = NULL, array $options = array()) { // Merge in defaults. $options += array( 'fragment' => '', - 'query' => '', + 'query' => array(), 'absolute' => FALSE, 'alias' => FALSE, 'https' => FALSE, @@ -2230,21 +2382,19 @@ function url($path = NULL, array $options = array()) { if ($options['fragment']) { $options['fragment'] = '#' . $options['fragment']; } - if (is_array($options['query'])) { - $options['query'] = drupal_query_string_encode($options['query']); - } if ($options['external']) { // Split off the fragment. if (strpos($path, '#') !== FALSE) { list($path, $old_fragment) = explode('#', $path, 2); + // If $options contains no fragment, take it over from the path. if (isset($old_fragment) && !$options['fragment']) { $options['fragment'] = '#' . $old_fragment; } } // Append the query. if ($options['query']) { - $path .= (strpos($path, '?') !== FALSE ? '&' : '?') . $options['query']; + $path .= (strpos($path, '?') !== FALSE ? '&' : '?') . drupal_http_build_query($options['query']); } // Reassemble. return $path . $options['fragment']; @@ -2295,28 +2445,31 @@ function url($path = NULL, array $options = array()) { $base = $options['absolute'] ? $options['base_url'] . '/' : base_path(); $prefix = empty($path) ? rtrim($options['prefix'], '/') : $options['prefix']; - $path = drupal_encode_path($prefix . $path); - if (variable_get('clean_url', '0')) { - // With Clean URLs. + // With Clean URLs. + if (!empty($GLOBALS['conf']['clean_url'])) { + $path = drupal_encode_path($prefix . $path); if ($options['query']) { - return $base . $path . '?' . $options['query'] . $options['fragment']; + return $base . $path . '?' . drupal_http_build_query($options['query']) . $options['fragment']; } else { return $base . $path . $options['fragment']; } } + // Without Clean URLs. else { - // Without Clean URLs. - $variables = array(); + $path = $prefix . $path; + $query = array(); if (!empty($path)) { - $variables[] = 'q=' . $path; + $query['q'] = $path; } - if (!empty($options['query'])) { - $variables[] = $options['query']; + if ($options['query']) { + // We do not use array_merge() here to prevent overriding $path via query + // parameters. + $query += $options['query']; } - if ($query = join('&', $variables)) { - return $base . $script . '?' . $query . $options['fragment']; + if ($query) { + return $base . $script . '?' . drupal_http_build_query($query) . $options['fragment']; } else { return $base . $options['fragment']; @@ -3643,38 +3796,6 @@ function drupal_json_output($var = NULL) { } /** - * Wrapper around urlencode() which avoids Apache quirks. - * - * Should be used when placing arbitrary data in an URL. Note that Drupal paths - * are urlencoded() when passed through url() and do not require urlencoding() - * of individual components. - * - * Notes: - * - For esthetic reasons, we do not escape slashes. This also avoids a 'feature' - * in Apache where it 404s on any path containing '%2F'. - * - mod_rewrite unescapes %-encoded ampersands, hashes, and slashes when clean - * URLs are used, which are interpreted as delimiters by PHP. These - * characters are double escaped so PHP will still see the encoded version. - * - With clean URLs, Apache changes '//' to '/', so every second slash is - * double escaped. - * - This function should only be used on paths, not on query string arguments, - * otherwise unwanted double encoding will occur. - * - * @param $text - * String to encode - */ -function drupal_encode_path($text) { - if (variable_get('clean_url', '0')) { - return str_replace(array('%2F', '%26', '%23', '//'), - array('/', '%2526', '%2523', '/%252F'), - rawurlencode($text)); - } - else { - return str_replace('%2F', '/', rawurlencode($text)); - } -} - -/** * Returns a string of highly randomized bytes (over the full 8-bit range). * * This function is better than simply calling mt_rand() or any other built-in diff --git a/includes/form.inc b/includes/form.inc index d92e2a7ee..3ee8af7b7 100644 --- a/includes/form.inc +++ b/includes/form.inc @@ -2984,7 +2984,7 @@ function batch_process($redirect = NULL, $url = NULL) { // Set the batch number in the session to guarantee that it will stay alive. $_SESSION['batches'][$batch['id']] = TRUE; - drupal_goto($batch['url'], 'op=start&id=' . $batch['id']); + drupal_goto($batch['url'], array('op' => 'start', 'id' => $batch['id'])); } else { // Non-progressive execution: bypass the whole progressbar workflow diff --git a/includes/pager.inc b/includes/pager.inc index 554282b49..0bee418df 100644 --- a/includes/pager.inc +++ b/includes/pager.inc @@ -171,18 +171,18 @@ class PagerDefault extends SelectQueryExtender { } /** - * Compose a query string to append to pager requests. + * Compose a URL query parameter array for pager links. * * @return - * A query string that consists of all components of the current page request - * except for those pertaining to paging. + * A URL query parameter array that consists of all components of the current + * page request except for those pertaining to paging. */ -function pager_get_querystring() { - static $string = NULL; - if (!isset($string)) { - $string = drupal_query_string_encode($_REQUEST, array_merge(array('q', 'page'), array_keys($_COOKIE))); +function pager_get_query_parameters() { + $query = &drupal_static(__FUNCTION__); + if (!isset($query)) { + $query = drupal_get_query_parameters($_REQUEST, array_merge(array('q', 'page'), array_keys($_COOKIE))); } - return $string; + return $query; } /** @@ -465,11 +465,10 @@ function theme_pager_link($text, $page_new, $element, $parameters = array(), $at $query = array(); if (count($parameters)) { - $query[] = drupal_query_string_encode($parameters, array()); + $query = drupal_get_query_parameters($parameters, array()); } - $querystring = pager_get_querystring(); - if ($querystring != '') { - $query[] = $querystring; + if ($query_pager = pager_get_query_parameters()) { + $query = array_merge($query, $query_pager); } // Set each pager link title @@ -491,7 +490,7 @@ function theme_pager_link($text, $page_new, $element, $parameters = array(), $at } } - return l($text, $_GET['q'], array('attributes' => $attributes, 'query' => count($query) ? implode('&', $query) : NULL)); + return l($text, $_GET['q'], array('attributes' => $attributes, 'query' => $query)); } /** diff --git a/includes/tablesort.inc b/includes/tablesort.inc index 1a6c9f2f3..6c005977c 100644 --- a/includes/tablesort.inc +++ b/includes/tablesort.inc @@ -59,7 +59,7 @@ class TableSort extends SelectQueryExtender { protected function init() { $ts = $this->order(); $ts['sort'] = $this->getSort(); - $ts['query_string'] = $this->getQueryString(); + $ts['query'] = $this->getQueryParameters(); return $ts; } @@ -87,14 +87,16 @@ class TableSort extends SelectQueryExtender { } /** - * Compose a query string to append to table sorting requests. + * Compose a URL query parameter array to append to table sorting requests. * * @return - * A query string that consists of all components of the current page request - * except for those pertaining to table sorting. + * A URL query parameter array that consists of all components of the current + * page request except for those pertaining to table sorting. + * + * @see tablesort_get_query_parameters() */ - protected function getQueryString() { - return drupal_query_string_encode($_REQUEST, array_merge(array('q', 'sort', 'order'), array_keys($_COOKIE))); + protected function getQueryParameters() { + return tablesort_get_query_parameters(); } /** @@ -141,7 +143,7 @@ class TableSort extends SelectQueryExtender { function tablesort_init($header) { $ts = tablesort_get_order($header); $ts['sort'] = tablesort_get_sort($header); - $ts['query_string'] = tablesort_get_querystring(); + $ts['query'] = tablesort_get_query_parameters(); return $ts; } @@ -174,11 +176,7 @@ function tablesort_header($cell, $header, $ts) { $ts['sort'] = 'asc'; $image = ''; } - - if (!empty($ts['query_string'])) { - $ts['query_string'] = '&' . $ts['query_string']; - } - $cell['data'] = l($cell['data'] . $image, $_GET['q'], array('attributes' => array('title' => $title), 'query' => 'sort=' . $ts['sort'] . '&order=' . urlencode($cell['data']) . $ts['query_string'], 'html' => TRUE)); + $cell['data'] = l($cell['data'] . $image, $_GET['q'], array('attributes' => array('title' => $title), 'query' => array_merge($ts['query'], array('sort' => $ts['sort'], 'order' => $cell['data'])), 'html' => TRUE)); unset($cell['field'], $cell['sort']); } @@ -214,14 +212,14 @@ function tablesort_cell($cell, $header, $ts, $i) { } /** - * Compose a query string to append to table sorting requests. + * Compose a URL query parameter array for table sorting links. * * @return - * A query string that consists of all components of the current page request - * except for those pertaining to table sorting. + * A URL query parameter array that consists of all components of the current + * page request except for those pertaining to table sorting. */ -function tablesort_get_querystring() { - return drupal_query_string_encode($_REQUEST, array_merge(array('q', 'sort', 'order'), array_keys($_COOKIE))); +function tablesort_get_query_parameters() { + return drupal_get_query_parameters($_REQUEST, array_merge(array('q', 'sort', 'order'), array_keys($_COOKIE))); } /** |