From ebbd4119c2e0216b9b8a16ccf1436c65bf4fba12 Mon Sep 17 00:00:00 2001 From: Thomas Gelf Date: Mon, 17 Mar 2014 17:14:16 +0100 Subject: [PATCH] New approach for view helpers - register anonymous functions * May be subject to change * TODO: Find out how to phpDoc them * Removed a bunch of "old-style" view helpers * more to come --- application/views/helpers/Auth.php | 45 --------- application/views/helpers/Format.php | 42 -------- application/views/helpers/Href.php | 49 --------- application/views/helpers/Icon.php | 44 -------- application/views/helpers/Img.php | 59 ----------- application/views/helpers/Qlink.php | 64 ------------ application/views/helpers/TimeSince.php | 47 --------- application/views/helpers/TimeUnless.php | 47 --------- library/Icinga/Web/View.php | 92 +++++++++++++++-- library/Icinga/Web/View/helpers/format.php | 24 +++++ library/Icinga/Web/View/helpers/generic.php | 10 ++ library/Icinga/Web/View/helpers/url.php | 106 ++++++++++++++++++++ 12 files changed, 223 insertions(+), 406 deletions(-) delete mode 100644 application/views/helpers/Auth.php delete mode 100644 application/views/helpers/Format.php delete mode 100644 application/views/helpers/Href.php delete mode 100644 application/views/helpers/Icon.php delete mode 100644 application/views/helpers/Img.php delete mode 100644 application/views/helpers/Qlink.php delete mode 100644 application/views/helpers/TimeSince.php delete mode 100644 application/views/helpers/TimeUnless.php create mode 100644 library/Icinga/Web/View/helpers/format.php create mode 100644 library/Icinga/Web/View/helpers/generic.php create mode 100644 library/Icinga/Web/View/helpers/url.php diff --git a/application/views/helpers/Auth.php b/application/views/helpers/Auth.php deleted file mode 100644 index 4c8b41c44..000000000 --- a/application/views/helpers/Auth.php +++ /dev/null @@ -1,45 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 - * @author Icinga Development Team - * - */ -// {{{ICINGA_LICENSE_HEADER}}} - -use Icinga\Authentication\Manager; - -/** - * Class Zend_View_Helper_Auth - */ -class Zend_View_Helper_Auth extends Zend_View_Helper_Abstract -{ - public function auth() - { - return Manager::getInstance(); - } -} - -// @codingStandardsIgnoreEnd diff --git a/application/views/helpers/Format.php b/application/views/helpers/Format.php deleted file mode 100644 index 85f42944c..000000000 --- a/application/views/helpers/Format.php +++ /dev/null @@ -1,42 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 - * @author Icinga Development Team - * - */ -// {{{ICINGA_LICENSE_HEADER}}} - -use Icinga\Util\Format; - -class Zend_View_Helper_Format extends Zend_View_Helper_Abstract -{ - public function format() - { - return Format::getInstance(); - } -} - -// @codingStandardsIgnoreStop diff --git a/application/views/helpers/Href.php b/application/views/helpers/Href.php deleted file mode 100644 index 9f7e60983..000000000 --- a/application/views/helpers/Href.php +++ /dev/null @@ -1,49 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 - * @author Icinga Development Team - * - */ -// {{{ICINGA_LICENSE_HEADER}}} - -use Icinga\Web\Url; - -/** - * Helper class for creating absolute links from relative strings - * - */ -class Zend_View_Helper_Href extends Zend_View_Helper_Abstract -{ - - /** - * Turn the relative link $link into absolute one - * - * @param $link - */ - public function href($link, array $params = array()) { - return Url::fromPath($link, $params)->getAbsoluteUrl(); - } -} -// @codingStandardsIgnoreEnd diff --git a/application/views/helpers/Icon.php b/application/views/helpers/Icon.php deleted file mode 100644 index cb8525790..000000000 --- a/application/views/helpers/Icon.php +++ /dev/null @@ -1,44 +0,0 @@ - $val) { - $attributes[] = sprintf( - '%s="%s"', - filter_var($key, FILTER_SANITIZE_URL), - filter_var($val, FILTER_SANITIZE_FULL_SPECIAL_CHARS) - ); - } - if (! array_key_exists('alt', $properties)) { - $attributes[] = 'alt=""'; - } - if (! array_key_exists('class', $properties)) { - $attributes[] = 'class="icon"'; - } - if (! array_key_exists('title', $properties) && $title !== null) { - $attributes[] = 'title="' . htmlspecialchars($title) . '"'; - } - - return sprintf( - '', - Url::fromPath('img/icons/' . $img), - !empty($attributes) ? ' ' . implode(' ', $attributes) : '' - ); - } -} - -// @codingStandardsIgnoreStart diff --git a/application/views/helpers/Img.php b/application/views/helpers/Img.php deleted file mode 100644 index e6c1e651b..000000000 --- a/application/views/helpers/Img.php +++ /dev/null @@ -1,59 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 - * @author Icinga Development Team - * - */ -// {{{ICINGA_LICENSE_HEADER}}} - -/** - * Class Zend_View_Helper_Img - */ -class Zend_View_Helper_Img extends Zend_View_Helper_Abstract -{ - public function img($url, array $properties = array()) - { - $attributes = array(); - $has_alt = false; - foreach ($properties as $key => $val) { - if ($key === 'alt') $has_alt = true; - $attributes[] = sprintf( - '%s="%s"', - filter_var($key, FILTER_SANITIZE_URL), - filter_var($val, FILTER_SANITIZE_FULL_SPECIAL_CHARS) - ); - } - if (! $has_alt) $attributes[] = 'alt=""'; - - return sprintf( - '', - $this->view->baseUrl($url), - !empty($attributes) ? ' ' . implode(' ', $attributes) : '' - ); - } -} - -// @codingStandardsIgnoreStart diff --git a/application/views/helpers/Qlink.php b/application/views/helpers/Qlink.php deleted file mode 100644 index 5e7d8f567..000000000 --- a/application/views/helpers/Qlink.php +++ /dev/null @@ -1,64 +0,0 @@ - $val) { - if ($key === 'quote') { - $quote = $val; - continue; - } - if ($key === 'style' && is_array($val)) { - if (empty($val)) { - continue; - } - $parts = array(); - foreach ($val as $k => $v) { - $parts[] = "$k: $v"; - } - $attributes[] = 'style="' . implode('; ', $parts) . '"'; - continue; - } - $attributes[] = sprintf( - '%s="%s"', - //filter_var($key, FILTER_SANITIZE_URL), - rawurlencode($key), - //filter_var($val, FILTER_SANITIZE_FULL_SPECIAL_CHARS) - rawurlencode($val) - ); - - } - if ($urlFormat instanceof Url) { - $url = $urlFormat; - $uriParams = $url->getParams() + $uriParams; - } else { - $url = Url::fromPath($urlFormat); - } - $url->setParams($uriParams); - return sprintf( - '%s', - $url, - !empty($attributes) ? ' ' . implode(' ', $attributes) : '', - $quote - ? filter_var( - $htmlContent, - FILTER_SANITIZE_FULL_SPECIAL_CHARS, - FILTER_FLAG_NO_ENCODE_QUOTES - ) - // Alternativ: htmlentities($htmlContent) - : $htmlContent - ); - } -} - diff --git a/application/views/helpers/TimeSince.php b/application/views/helpers/TimeSince.php deleted file mode 100644 index 0995a12a0..000000000 --- a/application/views/helpers/TimeSince.php +++ /dev/null @@ -1,47 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 - * @author Icinga Development Team - * - */ -// {{{ICINGA_LICENSE_HEADER}}} - -use Icinga\Util\Format; - -/** - * Class Zend_View_Helper_TimeSince - */ -class Zend_View_Helper_TimeSince extends Zend_View_Helper_Abstract -{ - public function timeSince($timestamp) - { - return '' - . Format::timeSince($timestamp) - . ''; - } -} - -// @codingStandardsIgnoreStop diff --git a/application/views/helpers/TimeUnless.php b/application/views/helpers/TimeUnless.php deleted file mode 100644 index 6fde74f91..000000000 --- a/application/views/helpers/TimeUnless.php +++ /dev/null @@ -1,47 +0,0 @@ - - * @license http://www.gnu.org/licenses/gpl-2.0.txt GPL, version 2 - * @author Icinga Development Team - * - */ -// {{{ICINGA_LICENSE_HEADER}}} - -use Icinga\Util\Format; - -/** - * Class Zend_View_Helper_TimeSince - */ -class Zend_View_Helper_TimeUnless extends Zend_View_Helper_Abstract -{ - public function timeUnless($timestamp) - { - if (! $timestamp) return ''; - return '' - . Format::timeUntil($timestamp) - . ''; - } -} -// @codingStandardsIgnoreStop diff --git a/library/Icinga/Web/View.php b/library/Icinga/Web/View.php index c92955211..37093e853 100644 --- a/library/Icinga/Web/View.php +++ b/library/Icinga/Web/View.php @@ -29,15 +29,25 @@ namespace Icinga\Web; -use \Zend_View_Abstract; -use \Icinga\Web\Url; -use \Icinga\Util\Format; +use Icinga\Exception\ProgrammingError; +use Zend_View_Abstract; +use Closure; /** * Icinga view */ class View extends Zend_View_Abstract { + /** + * Charset to be used - we only support UTF-8 + */ + const CHARSET = 'UTF-8'; + + /** + * The flags we use for htmlspecialchars depend on our PHP version + */ + private $replaceFlags; + /** * Flag to register stream wrapper * @@ -45,6 +55,11 @@ class View extends Zend_View_Abstract */ private $useViewStream = false; + /** + * Registered helper functions + */ + private $helperFunctions = array(); + /** * Create a new view object * @@ -59,6 +74,13 @@ class View extends Zend_View_Abstract stream_wrapper_register('zend.view', '\Icinga\Web\ViewStream'); } } + + if (version_compare(PHP_VERSION, '5.4.0') >= 0) { + $this->replaceFlags = ENT_COMPAT | ENT_SUBSTITUTE | ENT_HTML5; + } else { + $this->replaceFlags = ENT_COMPAT | ENT_IGNORE; + } + parent::__construct($config); } @@ -72,6 +94,62 @@ class View extends Zend_View_Abstract $this->loadGlobalHelpers(); } + /** + * Escape the given value top be safely used in view scripts + * + * @param string $value The output to be escaped + * @return string + */ + public function escape($value) { + return htmlspecialchars($value, $this->replaceFlags, self::CHARSET, true); + } + + /** + * Whether a specific helper (closure) has been registered + * + * @param string $name The desired function name + * @return boolean + */ + public function hasHelperFunction($name) + { + return array_key_exists($name, $this->helperFunctions); + } + + /** + * Add a new helper function + * + * @param string $name The desired function name + * @param Closure $function An anonymous function + * @return self + */ + public function addHelperFunction($name, Closure $function) + { + if ($this->hasHelperFunction($name)) { + throw new ProgrammingError( + sprintf('Cannot assign the same helper function twice: "%s"', + $name + )); + } + + $this->helperFunctions[$name] = $function; + return $this; + } + + /** + * Call a helper function + * + * @param string $name The desired function name + * @param Array $args Function arguments + * @return mixed + */ + public function callHelperFunction($name, $args) + { + return call_user_func_array( + $this->helperFunctions[$name], + $args + ); + } + /** * Load helpers */ @@ -117,12 +195,8 @@ class View extends Zend_View_Abstract */ public function __call($name, $args) { - $namespaced = '\\Icinga\\Web\\View\\' . $name; - if (function_exists($namespaced)) { - return call_user_func_array( - $namespaced, - $args - ); + if ($this->hasHelperFunction($name)) { + return $this->callHelperFunction($name, $args); } else { return parent::__call($name, $args); } diff --git a/library/Icinga/Web/View/helpers/format.php b/library/Icinga/Web/View/helpers/format.php new file mode 100644 index 000000000..d06988cc2 --- /dev/null +++ b/library/Icinga/Web/View/helpers/format.php @@ -0,0 +1,24 @@ +addHelperFunction('format', function () { + return Format::getInstance(); +}); + +$this->addHelperFunction('timeSince', function ($timestamp) { + return '' + . Format::timeSince($timestamp) + . ''; +}); + +$this->addHelperFunction('timeUnless', function ($timestamp) { + if (! $timestamp) return ''; + return '' + . Format::timeUntil($timestamp) + . ''; +}); + diff --git a/library/Icinga/Web/View/helpers/generic.php b/library/Icinga/Web/View/helpers/generic.php new file mode 100644 index 000000000..c1be41a2c --- /dev/null +++ b/library/Icinga/Web/View/helpers/generic.php @@ -0,0 +1,10 @@ +addHelperFunction('auth', function () { + return Manager::getInstance(); +}); + diff --git a/library/Icinga/Web/View/helpers/url.php b/library/Icinga/Web/View/helpers/url.php new file mode 100644 index 000000000..b99d571fd --- /dev/null +++ b/library/Icinga/Web/View/helpers/url.php @@ -0,0 +1,106 @@ +addHelperFunction('href', function ($path = null, $params = null) use ($view) { + return $view->url($path, $params); +}); + +$this->addHelperFunction('url', function ($path = null, $params = null) { + if ($path === null) { + $url = Url::fromRequest(); + } elseif ($path instanceof Url) { + $url = $path; + } else { + $url = Url::fromPath($path); + } + if ($params !== null) { + $url->setParams($params); + } + + return $url; +}); + + +$this->addHelperFunction('qlink', function ($title, $url, $params = null, $properties = array()) use ($view) { + return sprintf( + '%s', + $view->url($url, $params), + $view->propertiesToString($properties), + $view->escape($title) + ); +}); + +$this->addHelperFunction('img', function ($url, array $properties = array()) use ($view) { + if (! array_key_exists('alt', $properties)) { + $properties['alt'] = ''; + } + + return sprintf( + '', + $view->url($url), + $view->propertiesToString($properties) + ); +}); + +$this->addHelperFunction('icon', function ($img, $title = null, array $properties = array()) use ($view) { + // TODO: join with classes passed in $properties? + $attributes = array( + 'class' => 'icon', + ); + if ($title !== null) { + $attributes['title'] = $title; + } + + return $view->img( + 'img/icons/' . $img, + array_merge($attributes, $properties) + ); +}); + +$this->addHelperFunction('propertiesToString', function ($properties) use ($view) { + if (empty($properties)) { + return ''; + } + $attributes = array(); + + foreach ($properties as $key => $val) { + if ($key === 'style' && is_array($val)) { + if (empty($val)) { + continue; + } + $parts = array(); + foreach ($val as $k => $v) { + $parts[] = "$k: $v"; + } + $val = implode('; ', $parts); + continue; + } + + $attributes[] = $view->attributeToString($key, $val); + } + return ' ' . implode(' ', $attributes); +}); + +$this->addHelperFunction('attributeToString', function ($key, $value) +{ + // TODO: Doublecheck this! + if (! preg_match('~^[a-zA-Z0-9-]+$~', $key)) { + throw new ProgrammingError(sprintf( + 'Trying to set an invalid HTML attribute name: %s', + $key + )); + } + + return sprintf( + '%s="%s"', + $key, + $value + ); +}); +