API CHANGE HTTP::setGetVar() always returns absolute URLs. Use Director::makeRelative() to make them relative again.

API CHANGE HTTP::setGetVar() combines any GET parameters in PHP array notation (e.g. "foo[bar]=val") instead of replacing the whole array
BUGFIX HTTP::setGetVar() uses parse_url() and http_build_query() to add query parameters to an existing URL, instead of doing its own regex-based parsing. This means existing GET parameters are correctly url encoded.

git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/branches/2.4@98373 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Ingo Schommer 2010-02-08 02:17:36 +00:00 committed by Sam Minnee
parent ff724b44de
commit 792b4cc1c4
2 changed files with 149 additions and 84 deletions

View File

@ -73,26 +73,59 @@ class HTTP {
return $content; return $content;
} }
/**
* Will try to include a GET parameter for an existing URL,
* preserving existing parameters and fragments.
* If no URL is given, falls back to $_SERVER['REQUEST_URI'].
*
* CAUTION: If the URL is determined to be relative,
* it is prepended with Director::absoluteBaseURL().
* This method will always return an absolute URL because
* Director::makeRelative() can lead to inconsistent results.
*
* @param String $varname
* @param String $varvalue
* @param String $currentURL Relative or absolute URL (Optional).
* @return String Absolute URL
*/
public static function setGetVar($varname, $varvalue, $currentURL = null) { public static function setGetVar($varname, $varvalue, $currentURL = null) {
$scriptbase = $currentURL ? $currentURL : $_SERVER['REQUEST_URI']; $uri = $currentURL ? $currentURL : Director::makeRelative($_SERVER['REQUEST_URI']);
$scriptbase = str_replace('&', '&', $scriptbase); // We need absolute URLs for parse_url()
$scriptbase = preg_replace('/\?' . quotemeta($varname) . '=([^&]*)&/', '?', $scriptbase); if(Director::is_relative_url($uri)) $uri = Director::absoluteBaseURL() . $uri;
$scriptbase = preg_replace('/([\?&]+)' . quotemeta($varname) . '=([^&]*)/', null, $scriptbase);
$suffix = ''; // try to parse uri
if(($hashPos = strpos($scriptbase,'#')) !== false) { $parts = parse_url($uri);
$suffix .= substr($scriptbase, $hashPos); if(!$parts) {
$scriptbase = substr($scriptbase, 0, $hashPos); throw new InvalidArgumentException("Can't parse URL: " . $uri);
} }
if($varvalue !== null) { // Parse params and add new variable
$scriptbase .= (strrpos($scriptbase,'?') !== false) ? '&' : '?'; $params = array();
$scriptbase .= "$varname=" . (isset($urlEncodeVarValue) ? urlencode($varvalue) : $varvalue); if(isset($parts['query'])) parse_str($parts['query'], $params);
} $params[$varname] = $varvalue;
$scriptbase = str_replace('&','&',$scriptbase); // Recompile Uri
return $scriptbase . $suffix; $newUri = $parts['scheme'] . '://' . (
isset($parts['user']) && $parts['user'] != '' && isset($parts['pass'])
? $parts['user'] . ':' . $parts['pass'] . '@'
: ''
) .
$parts['host'] . (
isset($parts['path']) && $parts['path'] != ''
? $parts['path']
: ''
) . (
($params)
? '?' . http_build_query($params)
: ''
) . (
isset($parts['fragment']) && $parts['fragment'] != ''
? '#' . $parts['fragment']
: ''
);
return $newUri;
} }
static function RAW_setGetVar($varname, $varvalue, $currentURL = null) { static function RAW_setGetVar($varname, $varvalue, $currentURL = null) {

View File

@ -47,19 +47,51 @@ class HTTPTest extends SapphireTest {
* Tests {@link HTTP::setGetVar()} * Tests {@link HTTP::setGetVar()}
*/ */
public function testSetGetVar() { public function testSetGetVar() {
$expected = array ( // HACK No easy way to get the current URL without the query string or fragment
'/?foo=bar' => array('foo', 'bar', '/'), $base = Director::absoluteBaseURL() . 'dev/tests/HTTPTest';
'/?baz=buz&foo=bar' => array('foo', 'bar', '/?baz=buz'),
'/?buz=baz&foo=baz' => array('foo', 'baz', '/?foo=bar&buz=baz'), $this->assertEquals(
'/?foo=var' => array('foo', 'var', '/?foo=&foo=bar'), $base . '?foo=bar',
'/?foo[test]=var' => array('foo[test]', 'var', '/?foo[test]=another') HTTP::setGetVar('foo', 'bar'),
'Omitting a URL falls back to current URL'
); );
foreach($expected as $result => $args) {
$this->assertEquals( $this->assertEquals(
call_user_func_array(array('HTTP', 'setGetVar'), $args), str_replace('&', '&', $result) Director::absoluteBaseURL() . 'relative/url?foo=bar',
HTTP::setGetVar('foo', 'bar', 'relative/url'),
'Relative URL without slash prefix returns URL with absolute base'
);
$this->assertEquals(
Director::absoluteBaseURL() . '/relative/url?foo=bar',
HTTP::setGetVar('foo', 'bar', '/relative/url'),
'Relative URL with slash prefix returns URL with absolute base'
);
$this->assertEquals(
Director::absoluteBaseURL() . '/relative/url?baz=buz&foo=bar',
HTTP::setGetVar('foo', 'bar', '/relative/url?baz=buz'),
'Relative URL with existing query params, and new added key'
);
$this->assertEquals(
'http://test.com/?foo=new&buz=baz',
HTTP::setGetVar('foo', 'new', 'http://test.com/?foo=old&buz=baz'),
'Absolute URL without path and multipe existing query params, overwriting an existing parameter'
);
$this->assertEquals(
'http://test.com/?foo=new',
HTTP::setGetVar('foo', 'new', 'http://test.com/?foo=&foo=old'),
'Absolute URL and empty query param'
);
$this->assertEquals(
// http_build_query() escapes angular brackets, they should be correctly urldecoded by the browser client
'http://test.com/?foo%5Btest%5D=one&foo%5Btest%5D=two',
HTTP::setGetVar('foo[test]', 'two', 'http://test.com/?foo[test]=one'),
'Absolute URL and PHP array query string notation'
); );
}
} }
} }