ソースを参照

Add font icon support for FormatHelper with BC.

euromark 11 年 前
コミット
d88af2bb04

+ 2 - 2
Controller/Component/Auth/TinyAuthorize.php

@@ -193,13 +193,13 @@ class TinyAuthorize extends BaseAuthorize {
 		if (!file_exists($path . ACL_FILE)) {
 			touch($path . ACL_FILE);
 		}
-		
+
 		if (function_exists('parse_ini_file')) {
 			$iniArray = parse_ini_file($path . ACL_FILE, true);
 		} else {
 			$iniArray = parse_ini_string(file_get_contents($path . ACL_FILE), true);
 		}
-		
+
 		$availableRoles = Configure::read($this->settings['aclModel']);
 		if (!is_array($availableRoles)) {
 			$Model = $this->getModel();

+ 264 - 0
Lib/View/StringTemplate.php

@@ -0,0 +1,264 @@
+<?php
+App::uses('PhpReader', 'Configure');
+
+/**
+ * Provides an interface for registering and inserting
+ * content into simple logic-less string templates.
+ *
+ * Used by several helpers to provide simple flexible templates
+ * for generating HTML and other content.
+ *
+ * Backported from CakePHP3.0
+ */
+class StringTemplate {
+
+/**
+ * List of attributes that can be made compact.
+ *
+ * @var array
+ */
+	protected $_compactAttributes = array(
+		'compact', 'checked', 'declare', 'readonly', 'disabled', 'selected',
+		'defer', 'ismap', 'nohref', 'noshade', 'nowrap', 'multiple', 'noresize',
+		'autoplay', 'controls', 'loop', 'muted', 'required', 'novalidate', 'formnovalidate'
+	);
+
+/**
+ * The default templates this instance holds.
+ *
+ * @var array
+ */
+	protected $_defaultConfig = [
+		'attribute' => '{{name}}="{{value}}"',
+		'compactAttribute' => '{{name}}="{{value}}"',
+	];
+
+	protected $_config;
+
+/**
+ * Contains the list of compiled templates
+ *
+ * @var array
+ */
+	protected $_compiled = array();
+
+/**
+ * Constructor.
+ *
+ * @param array $config A set of templates to add.
+ */
+	public function __construct(array $config = array()) {
+		$this->config($config);
+	}
+
+/**
+ * StringTemplate::config()
+ *
+ * @param string|array|null $key The key to get/set, or a complete array of configs.
+ * @param mixed|null $value The value to set.
+ * @param bool $merge Whether to merge or overwrite existing config defaults to true.
+ * @return mixed Config value being read, or the whole array itself on write operations.
+ */
+	public function config($key = null, $value = null, $merge = true) {
+		if ($key === null) {
+			return $this->_config;
+		}
+		if (is_array($key)) {
+			if ($merge) {
+				$this->_config = $key + $this->_defaultConfig;
+			} else {
+				$this->_config = $key;
+			}
+			return;
+		}
+
+		if (func_num_args() >= 2) {
+			if ($value === null) {
+				unset($this->_config[$key]);
+			} else {
+				$this->_config[$key] = $value;
+			}
+			return $this->_config;
+		}
+		if (!isset($this->_config[$key])) {
+			return null;
+		}
+		return $this->_config[$key];
+	}
+
+/**
+ * Registers a list of templates by name
+ *
+ * ### Example:
+ *
+ * {{{
+ * $templater->add([
+ *	'link' => '<a href="{{url}}">{{title}}</a>'
+ *	'button' => '<button>{{text}}</button>'
+ * ]);
+ * }}}
+ *
+ * @param array an associative list of named templates
+ * @return \Cake\View\StringTemplate same instance
+ */
+	public function add(array $templates) {
+		$this->config($templates);
+		$this->_compiled = array_diff_key($this->_compiled, $templates);
+		return $this;
+	}
+
+/**
+ * Load a config file containing templates.
+ *
+ * Template files should define a `$config` variable containing
+ * all the templates to load. Loaded templates will be merged with existing
+ * templates.
+ *
+ * @param string $file The file to load
+ * @return void
+ */
+	public function load($file) {
+		$loader = new PhpReader();
+		$templates = $loader->read($file);
+		$this->add($templates);
+	}
+
+/**
+ * Remove the named template.
+ *
+ * @param string $name The template to remove.
+ * @return void
+ */
+	public function remove($name) {
+		$this->config($name, null);
+		unset($this->_compiled[$name]);
+	}
+
+/**
+ * Returns an array containing the compiled template to be used with
+ * the sprintf function and a list of placeholder names that were found
+ * in the template in the order that they should be replaced.
+ *
+ * @param string $name The compiled template info
+ * @return array
+ */
+	public function compile($name) {
+		if (isset($this->_compiled[$name])) {
+			return $this->_compiled[$name];
+		}
+
+		$template = $this->config($name);
+		if ($template === null) {
+			return $this->_compiled[$name] = [null, null];
+		}
+
+		preg_match_all('#\{\{(\w+)\}\}#', $template, $matches);
+		return $this->_compiled[$name] = [
+			str_replace($matches[0], '%s', $template),
+			$matches[1]
+		];
+	}
+
+/**
+ * Format a template string with $data
+ *
+ * @param string $name The template name.
+ * @param array $data The data to insert.
+ * @return string
+ */
+	public function format($name, array $data) {
+		list($template, $placeholders) = $this->compile($name);
+		if ($template === null) {
+			return '';
+		}
+		$replace = array();
+		foreach ($placeholders as $placeholder) {
+			$replace[] = isset($data[$placeholder]) ? $data[$placeholder] : null;
+		}
+		return vsprintf($template, $replace);
+	}
+
+/**
+ * Returns a space-delimited string with items of the $options array. If a key
+ * of $options array happens to be one of those listed
+ * in `StringTemplate::$_compactAttributes` and its value is one of:
+ *
+ * - '1' (string)
+ * - 1 (integer)
+ * - true (boolean)
+ * - 'true' (string)
+ *
+ * Then the value will be reset to be identical with key's name.
+ * If the value is not one of these 4, the parameter is not output.
+ *
+ * 'escape' is a special option in that it controls the conversion of
+ * attributes to their html-entity encoded equivalents. Set to false to disable html-encoding.
+ *
+ * If value for any option key is set to `null` or `false`, that option will be excluded from output.
+ *
+ * This method uses the 'attribute' and 'compactAttribute' templates. Each of
+ * these templates uses the `name` and `value` variables. You can modify these
+ * templates to change how attributes are formatted.
+ *
+ * @param array $options Array of options.
+ * @param null|array $exclude Array of options to be excluded, the options here will not be part of the return.
+ * @return string Composed attributes.
+ */
+	public function formatAttributes($options, $exclude = null) {
+		$insertBefore = ' ';
+		$options = (array)$options + ['escape' => true];
+
+		if (!is_array($exclude)) {
+			$exclude = array();
+		}
+
+		$exclude = ['escape' => true, 'idPrefix' => true] + array_flip($exclude);
+		$escape = $options['escape'];
+		$attributes = array();
+
+		foreach ($options as $key => $value) {
+			if (!isset($exclude[$key]) && $value !== false && $value !== null) {
+				$attributes[] = $this->_formatAttribute($key, $value, $escape);
+			}
+		}
+		$out = trim(implode(' ', $attributes));
+		return $out ? $insertBefore . $out : '';
+	}
+
+/**
+ * Formats an individual attribute, and returns the string value of the composed attribute.
+ * Works with minimized attributes that have the same value as their name such as 'disabled' and 'checked'
+ *
+ * @param string $key The name of the attribute to create
+ * @param string|array $value The value of the attribute to create.
+ * @param bool $escape Define if the value must be escaped
+ * @return string The composed attribute.
+ */
+	protected function _formatAttribute($key, $value, $escape = true) {
+		if (is_array($value)) {
+			$value = implode(' ', $value);
+		}
+		if (is_numeric($key)) {
+			return $this->format('compactAttribute', [
+				'name' => $value,
+				'value' => $value
+			]);
+		}
+		$truthy = [1, '1', true, 'true', $key];
+		$isMinimized = in_array($key, $this->_compactAttributes);
+		if ($isMinimized && in_array($value, $truthy, true)) {
+			return $this->format('compactAttribute', [
+				'name' => $key,
+				'value' => $key
+			]);
+		}
+		if ($isMinimized) {
+			return '';
+		}
+		return $this->format('attribute', [
+			'name' => $key,
+			'value' => $escape ? h($value) : $value
+		]);
+	}
+
+}

+ 187 - 0
Test/Case/Lib/View/StringTemplateTest.php

@@ -0,0 +1,187 @@
+<?php
+App::uses('StringTemplate', 'Tools.View');
+
+class StringTemplateTest extends CakeTestCase {
+
+/**
+ * setUp
+ *
+ * @return void
+ */
+	public function setUp() {
+		parent::setUp();
+		$this->template = new StringTemplate();
+	}
+
+/**
+ * Test adding templates through the constructor.
+ *
+ * @return void
+ */
+	public function testConstructorAdd() {
+		$templates = [
+			'link' => '<a href="{{url}}">{{text}}</a>'
+		];
+		$template = new StringTemplate($templates);
+		debug($template->config('link'));
+		$this->assertEquals($templates['link'], $template->config('link'));
+	}
+
+/**
+ * test adding templates.
+ *
+ * @return void
+ */
+	public function testAdd() {
+		$templates = [
+			'link' => '<a href="{{url}}">{{text}}</a>'
+		];
+		$result = $this->template->add($templates);
+		$this->assertSame(
+			$this->template,
+			$result,
+			'The same instance should be returned'
+		);
+
+		$this->assertEquals($templates['link'], $this->template->config('link'));
+	}
+
+/**
+ * Test remove.
+ *
+ * @return void
+ */
+	public function testRemove() {
+		$templates = [
+			'link' => '<a href="{{url}}">{{text}}</a>'
+		];
+		$this->template->add($templates);
+		$this->assertNull($this->template->remove('link'), 'No return');
+		$this->assertNull($this->template->config('link'), 'Template should be gone.');
+	}
+
+/**
+ * Test formatting strings.
+ *
+ * @return void
+ */
+	public function testFormat() {
+		$templates = [
+			'link' => '<a href="{{url}}">{{text}}</a>'
+		];
+		$this->template->add($templates);
+
+		$result = $this->template->format('not there', []);
+		$this->assertSame('', $result);
+
+		$result = $this->template->format('link', [
+			'url' => '/',
+			'text' => 'example'
+		]);
+		$this->assertEquals('<a href="/">example</a>', $result);
+	}
+
+/**
+ * Test loading templates files in the app.
+ *
+ * @return void
+ */
+	public function testLoad() {
+		$this->skipIf(true, 'Find a way to mock the path from /Tools/Config to /Tools/Test/test_app/Config');
+
+		$this->template->remove('attribute');
+		$this->template->remove('compactAttribute');
+		$this->assertEquals([], $this->template->config());
+		$this->assertNull($this->template->load('Tools.test_templates'));
+		$this->assertEquals('<a href="{{url}}">{{text}}</a>', $this->template->config('link'));
+	}
+
+/**
+ * Test that loading non-existing templates causes errors.
+ *
+ * @expectedException ConfigureException
+ * @expectedExceptionMessage Could not load configuration file
+ */
+	public function testLoadErrorNoFile() {
+		$this->template->load('no_such_file');
+	}
+
+/**
+ * Test formatting compact attributes.
+ *
+ * @return void
+ */
+	public function testFormatAttributesCompact() {
+		$attrs = ['disabled' => true, 'selected' => 1, 'checked' => '1', 'multiple' => 'multiple'];
+		$result = $this->template->formatAttributes($attrs);
+		$this->assertEquals(
+			' disabled="disabled" selected="selected" checked="checked" multiple="multiple"',
+			$result
+		);
+
+		$attrs = ['disabled' => false, 'selected' => 0, 'checked' => '0', 'multiple' => null];
+		$result = $this->template->formatAttributes($attrs);
+		$this->assertEquals(
+			'',
+			$result
+		);
+	}
+
+/**
+ * Test formatting normal attributes.
+ *
+ * @return void
+ */
+	public function testFormatAttributes() {
+		$attrs = ['name' => 'bruce', 'data-hero' => '<batman>'];
+		$result = $this->template->formatAttributes($attrs);
+		$this->assertEquals(
+			' name="bruce" data-hero="&lt;batman&gt;"',
+			$result
+		);
+
+		$attrs = ['escape' => false, 'name' => 'bruce', 'data-hero' => '<batman>'];
+		$result = $this->template->formatAttributes($attrs);
+		$this->assertEquals(
+			' name="bruce" data-hero="<batman>"',
+			$result
+		);
+
+		$attrs = ['name' => 'bruce', 'data-hero' => '<batman>'];
+		$result = $this->template->formatAttributes($attrs, ['name']);
+		$this->assertEquals(
+			' data-hero="&lt;batman&gt;"',
+			$result
+		);
+	}
+
+/**
+ * Test formatting array attributes.
+ *
+ * @return void
+ */
+	public function testFormatAttributesArray() {
+		$attrs = ['name' => ['bruce', 'wayne']];
+		$result = $this->template->formatAttributes($attrs);
+		$this->assertEquals(
+			' name="bruce wayne"',
+			$result
+		);
+	}
+
+/**
+ * Tests that compile information is refreshed on adds and removes
+ *
+ * @return void
+ */
+	public function testCopiledInfoRefresh() {
+		$compilation = $this->template->compile('link');
+		$this->template->add([
+			'link' => '<a bar="{{foo}}">{{baz}}</a>'
+		]);
+		$this->assertNotEquals($compilation, $this->template->compile('link'));
+		$this->template->remove('link');
+		$this->assertEquals([null, null], $this->template->compile('link'));
+	}
+
+}

+ 2 - 2
Test/Case/Model/Behavior/SluggedBehaviorTest.php

@@ -44,10 +44,10 @@ class SluggedBehaviorTest extends CakeTestCase {
 			'ẞ' => 'SS'
 		),
 		'latin' => array (
-			'À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Å' => 'A','Ă' => 'A', 'Æ' => 'AE', 'Ç' =>
+			'À' => 'A', 'Á' => 'A', 'Â' => 'A', 'Ã' => 'A', 'Å' => 'A', 'Ă' => 'A', 'Æ' => 'AE', 'Ç' =>
 			'C', 'È' => 'E', 'É' => 'E', 'Ê' => 'E', 'Ë' => 'E', 'Ì' => 'I', 'Í' => 'I', 'Î' => 'I',
 			'Ï' => 'I', 'Ð' => 'D', 'Ñ' => 'N', 'Ò' => 'O', 'Ó' => 'O', 'Ô' => 'O', 'Õ' => 'O', 'Ő' => 'O', 'Ø' => 'O',
-			'Ș' => 'S','Ț' => 'T', 'Ù' => 'U', 'Ú' => 'U', 'Û' => 'U', 'Ű' => 'U',
+			'Ș' => 'S', 'Ț' => 'T', 'Ù' => 'U', 'Ú' => 'U', 'Û' => 'U', 'Ű' => 'U',
 			'Ý' => 'Y', 'Þ' => 'TH', 'à' => 'a', 'á' => 'a', 'â' => 'a', 'ã' => 'a',
 			'å' => 'a', 'ă' => 'a', 'æ' => 'ae', 'ç' => 'c', 'è' => 'e', 'é' => 'e', 'ê' => 'e', 'ë' => 'e',
 			'ì' => 'i', 'í' => 'i', 'î' => 'i', 'ï' => 'i', 'ð' => 'd', 'ñ' => 'n', 'ò' => 'o', 'ó' =>

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

@@ -36,6 +36,7 @@ class FormatHelperTest extends MyCakeTestCase {
 	}
 
 	/**
+	 * @return void
 	 */
 	public function testWarning() {
 		$content = 'xyz';
@@ -51,6 +52,59 @@ class FormatHelperTest extends MyCakeTestCase {
 	}
 
 	/**
+	 * FormatHelperTest::testIcon()
+	 *
+	 * @return void
+	 */
+	public function testIcon() {
+		$result = $this->Format->icon('edit');
+		$expected = '<img src="/img/icons/edit.gif" title="' . __('Edit') . '" alt="[' . __('Edit') . ']" class="icon" />';
+		$this->assertEquals($expected, $result);
+	}
+
+	/**
+	 * FormatHelperTest::testIconWithFontIcon()
+	 *
+	 * @return void
+	 */
+	public function testIconWithFontIcon() {
+		$this->Format->settings['fontIcons'] = array('edit' => 'fa fa-pencil');
+		$result = $this->Format->icon('edit');
+		$expected = '<i class="fa fa-pencil edit" title="' . __('Edit') . '" data-placement="bottom" data-toggle="tooltip"></i>';
+		$this->assertEquals($expected, $result);
+	}
+
+	/**
+	 * FormatHelperTest::testSpeedOfIcons()
+	 *
+	 * @return void
+	 */
+	public function testSpeedOfIcons() {
+		$count = 1000;
+
+		$time1 = microtime(true);
+		for ($i = 0; $i < $count; $i++) {
+			$result = $this->Format->icon('edit');
+		}
+		$time2 = microtime(true);
+
+		$this->Format->settings['fontIcons'] = array('edit' => 'fa fa-pencil');
+
+		$time3 = microtime(true);
+		for ($i = 0; $i < $count; $i++) {
+			$result = $this->Format->icon('edit');
+		}
+		$time4 = microtime(true);
+
+		$normalIconSpeed = number_format($time2 - $time1, 2);
+		$this->debug('Normal Icons: ' . $normalIconSpeed);
+		$fontIconViaStringTemplateSpeed = number_format($time4 - $time3, 2);
+		$this->debug('StringTemplate and Font Icons: ' . $fontIconViaStringTemplateSpeed);
+		$this->assertTrue($fontIconViaStringTemplateSpeed < $normalIconSpeed);
+	}
+
+	/**
+	 * @return void
 	 */
 	public function testFontIcon() {
 		$result = $this->Format->fontIcon('signin');

+ 7 - 0
Test/test_app/Config/test_templates.php

@@ -0,0 +1,7 @@
+<?php
+/**
+ * Template strings for testing.
+ */
+$config = [
+	'link' => '<a href="{{url}}">{{text}}</a>',
+];

+ 57 - 15
View/Helper/FormatHelper.php

@@ -1,5 +1,6 @@
 <?php
 App::uses('TextHelper', 'View/Helper');
+App::uses('StringTemplate', 'Tools.View');
 
 /**
  * Format helper with basic html snippets
@@ -18,8 +19,24 @@ class FormatHelper extends TextHelper {
 	 */
 	public $helpers = array('Html', 'Tools.Numeric');
 
+	protected $_defaultConfig = array(
+		'fontIcons' => false
+	);
+
+	public function __construct(View $View, $config = array()) {
+		$config += $this->_defaultConfig;
+
+		if ($config['fontIcons'] === true) {
+			$config['fontIcons'] = (array)Configure::read('Format.fontIcons');
+		}
+
+		parent::__construct($View, $config);
+	}
+
 	/**
 	 * jqueryAccess: {id}Pro, {id}Contra
+	 *
+	 * @return string
 	 */
 	public function thumbs($id, $inactive = false, $inactiveTitle = null) {
 		$class = 'Active';
@@ -46,6 +63,7 @@ class FormatHelper extends TextHelper {
 	 * - name: title name: next{Record} (if none is provided, "record" is used - not translated!)
 	 * - slug: true/false (defaults to false)
 	 * - titleField: field or Model.field
+	 * @return string
 	 */
 	public function neighbors($neighbors, $field, $options = array()) {
 		if (mb_strpos($field, '.') !== false) {
@@ -159,7 +177,10 @@ class FormatHelper extends TextHelper {
 	/**
 	 * Returns img from customImgFolder
 	 *
-	 * @param ARRAY options (ending [default: gif])
+	 * @param string $folder
+	 * @param string $icon
+	 * @param bool $checkExists
+	 * @param array $options (ending [default: gif])
 	 * @return string
 	 */
 	public function customIcon($folder, $icon = null, $checkExist = false, $options = array(), $attr = array()) {
@@ -275,9 +296,9 @@ class FormatHelper extends TextHelper {
 	 *
 	 * @param string|array $icon
 	 * @param array $options
-	 * @return void
+	 * @return string
 	 */
-	public function fontIcon($icon, $options = array()) {
+	public function fontIcon($icon, array $options = array(), array $attributes = array()) {
 		$icon = (array)$icon;
 		$class = array();
 		foreach ($icon as $i) {
@@ -304,7 +325,7 @@ class FormatHelper extends TextHelper {
 	}
 
 	/**
-	 * Quick way of printing default icons (have to be 16px X 16px !!!)
+	 * Quick way of printing default icons
 	 *
 	 * @param type
 	 * @param title
@@ -314,8 +335,6 @@ class FormatHelper extends TextHelper {
 	 * @return string
 	 */
 	public function icon($type, $t = null, $a = null, $translate = null, $options = array()) {
-		$html = '';
-
 		if (isset($t) && $t === false) {
 			$title = '';
 		} elseif (empty($t)) {
@@ -347,13 +366,36 @@ class FormatHelper extends TextHelper {
 			$alt = '';
 		}
 
-		$defaultOptions = array('title' => $title, 'alt' => $alt, 'class' => 'icon');
-		//$newOptions['onclick'] = $options['onclick'];
-		$newOptions = array_merge($defaultOptions, $options);
+		if (!$this->settings['fontIcons'] || !isset($this->settings['fontIcons'][$type])) {
+			$defaults = array('title' => $title, 'alt' => $alt, 'class' => 'icon');
+			$newOptions = $options + $defaults;
 
-		$html .= $this->Html->image('icons/' . $pic, $newOptions);
+			return $this->Html->image('icons/' . $pic, $newOptions);
+		}
 
-		return $html;
+		$iconType = $this->settings['fontIcons'][$type];
+
+		$templates = array(
+			'icon' => '<i class="{{class}}" title="{{title}}" data-placement="bottom" data-toggle="tooltip"></i>',
+		);
+		if (!isset($this->template)) {
+			$this->template = new StringTemplate($templates);
+		}
+
+		if (!$title) {
+			$title = ucfirst($type);
+			if ($translate !== false) {
+				$title = __($title);
+			}
+		}
+
+		$defaults = array(
+			'title' => $title,
+			'class' => $iconType . ' ' . $type
+		);
+
+		$options += $defaults;
+		return $this->template->format('icon', $options);
 	}
 
 	/**
@@ -599,11 +641,11 @@ class FormatHelper extends TextHelper {
 	/**
 	 * Display yes/no symbol.
 	 *
+	 * @todo $on=1, $text=false, $ontitle=false,... => in array(OPTIONS) packen
+	 *
 	 * @param text: default FALSE; if TRUE, text instead of the image
 	 * @param ontitle: default FALSE; if it is embadded in a link, set to TRUE
 	 * @return image:Yes/No or text:Yes/No
-	 *
-	 * @todo $on=1, $text=false, $ontitle=false,... => in array(OPTIONS) packen
 	 */
 	public function yesNo($v, $ontitle = null, $offtitle = null, $on = 1, $text = false, $notitle = false) {
 		$ontitle = (!empty($ontitle) ? $ontitle : __('Ja'));
@@ -633,7 +675,7 @@ class FormatHelper extends TextHelper {
 	/**
 	 * Get URL of a png img of a website (16x16 pixel).
 	 *
-	 * @parm string domain
+	 * @param string domain
 	 * @return string
 	 */
 	public function siteIconUrl($domain) {
@@ -745,7 +787,7 @@ class FormatHelper extends TextHelper {
 	}
 
 	/**
-	 * Generate a pagination count: #1 etc for each pagiation record
+	 * Generates a pagination count: #1 etc for each pagination record
 	 * respects order (ASC/DESC)
 	 *
 	 * @param array $paginator