浏览代码

FormatHelper font icons and MyAsset fix

euromark 12 年之前
父节点
当前提交
39d53d9bf4

+ 27 - 24
Lib/InlineCssLib.php

@@ -1,8 +1,8 @@
 <?php
 
 /**
- * Wrapper for Inline CSS replacement
- * Useful for sending HTML emails
+ * Wrapper for Inline CSS replacement.
+ * Useful for sending HTML emails.
  *
  * Note: requires vendors CssToInline or emogrifier!
  * Default engine: CssToInline
@@ -35,7 +35,7 @@ class InlineCssLib {
 	public function __construct($settings = array()) {
 		$defaults = am($this->_defaults, (array) Configure::read('InlineCss'));
 		$this->settings = array_merge($defaults, $settings);
-		if (!method_exists($this, '_process'.ucfirst($this->settings['engine']))) {
+		if (!method_exists($this, '_process' . ucfirst($this->settings['engine']))) {
 			throw new InternalErrorException('Engine does not exist');
 		}
 	}
@@ -48,7 +48,7 @@ class InlineCssLib {
 		if (($html = trim($html)) === '') {
 			return $html;
 		}
-		$method = '_process'.ucfirst($this->settings['engine']);
+		$method = '_process' . ucfirst($this->settings['engine']);
 		return $this->{$method}($html, $css);
 	}
 
@@ -58,7 +58,7 @@ class InlineCssLib {
 	 */
 	protected function _processEmogrifier($html, $css) {
 		$css .= $this->_extractAndRemoveCss($html);
-		App::import('Vendor', 'Emogrifier', array('file' => 'emogrifier'.DS.'emogrifier.php'));
+		App::import('Vendor', 'Emogrifier', array('file' => 'emogrifier' . DS . 'emogrifier.php'));
 		$Emogrifier = new Emogrifier($html, $css);
 
 		return @$Emogrifier->emogrify();
@@ -71,7 +71,7 @@ class InlineCssLib {
 	 * @return string HTML output
 	 */
 	protected function _processCssToInline($html, $css) {
-		App::import('Vendor', 'CssToInline', array('file' => 'css_to_inline_styles'.DS.'css_to_inline_styles.php'));
+		App::import('Vendor', 'CssToInline', array('file' => 'css_to_inline_styles' . DS . 'css_to_inline_styles.php'));
 
 		//fix issue with <html> being added
 		$separator = '~~~~~~~~~~~~~~~~~~~~';
@@ -109,7 +109,7 @@ class InlineCssLib {
 	}
 
 	/**
-	 * some reverse function of strip_tags with blacklisting instead of whitelisting
+	 * Some reverse function of strip_tags with blacklisting instead of whitelisting
 	 * //maybe move to Tools.Utility/String/Text?
 	 *
 	 * @return string $cleanedStr
@@ -117,14 +117,17 @@ class InlineCssLib {
 	 */
 	public function stripOnly($str, $tags, $stripContent = false) {
 		$content = '';
-		if(!is_array($tags)) {
+		if (!is_array($tags)) {
 			$tags = (strpos($str, '>') !== false ? explode('>', str_replace('<', '', $tags)) : array($tags));
-			if(end($tags) == '') array_pop($tags);
+			if (end($tags) === '') {
+				array_pop($tags);
+			}
 		}
 		foreach($tags as $tag) {
-			if ($stripContent)
-				 $content = '(.+</'.$tag.'[^>]*>|)';
-			 $str = preg_replace('#</?'.$tag.'[^>]*>'.$content.'#is', '', $str);
+			if ($stripContent) {
+				 $content = '(.+</' . $tag . '[^>]*>|)';
+			}
+			$str = preg_replace('#</?' . $tag . '[^>]*>' . $content . '#is', '', $str);
 		}
 		return $str;
 	}
@@ -154,12 +157,12 @@ class InlineCssLib {
 				if ($link->hasAttribute('media')) {
 					foreach($this->media_types as $css_link_media) {
 						if (strstr($link->getAttribute('media'), $css_link_media)) {
-							$css .= $this->_findAndLoadCssFile($link->getAttribute('href'))."\n\n";
+							$css .= $this->_findAndLoadCssFile($link->getAttribute('href')) . "\n\n";
 							$remove_doms[] = $link;
 						}
 					}
 				} else {
-					$css .= $this->_findAndLoadCssFile($link->getAttribute('href'))."\n\n";
+					$css .= $this->_findAndLoadCssFile($link->getAttribute('href')) . "\n\n";
 					$remove_doms[] = $link;
 				}
 			}
@@ -208,9 +211,9 @@ class InlineCssLib {
 		// Build an array of the ever more path specific $cssHref location
 		$cssHrefs = split(DS, $cssHref);
 		$cssHref_paths = array();
-		for($i=count($cssHrefs)-1; $i>0; $i--) {
-			if(isset($cssHref_paths[count($cssHref_paths)-1])) {
-				$cssHref_paths[] = $cssHrefs[$i].DS.$cssHref_paths[count($cssHref_paths)-1];
+		for ($i = count($cssHrefs) - 1; $i > 0; $i--) {
+			if (isset($cssHref_paths[count($cssHref_paths) - 1])) {
+				$cssHref_paths[] = $cssHrefs[$i] . DS . $cssHref_paths[count($cssHref_paths) - 1];
 			}
 			else {
 				$cssHref_paths[] = $cssHrefs[$i];
@@ -220,11 +223,11 @@ class InlineCssLib {
 		// the longest string match will be the match we are looking for
 		$best_css_filename = null;
 		$best_css_match_length = 0;
-		foreach($css_filenames as $css_filename) {
-			foreach($cssHref_paths as $cssHref_path) {
-				$regex = "/".str_replace('/','\/', str_replace('.', '\.', $cssHref_path))."/";
-				if(preg_match($regex, $css_filename, $match)) {
-					if(strlen($match[0]) > $best_css_match_length) {
+		foreach ($css_filenames as $css_filename) {
+			foreach ($cssHref_paths as $cssHref_path) {
+				$regex = '/' . str_replace('/', '\/', str_replace('.', '\.', $cssHref_path)) . '/';
+				if (preg_match($regex, $css_filename, $match)) {
+					if (strlen($match[0]) > $best_css_match_length) {
 						$best_css_match_length = strlen($match[0]);
 						$best_css_filename = $css_filename;
 					}
@@ -233,7 +236,7 @@ class InlineCssLib {
 		}
 
 		$css = null;
-		if(!empty($best_css_filename) && is_file($best_css_filename)) {
+		if (!empty($best_css_filename) && is_file($best_css_filename)) {
 			$css = file_get_contents($best_css_filename);
 		}
 
@@ -267,7 +270,7 @@ class InlineCssLib {
 	protected function _parseInlineCssAndLoadImports($css) {
 		// Remove any <!-- --> comment tags - they are valid in HTML but we probably
 		// don't want to be commenting out CSS
-		$css = str_replace('-->', '', str_replace('<!--', '', $css))."\n\n";
+		$css = str_replace('-->', '', str_replace('<!--', '', $css)) . "\n\n";
 
 		// Load up the @import CSS if any exists
 		preg_match_all("/\@import.*?url\((.*?)\)/i", $css, $matches);

+ 289 - 0
Lib/MyAsset.php

@@ -0,0 +1,289 @@
+<?php
+
+# idea: serve (tools) package files on the fly...
+class MyAsset {
+
+	protected $pluginPaths = array();
+
+	protected $url;
+
+	protected $parseData;
+
+	/**
+	 * MyAsset::js()
+	 *
+	 * @param bool $setHeaders
+	 * @return string Script tags.
+	 */
+	public function js($setHeaders = true) {
+		$this->_init();
+
+		$res = $this->_parse($_SERVER['QUERY_STRING'], 'js');
+
+		if ($setHeaders) {
+			$this->_headers('js');
+		}
+		return $this->_get($res, 'js');
+	}
+
+	/**
+	 * MyAsset::css()
+	 *
+	 * @param bool $setHeaders
+	 * @return string Style tags.
+	 */
+	public function css($setHeaders = true) {
+		$this->_init();
+
+		$res = $this->_parse($_SERVER['QUERY_STRING'], 'css');
+
+		if ($setHeaders) {
+			$this->_headers('css');
+		}
+		return $this->_get($res, 'css');
+	}
+
+	/**
+	 * MyAsset::_headers()
+	 *
+	 * @param string $type
+	 * @return void
+	 */
+	public function _headers($type) {
+		if ($type === 'js') {
+			$type = 'text/javascript'; //'application/x-javascript';
+		} elseif ($type === 'css') {
+			$type = 'text/css';
+		}
+		header("Date: " . date("D, j M Y G:i:s ", time()) . 'GMT');
+		header("Content-Type: ".$type.'; charset=utf-8');
+		header("Expires: " . gmdate("D, j M Y H:i:s", time() + DAY) . " GMT");
+		header("Cache-Control: max-age=".DAY.", must-revalidate"); // HTTP/1.1
+		header("Pragma: cache"); // HTTP/1.0
+	}
+
+	/**
+	 * searches, combines, packs, caches and returns scripts
+	 * 2011-03-23 ms
+	 */
+	public function _get($assets, $type = null) {
+		$script = array();
+		foreach ($assets as $plugin => $packages) {
+			foreach ($packages as $package => $packageFiles) {
+				$path = $this->_path($plugin, $package, $type);
+				foreach ($packageFiles as $file) {
+					$script[] = $this->_read($path . $file);
+				}
+			}
+		}
+		$script = implode(PHP_EOL, $script);
+
+		return $script;
+	}
+
+	public function _init() {
+		if (empty($_SERVER['QUERY_STRING'])) {
+			header('HTTP/1.1 404 Not Found');
+			exit('File Not Found');
+		}
+		Configure::write('debug', 0);
+	}
+
+	/**
+	 * get the content of a single file
+	 * 2011-03-23 ms
+	 */
+	public function _read($path) {
+		$path = str_replace('/', DS, $path);
+		if (strpos($path, '..') !== false) {
+			trigger_error('MyAsset: Invalid file ('.$path.') ['.$this->url.']');
+			return '';
+		}
+		if (!file_exists($path)) {
+			trigger_error('MyAsset: File not exists ('.$path.') ['.$this->url.']');
+			return '';
+		}
+		$data = file_get_contents($path);
+		//TODO: compress?
+		return $data;
+	}
+
+	/**
+	 * @deprecated?
+	 * 2011-03-23 ms
+	 */
+	public function _makeCleanCss($path, $name, $pack = false) {
+
+		$data = file_get_contents($path);
+		if (!$pack) {
+			$output = " /* file: $name, uncompressed */ " . $data;
+			return $output;
+		}
+		if (!isset($this->Css)) {
+			App::import('Vendor', 'csspp' . DS . 'csspp');
+			$this->Css = new csspp();
+		}
+		$output = $this->Css->compress($data);
+		$ratio = 100 - (round(strlen($output) / strlen($data), 3) * 100);
+		$output = " /* file: $name, ratio: $ratio% */ " . $output;
+		return $output;
+	}
+
+	/**
+	 * @return string $result or bool FALSE on failure
+	 * 2011-03-23 ms
+	 */
+	public function _readCssCache($path) {
+		if (file_exists($path)) {
+			return file_get_contents($path);
+		}
+		return false;
+	}
+
+	/**
+	 * @return bool $result
+	 * @deprecated?
+	 * 2011-03-23 ms
+	 */
+	public function _writeCssCache($path, $content) {
+		if (!is_dir(dirname($path))) {
+			if (!mkdir(dirname($path), 0755, true)) {
+				trigger_error('MyAsset: Cannot create cache folder');
+				return false;
+			}
+		}
+		$cache = new File($path);
+		return $cache->write($content);
+	}
+
+	/**
+	 * Get correct path of asset file.
+	 *
+	 * - PluginName.Asset => webroot/asset/ dir in plugin (new)
+	 * - App.Webroot => webroot/ dir
+	 * - App => packages
+	 * - Root => packages
+	 * - PluginName => packages in plugin
+	 *
+	 * @return string Path or bool false on failure.
+	 * 2011-03-23 ms
+	 */
+	public function _path($plugin, $package, $type = null) {
+		if ($type !== 'js' && $type !== 'css') {
+			return false;
+		}
+		$pluginPathKey = $plugin;
+		if ($package === 'Asset') {
+			$pluginPathKey .= $package;
+		}
+		if (isset($this->pluginPaths[$pluginPathKey])) {
+			return $this->pluginPaths[$pluginPathKey];
+		}
+		if ($plugin === 'App' && $package === 'Webroot') {
+			$pluginPath = WWW_ROOT . $type . DS;
+		} elseif ($plugin === 'App') {
+			$pluginPath = APP . 'packages' . DS;
+		} elseif ($plugin === 'Root') {
+			$pluginPath = ROOT . DS . 'packages' . DS;
+		} elseif ($package === 'Asset') {
+			$pluginPath = App::pluginPath($plugin) . 'webroot' . DS . 'asset' . DS;
+			$plugin = $plugin . 'Asset';
+		} else {
+			$pluginPath = App::pluginPath($plugin) . 'packages' . DS;
+		}
+		if (!$pluginPath) {
+			return false;
+		}
+		if ($package === 'Webroot' || $package === 'Asset') {
+			$packagePath = '';
+		} else {
+			$packagePath = strtolower($package) . DS . 'files' . DS;
+		}
+
+		$this->pluginPaths[$pluginPathKey] = $pluginPath;
+		return $this->pluginPaths[$pluginPathKey] . $packagePath;
+	}
+
+	/**
+	 * url (example): file=x & file=Tools|y & file=Tools.Jquery|jquery/sub/z
+	 * => x is in webroot/
+	 * => y is in plugins/tools/webroot/
+	 * => z is in plugins/tools/packages/jquery/files/jquery/sub/
+	 * 2011-03-23 ms
+	 */
+	public function _parse($string, $type = null) {
+		$parts = explode('&', urldecode($string));
+		$res = array();
+		foreach ($parts as $part) {
+			if (preg_match('|\.\.|', $part)) {
+				trigger_error('MyAsset: Invalid piece ('.$part.')');
+				continue;
+			}
+			$plugin = 'App';
+			$package = 'Webroot';
+			list($key, $content) = explode('=', $part, 2);
+			if ($key !== 'file') {
+				continue;
+			}
+			if (strpos($content, '|') !== false) {
+				list($plugin, $content) = explode('|', $content, 2);
+				if (strpos($plugin, '.') !== false) {
+					list($plugin, $package) = explode('.', $plugin, 2);
+				}
+			}
+			if ($type === 'js') {
+				if (substr($content, -3) !== '.js') {
+					$content .= '.js';
+				}
+			} elseif ($type === 'css') {
+				if (substr($content, -4) !== '.css') {
+					$content .= '.css';
+				}
+			}
+			$res[$plugin][$package][] = $content;
+		}
+		$this->url = $string;
+		$this->parseData = $res;
+		return $res;
+	}
+
+}
+
+/*
+
+	if (preg_match('|\.\.|', $url) || !preg_match('|^ccss/(.+)$|i', $url, $regs)) {
+		die('Wrong file name.');
+	}
+
+	$filename = 'css/' . $regs[1];
+	$filepath = CSS . $regs[1];
+	$cachepath = CACHE . 'css' . DS . str_replace(array('/','\\'), '-', $regs[1]);
+
+	if (!file_exists($filepath)) {
+		die('Wrong file name.');
+	}
+
+	if (file_exists($cachepath)) {
+		$templateModified = filemtime($filepath);
+		$cacheModified = filemtime($cachepath);
+
+		if ($templateModified > $cacheModified) {
+			$output = make_clean_css($filepath, $filename);
+			write_css_cache($cachepath, $output);
+		} else {
+			$output = file_get_contents($cachepath);
+		}
+	} else {
+		$output = make_clean_css($filepath, $filename);
+		write_css_cache($cachepath, $output);
+		$templateModified = time();
+	}
+
+	header("Date: " . date("D, j M Y G:i:s ", $templateModified) . 'GMT');
+	header("Content-Type: text/css");
+	header("Expires: " . gmdate("D, d M Y H:i:s", time() + DAY) . " GMT");
+	header("Cache-Control: max-age=86400, must-revalidate"); // HTTP/1.1
+	header("Pragma: cache"); // HTTP/1.0
+	print $output;
+
+*/

+ 19 - 0
Test/Case/View/Helper/FormatHelperTest.php

@@ -10,6 +10,8 @@ App::uses('View', 'View');
  */
 class FormatHelperTest extends MyCakeTestCase {
 
+	public $Format;
+
 	public function setUp() {
 		parent::setUp();
 
@@ -53,6 +55,23 @@ class FormatHelperTest extends MyCakeTestCase {
 	/**
 	 * 2009-08-30 ms
 	 */
+	public function testFontIcon() {
+		$result = $this->Format->fontIcon('signin');
+		$expected = '<i class="icon-signin"></i>';
+		$this->assertEquals($expected, $result);
+
+		$result = $this->Format->fontIcon('signin', array('rotate' => 90));
+		$expected = '<i class="icon-signin icon-rotate-90"></i>';
+		$this->assertEquals($expected, $result);
+
+		$result = $this->Format->fontIcon('signin', array('size' => 5, 'extra' => array('muted')));
+		$expected = '<i class="icon-signin icon-muted icon-5x"></i>';
+		$this->assertEquals($expected, $result);
+	}
+
+	/**
+	 * 2009-08-30 ms
+	 */
 	public function testOk() {
 		$content = 'xyz';
 		$data = array(

+ 258 - 0
Test/Case/View/Helper/GravatarHelperTest.php

@@ -0,0 +1,258 @@
+<?php
+
+if (!defined('CAKEPHP_UNIT_TEST_EXECUTION')) {
+	define('CAKEPHP_UNIT_TEST_EXECUTION', 1);
+
+}
+
+define('VALID_TEST_EMAIL', 'graham@grahamweldon.com'); # for testing normal behavior
+define('GARBIGE_TEST_EMAIL', 'test@test.de'); # for testing default image behavior
+
+App::uses('HtmlHelper', 'View/Helper');
+App::uses('GravatarHelper', 'Tools.View/Helper');
+App::uses('MyCakeTestCase', 'Tools.TestSuite');
+App::uses('View', 'View');
+
+/**
+ * Gravatar Test Case
+ *
+ * 2010-05-27 ms
+ */
+class GravatarHelperTest extends MyCakeTestCase {
+
+	/**
+	 * setUp method
+	 */
+	public function setUp() {
+		parent::setUp();
+
+		$this->Gravatar = new GravatarHelper(new View(null));
+		$this->Gravatar->Html = new HtmlHelper(new View(null));
+	}
+
+	/**
+	 * tearDown method
+	 */
+	public function tearDown() {
+		parent::tearDown();
+
+		unset($this->Gravatar);
+	}
+
+/** OWN ONES **/
+
+	/**
+	 * @access public
+	 * @return void
+	 * 2009-07-30 ms
+	 */
+	public function testDefaultImages() {
+
+		$is = $this->Gravatar->defaultImages();
+		$expectedCount = 7;
+
+		foreach ($is as $image) {
+			echo $image.' ';
+		}
+		$this->assertTrue(is_array($is) && (count($is) === $expectedCount));
+
+	}
+
+	/**
+	 * @access public
+	 * @return void
+	 * 2009-07-30 ms
+	 */
+	public function testImages() {
+
+		$is = $this->Gravatar->image(GARBIGE_TEST_EMAIL);
+		echo $is;
+		$this->assertTrue(!empty($is));
+
+		$is = $this->Gravatar->image(Configure::read('Config.admin_email'));
+		echo $is;
+		$this->assertTrue(!empty($is));
+
+		$is = $this->Gravatar->image(VALID_TEST_EMAIL);
+		echo $is;
+		$this->assertTrue(!empty($is));
+
+		$is = $this->Gravatar->image(VALID_TEST_EMAIL, array('size'=>'200'));
+		echo $is;
+		$this->assertTrue(!empty($is));
+
+		$is = $this->Gravatar->image(VALID_TEST_EMAIL, array('size'=>'20'));
+		echo $is;
+		$this->assertTrue(!empty($is));
+
+		$is = $this->Gravatar->image(VALID_TEST_EMAIL, array('rating'=>'X')); # note the capit. x
+		echo $is;
+		$this->assertTrue(!empty($is));
+
+		$is = $this->Gravatar->image(VALID_TEST_EMAIL, array('ext'=>true));
+		echo $is;
+		$this->assertTrue(!empty($is));
+
+		$is = $this->Gravatar->image(VALID_TEST_EMAIL, array('default'=>'none'));
+		echo $is;
+		$this->assertTrue(!empty($is));
+
+		$is = $this->Gravatar->image(GARBIGE_TEST_EMAIL, array('default'=>'none'));
+		echo $is;
+		$this->assertTrue(!empty($is));
+
+		$is = $this->Gravatar->image(GARBIGE_TEST_EMAIL, array('default'=>'http://2.gravatar.com/avatar/8379aabc84ecee06f48d8ca48e09eef4?d=identicon'));
+		echo $is;
+		$this->assertTrue(!empty($is));
+
+	}
+
+/** BASE TEST CASES **/
+
+/**
+ * testBaseUrlGeneration
+ *
+ * @return void
+ * @access public
+ */
+	public function testBaseUrlGeneration() {
+		$expected = 'http://www.gravatar.com/avatar/' . md5('example@gravatar.com');
+		$result = $this->Gravatar->imageUrl('example@gravatar.com', array('ext' => false, 'default' => 'wavatar'));
+		list($url, $params) = explode('?', $result);
+		$this->assertEquals($expected, $url);
+	}
+
+/**
+ * testExtensions
+ *
+ * @return void
+ * @access public
+ */
+	public function testExtensions() {
+		$result = $this->Gravatar->imageUrl('example@gravatar.com', array('ext' => true, 'default' => 'wavatar'));
+		$this->assertRegExp('/\.jpg(?:$|\?)/', $result);
+	}
+
+/**
+ * testRating
+ *
+ * @return void
+ * @access public
+ */
+	public function testRating() {
+		$result = $this->Gravatar->imageUrl('example@gravatar.com', array('ext' => true, 'default' => 'wavatar'));
+		$this->assertRegExp('/\.jpg(?:$|\?)/', $result);
+	}
+
+/**
+ * testAlternateDefaultIcon
+ *
+ * @return void
+ * @access public
+ */
+	public function testAlternateDefaultIcon() {
+		$result = $this->Gravatar->imageUrl('example@gravatar.com', array('ext' => false, 'default' => 'wavatar'));
+		list($url, $params) = explode('?', $result);
+		$this->assertRegExp('/default=wavatar/', $params);
+	}
+
+/**
+ * testAlternateDefaultIconCorrection
+ *
+ * @return void
+ * @access public
+ */
+	public function testAlternateDefaultIconCorrection() {
+		$result = $this->Gravatar->imageUrl('example@gravatar.com', array('ext' => false, 'default' => '12345'));
+		$this->assertRegExp('/[^\?]+/', $result);
+	}
+
+/**
+ * testSize
+ *
+ * @return void
+ * @access public
+ */
+	public function testSize() {
+		$result = $this->Gravatar->imageUrl('example@gravatar.com', array('size' => '120'));
+		list($url, $params) = explode('?', $result);
+		$this->assertRegExp('/size=120/', $params);
+	}
+
+/**
+ * testImageTag
+ *
+ * @return void
+ * @access public
+ */
+	public function testImageTag() {
+		$expected = '<img src="http://www.gravatar.com/avatar/' . md5('example@gravatar.com') . '" alt="" />';
+		$result = $this->Gravatar->image('example@gravatar.com', array('ext' => false));
+		$this->assertEquals($expected, $result);
+
+		$expected = '<img src="http://www.gravatar.com/avatar/' . md5('example@gravatar.com') . '" alt="Gravatar" />';
+		$result = $this->Gravatar->image('example@gravatar.com', array('ext' => false, 'alt' => 'Gravatar'));
+		$this->assertEquals($expected, $result);
+	}
+
+/**
+ * testDefaulting
+ *
+ * @return void
+ * @access public
+ */
+	public function testDefaulting() {
+		$result = $this->Gravatar->imageUrl('example@gravatar.com', array('default' => 'wavatar', 'size' => 'default'));
+		list($url, $params) = explode('?', $result);
+		$this->assertEquals($params, 'default=wavatar');
+	}
+
+/**
+ * testNonSecureUrl
+ *
+ * @return void
+ * @access public
+ */
+	public function testNonSecureUrl() {
+		$_SERVER['HTTPS'] = false;
+
+		$expected = 'http://www.gravatar.com/avatar/' . md5('example@gravatar.com');
+		$result = $this->Gravatar->imageUrl('example@gravatar.com', array('ext' => false));
+		$this->assertEquals($expected, $result);
+
+		$expected = 'http://www.gravatar.com/avatar/' . md5('example@gravatar.com');
+		$result = $this->Gravatar->imageUrl('example@gravatar.com', array('ext' => false, 'secure' => false));
+		$this->assertEquals($expected, $result);
+
+		$_SERVER['HTTPS'] = true;
+		$expected = 'http://www.gravatar.com/avatar/' . md5('example@gravatar.com');
+		$result = $this->Gravatar->imageUrl('example@gravatar.com', array('ext' => false, 'secure' => false));
+		$this->assertEquals($expected, $result);
+	}
+
+/**
+ * testSecureUrl
+ *
+ * @return void
+ * @access public
+ */
+	public function testSecureUrl() {
+		$expected = 'https://secure.gravatar.com/avatar/' . md5('example@gravatar.com');
+		$result = $this->Gravatar->imageUrl('example@gravatar.com', array('ext' => false, 'secure' => true));
+		$this->assertEquals($expected, $result);
+
+		$_SERVER['HTTPS'] = true;
+
+		$this->Gravatar = new GravatarHelper(new View(null));
+		$this->Gravatar->Html = new HtmlHelper(new View(null));
+
+		$expected = 'https://secure.gravatar.com/avatar/' . md5('example@gravatar.com');
+		$result = $this->Gravatar->imageUrl('example@gravatar.com', array('ext' => false));
+		$this->assertEquals($expected, $result);
+
+		$expected = 'https://secure.gravatar.com/avatar/' . md5('example@gravatar.com');
+		$result = $this->Gravatar->imageUrl('example@gravatar.com', array('ext' => false, 'secure' => true));
+		$this->assertEquals($expected, $result);
+	}
+
+}

+ 1 - 1
View/Helper/FormExtHelper.php

@@ -42,7 +42,7 @@ class FormExtHelper extends FormHelper {
 			$this->settings['webroot'] = $webroot;
 		}
 		if (($js = Configure::read('Asset.js')) !== null) {
-			$this->settings['js'] = $webroot;
+			$this->settings['js'] = $js;
 		}
 
 		parent::__construct($View, $settings);

+ 42 - 1
View/Helper/FormatHelper.php

@@ -303,6 +303,47 @@ class FormatHelper extends TextHelper {
 	}
 
 	/**
+	 * Display a font icon (fast and resource-efficient).
+	 * Uses http://fontawesome.io/icons/
+	 *
+	 * Options:
+	 * - size (int|string: 1...5 or large)
+	 * - rotate (integer: 90, 270, ...)
+	 * - spin (booelan: true/false)
+	 * - extra (array: muted, light, dark, border)
+	 * - pull (string: left, right)
+	 *
+	 * @param string|array $icon
+	 * @param array $options
+	 * @return void
+	 */
+	public function fontIcon($icon, $options = array()) {
+		$icon = (array)$icon;
+		$class = array();
+		foreach ($icon as $i) {
+			$class[] = 'icon-' . $i;
+		}
+		if (!empty($options['extra'])) {
+			foreach ($options['extra'] as $i) {
+				$class[] = 'icon-' . $i;
+			}
+		}
+		if (!empty($options['size'])) {
+			$class[] = 'icon-' . ($options['size'] === 'large' ? 'large' : $options['size'] . 'x');
+		}
+		if (!empty($options['pull'])) {
+			$class[] = 'pull-' . $options['pull'];
+		}
+		if (!empty($options['rotate'])) {
+			$class[] = 'icon-rotate-' . (int)$options['rotate'];
+		}
+		if (!empty($options['spin'])) {
+			$class[] = 'icon-spin';
+		}
+		return '<i class="' . implode(' ', $class) . '"></i>';
+	}
+
+	/**
 	 * Quick way of printing default icons (have to be 16px X 16px !!!)
 	 * @param type
 	 * @param title
@@ -343,7 +384,7 @@ class FormatHelper extends TextHelper {
 			}
 			$alt = '['.$alt.']';
 		} else {
-			$pic='pixelspace.gif';
+			$pic = 'pixelspace.gif';
 			$title = '';
 			$alt = '';
 		}

+ 17 - 2
View/Helper/GravatarHelper.php

@@ -81,11 +81,26 @@ class GravatarHelper extends AppHelper {
  * @return string Gravatar image string
  */
 	public function image($email, $options = array()) {
-		$imageUrl = $this->url($email, $options);
+		$imageUrl = $this->imageUrl($email, $options);
 		unset($options['default'], $options['size'], $options['rating'], $options['ext']);
 		return $this->Html->image($imageUrl, $options);
 	}
 
+	/**
+	 * GravatarHelper::url()
+	 *
+	 * @param mixed $email
+	 * @param bool $options
+	 * @return void
+	 * @deprecated Use imageUrl() instead.
+	 */
+	public function url($email = null, $options = false) {
+		if ($options === false) {
+			$options = array();
+		}
+		$this->imageUrl($email, $options);
+	}
+
 /**
  * Generate image URL
  * TODO: rename to avoid E_STRICT errors here
@@ -94,7 +109,7 @@ class GravatarHelper extends AppHelper {
  * @param string $options Array of options, keyed from default settings
  * @return string Gravatar Image URL
  */
-	public function url($email, $options = array()) {
+	public function imageUrl($email, $options = array()) {
 		$options = $this->_cleanOptions(array_merge($this->_default, $options));
 		$ext = $options['ext'];
 		$secure = $options['secure'];