diff --git a/control/HTTP.php b/control/HTTP.php index 2baf4131f..e400c97db 100644 --- a/control/HTTP.php +++ b/control/HTTP.php @@ -41,20 +41,43 @@ class HTTP { */ public static function absoluteURLs($html) { $html = str_replace('$CurrentPageURL', $_SERVER['REQUEST_URI'], $html); - return HTTP::urlRewriter($html, '(substr($URL,0,1) == "/") ? ( Director::protocolAndHost() . $URL ) :' - . ' ( (preg_match("/^[A-Za-z]+:/", $URL)) ? $URL : Director::absoluteBaseURL() . $URL )' ); + return HTTP::urlRewriter($html, function($url) { + return Director::absoluteURL($url, true); + }); } - /* - * Rewrite all the URLs in the given content, evaluating the given string as PHP code + /** + * Rewrite all the URLs in the given content, evaluating the given string as PHP code. * * Put $URL where you want the URL to appear, however, you can't embed $URL in strings * Some example code: - * '"../../" . $URL' - * 'myRewriter($URL)' - * '(substr($URL,0,1)=="/") ? "../" . substr($URL,1) : $URL' + * + * + * As of 3.2 $code should be a callable which takes a single parameter and returns + * the rewritten URL. e.g. + * + * + * function($url) { + * return Director::absoluteURL($url, true); + * } + * + * + * @param string $content The HTML to search for links to rewrite + * @param string|callable $code Either a string that can evaluate to an expression + * to rewrite links (depreciated), or a callable that takes a single + * parameter and returns the rewritten URL + * @return The content with all links rewritten as per the logic specified in $code */ public static function urlRewriter($content, $code) { + if(!is_callable($code)) { + Deprecation::notice(3.1, 'HTTP::urlRewriter expects a callable as the second parameter'); + } + + // Replace attributes $attribs = array("src","background","a" => "href","link" => "href", "base" => "href"); foreach($attribs as $tag => $attrib) { if(!is_numeric($tag)) $tagPrefix = "$tag "; @@ -64,19 +87,28 @@ class HTTP { $regExps[] = "/(<{$tagPrefix}[^>]*$attrib *= *')([^']*)(')/i"; $regExps[] = "/(<{$tagPrefix}[^>]*$attrib *= *)([^\"' ]*)( )/i"; } - $regExps[] = '/(background-image:[^;]*url *\()([^)]+)(\))/i'; - $regExps[] = '/(background:[^;]*url *\()([^)]+)(\))/i'; - $regExps[] = '/(list-style-image:[^;]*url *\()([^)]+)(\))/i'; - $regExps[] = '/(list-style:[^;]*url *\()([^)]+)(\))/i'; + // Replace css styles + // @todo - http://www.css3.info/preview/multiple-backgrounds/ + $styles = array('background-image', 'background', 'list-style-image', 'list-style', 'content'); + foreach($styles as $style) { + $regExps[] = "/($style:[^;]*url *\(\")([^\"]+)(\"\))/i"; + $regExps[] = "/($style:[^;]*url *\(')([^']+)('\))/i"; + $regExps[] = "/($style:[^;]*url *\()([^\"\)')]+)(\))/i"; + } - // Make + // Callback for regexp replacement $callback = function($matches) use($code) { - return - stripslashes($matches[1]) . - str_replace('$URL', stripslashes($matches[2]), $code) . - stripslashes($matches[3]); + if(is_callable($code)) { + $rewritten = $code($matches[2]); + } else { + // Expose the $URL variable to be used by the $code expression + $URL = $matches[2]; + $rewritten = eval("return ($code);"); + } + return $matches[1] . $rewritten . $matches[3]; }; + // Execute each expression foreach($regExps as $regExp) { $content = preg_replace_callback($regExp, $callback, $content); } diff --git a/tests/control/HTTPTest.php b/tests/control/HTTPTest.php index 5379c7ae9..7cc962c70 100644 --- a/tests/control/HTTPTest.php +++ b/tests/control/HTTPTest.php @@ -120,4 +120,82 @@ class HTTPTest extends SapphireTest { HTTP::get_mime_type(FRAMEWORK_DIR.'/tests/control/files/file.psd')); $this->assertEquals('audio/x-wav', HTTP::get_mime_type(FRAMEWORK_DIR.'/tests/control/files/file.wav')); } + + /** + * Test that absoluteURLs correctly transforms urls within CSS to absolute + */ + public function testAbsoluteURLsCSS() { + $this->withBaseURL('http://www.silverstripe.org/', function($test){ + + // background-image + // Note that using /./ in urls is absolutely acceptable + $test->assertEquals( + '
Content
', + HTTP::absoluteURLs('
Content
') + ); + + // background + $test->assertEquals( + '
Content
', + HTTP::absoluteURLs('
Content
') + ); + + // list-style-image + $test->assertEquals( + '
Content
', + HTTP::absoluteURLs('
Content
') + ); + + // list-style + $test->assertEquals( + '
Content
', + HTTP::absoluteURLs('
Content
') + ); + }); + } + + /** + * Test that absoluteURLs correctly transforms urls within html attributes to absolute + */ + public function testAbsoluteURLsAttributes() { + $this->withBaseURL('http://www.silverstripe.org/', function($test){ + + // links + $test->assertEquals( + 'SS Blog', + HTTP::absoluteURLs('SS Blog') + ); + + // background + // Note that using /./ in urls is absolutely acceptable + $test->assertEquals( + '
SS Blog
', + HTTP::absoluteURLs('
SS Blog
') + ); + + // image + $test->assertEquals( + '', + HTTP::absoluteURLs('') + ); + + // link + $test->assertEquals( + '', + HTTP::absoluteURLs('') + ); + }); + } + + /** + * Run a test while mocking the base url with the provided value + * @param string $url The base URL to use for this test + * @param callable $callback The test to run + */ + protected function withBaseURL($url, $callback) { + $oldBase = Director::$alternateBaseURL; + Director::setBaseURL($url); + $callback($this); + Director::setBaseURL($oldBase); + } }