diff --git a/lib/php/Savvy.php b/lib/php/Savvy.php index 00110a1202b09cfb4b686a5b26bd2d419f0f4700..6412d049231f6602a055fb203d794f28c7a76424 100644 --- a/lib/php/Savvy.php +++ b/lib/php/Savvy.php @@ -10,7 +10,7 @@ * @copyright 2010 Brett Bieber * @license http://www.opensource.org/licenses/bsd-license.php New BSD License * @version SVN: $Id$ - * @link http://svn.php.net/repository/pear2/Savvy + * @link https://github.com/saltybeagle/savvy */ /** @@ -21,46 +21,47 @@ * @author Brett Bieber <saltybeagle@php.net> * @copyright 2010 Brett Bieber * @license http://www.opensource.org/licenses/bsd-license.php New BSD License - * @link http://svn.php.net/repository/pear2/Savvy + * @link https://github.com/saltybeagle/savvy */ class Savvy { /** - * + * * Array of configuration parameters. - * + * * @access protected - * + * * @var array - * + * */ - + protected $__config = array( - 'compiler' => null, - 'filters' => array(), - 'escape' => null, + 'compiler' => null, + 'filters' => array(), + 'escape' => null, + 'iterate_traversable' => false, ); - + /** * Parameters for escaping. - * + * * @var array */ protected $_escape = array( 'quotes' => ENT_COMPAT, 'charset' => 'UTF-8', ); - + /** * The output template to render using - * + * * @var string */ protected $template; /** * stack of templates, so we can access the parent template - * + * * @var array */ protected $templateStack = array(); @@ -68,35 +69,35 @@ class Savvy /** * To avoid stats on locating templates, populate this array with * full path => 1 for any existing templates - * + * * @var array */ protected $templateMap = array(); - + /** * An array of paths to look for template files in. - * + * * @var array */ protected $template_path = array('./'); /** * The current controller to use - * + * * @var string */ protected $selected_controller; - + /** * How class names are translated to templates - * + * * @var MapperInterface */ protected $class_to_template; /** * Array of globals available within every template - * + * * @var array */ protected $globals = array(); @@ -105,52 +106,56 @@ class Savvy // Constructor and magic methods // // ----------------------------------------------------------------- - - + /** - * + * * Constructor. - * + * * @access public - * + * * @param array $config An associative array of configuration keys for * the Main object. Any, or none, of the keys may be set. - * + * * @return Savvy A Savvy instance. - * + * */ - + public function __construct($config = null) { $savvy = $this; $this->selected_controller = 'basic'; - + // set the default template search path if (isset($config['template_path'])) { // user-defined dirs $this->setTemplatePath($config['template_path']); } - + // set the output escaping callbacks if (isset($config['escape'])) { $this->setEscape($config['escape']); } - + // set the default filter callbacks if (isset($config['filters'])) { $this->addFilters($config['filters']); } + + // set whether to iterate over Traversable objects + if (isset($config['iterate_traversable'])) { + $this->setIterateTraversable($config['iterate_traversable']); + } } /** * Basic output controller - * + * * @param mixed $context The context passed to the template * @param mixed $parent Parent template with context and parents $parent->context * @param mixed $file The filename to include * @param Savvy $savvy The Savvy templating system - * + * * @return string */ protected static function basicOutputController($context, $parent, $file, $savvy) @@ -161,17 +166,18 @@ class Savvy unset($__name, $__value); ob_start(); include $file; + return ob_get_clean(); } /** * Basic output controller - * + * * @param mixed $context The context passed to the template * @param mixed $parent Parent template with context and parents $parent->context * @param mixed $file The filename to include * @param Savvy $savvy The Savvy templating system - * + * * @return string */ protected static function filterOutputController($context, $parent, $file, $savvy) @@ -182,17 +188,18 @@ class Savvy unset($__name, $__value); ob_start(); include $file; + return $savvy->applyFilters(ob_get_clean()); } /** * Basic output controller - * + * * @param mixed $context The context passed to the template * @param mixed $parent Parent template with context and parents $parent->context * @param mixed $file The filename to include * @param Savvy $savvy The Savvy templating system - * + * * @return string */ protected static function basicCompiledOutputController($context, $parent, $file, $savvy) @@ -203,17 +210,18 @@ class Savvy unset($__name, $__value); ob_start(); include $savvy->template($file); + return ob_get_clean(); } /** * Basic output controller - * + * * @param mixed $context The context passed to the template * @param mixed $parent Parent template with context and parents $parent->context * @param mixed $file The filename to include * @param Savvy $savvy The Savvy templating system - * + * * @return string */ protected static function filterCompiledOutputController($context, $parent, $file, $savvy) @@ -224,17 +232,18 @@ class Savvy unset($__name, $__value); ob_start(); include $savvy->template($file); + return $savvy->applyFilters(ob_get_clean()); } /** * Basic output controller - * + * * @param mixed $context The context passed to the template * @param mixed $parent Parent template with context and parents $parent->context * @param mixed $file The filename to include * @param Savvy $savvy The Savvy templating system - * + * * @return string */ protected static function basicFastCompiledOutputController($context, $parent, $file, $savvy) @@ -244,12 +253,12 @@ class Savvy /** * Basic output controller - * + * * @param mixed $context The context passed to the template * @param mixed $parent Parent template with context and parents $parent->context * @param mixed $file The filename to include * @param Savvy $savvy The Savvy templating system - * + * * @return string */ protected static function filterFastCompiledOutputController($context, $parent, $file, $savvy) @@ -260,13 +269,24 @@ class Savvy /** * Add a global variable which will be available inside every template * - * @param string $var The global variable name - * @param mixed $value The value + * Inside templates, reference the global using the name passed + * <code> + * $savvy->addGlobal('formHelper', new FormHelper()); + * </code> * + * Sample template, Form.tpl.php + * <code> + * echo $formHelper->renderInput('name'); + * </code> + * + * @param string $var The global variable name + * @param mixed $value The value or variable to expose globally + * * @return void */ - function addGlobal($name, $value) + public function addGlobal($name, $value) { + // disallow specific variable names, these are reserved variables switch ($name) { case 'context': case 'parent': @@ -276,68 +296,86 @@ class Savvy throw new Savvy_BadMethodCallException('Invalid global variable name'); } + // if output is currently escaped, make sure the global is escaped if ($this->__config['escape']) { - switch (gettype($value)) { - case 'object': - if (!$value instanceof Savvy_ObjectProxy) { - $value = Savvy_ObjectProxy::factory($value, $this); - } - break; - case 'string': - case 'int': - case 'double': - $value = $this->escape($value); - break; - case 'array': - $value = new Savvy_ObjectProxy_ArrayIterator($value, $this); - break; - } + $value = $this->filterVar($value); } $this->globals[$name] = $value; } + /** + * Filter a variable of unknown type + * + * @param mixed $var The variable to filter + * + * @return string|Savvy_ObjectProxy + */ + public function filterVar($var) + { + switch (gettype($var)) { + case 'object': + if ($var instanceof ArrayIterator) { + return new Savvy_ObjectProxy_ArrayIterator($var, $this); + } + if ($var instanceof ArrayAccess) { + return new Savvy_ObjectProxy_ArrayAccess($var, $this); + } + + return Savvy_ObjectProxy::factory($var, $this); + case 'string': + case 'integer': + case 'double': + return $this->escape($var); + case 'array': + return new Savvy_ObjectProxy_ArrayObject( + new \ArrayObject($var), + $this + ); + } + + return $var; + } + /** * Get the array of assigned globals - * + * * @return array */ - function getGlobals() + public function getGlobals() { return $this->globals; } /** * Return the current template set (if any) - * + * * @return string */ - function getTemplate() + public function getTemplate() { return $this->template; } - - + // ----------------------------------------------------------------- // // Public configuration management (getters and setters). - // + // // ----------------------------------------------------------------- - - + /** * * Returns a copy of the Savvy configuration parameters. * * @access public - * + * * @param string $key The specific configuration key to return. If null, * returns the entire configuration array. - * + * * @return mixed A copy of the $this->__config array. - * + * */ - + public function getConfig($key = null) { if (is_null($key)) { @@ -351,25 +389,24 @@ class Savvy return $this->__config[$key]; } } - - + /** - * + * * Sets a custom compiler/pre-processor callback for template sources. - * + * * By default, Savvy does not use a compiler; use this to set your * own custom compiler (pre-processor) for template sources. - * + * * @access public - * + * * @param mixed $compiler A compiler callback value suitable for the * first parameter of call_user_func(). Set to null/false/empty to * use PHP itself as the template markup (i.e., no compiling). - * + * * @return void - * + * */ - + public function setCompiler(Savvy_CompilerInterface $compiler) { $this->__config['compiler'] = $compiler; @@ -384,56 +421,69 @@ class Savvy $this->selected_controller = 'filterfastcompiled'; break; } + return; } if (!strpos($this->selected_controller, 'compiled')) { $this->selected_controller .= 'compiled'; } } - + /** * Set the class to template mapper. - * + * * @see MapperInterface - * - * @param MapperInterface $mapper The mapper interface to use - * + * + * @param MapperInterface $mapper The mapper interface to use + * * @return Main */ - function setClassToTemplateMapper(Savvy_MapperInterface $mapper) + public function setClassToTemplateMapper(Savvy_MapperInterface $mapper) { $this->class_to_template = $mapper; + return $this; } - + /** * Get the class to template mapper. - * + * * @return MapperInterface */ - function getClassToTemplateMapper() + public function getClassToTemplateMapper() { if (!isset($this->class_to_template)) { $this->setClassToTemplateMapper(new Savvy_ClassToTemplateMapper()); } + return $this->class_to_template; } - - + + public function setIterateTraversable($iterate) + { + $this->__config['iterate_traversable'] = (bool)$iterate; + + return $this; + } + + public function getIterateTraversable() + { + return $this->__config['iterate_traversable']; + } + // ----------------------------------------------------------------- // // Output escaping and management. // // ----------------------------------------------------------------- - - + /** - * + * * Clears then sets the callbacks to use when calling $this->escape(). - * + * * Each parameter passed to this function is treated as a separate * callback. For example: - * + * * <code> * $savvy->setEscape( * 'stripslashes', @@ -442,20 +492,20 @@ class Savvy * array($object, $method) * ); * </code> - * + * * @access public * * @return Main * */ - + public function setEscape() { $this->__config['escape'] = @func_get_args(); + return $this; } - - + /** * * Gets the array of output-escaping callbacks. @@ -465,13 +515,12 @@ class Savvy * @return array The array of output-escaping callbacks. * */ - + public function getEscape() { return $this->__config['escape']; } - - + /** * Escapes a value for output in a view script. * @@ -479,7 +528,7 @@ class Savvy * {@link $_encoding} setting. * * @param mixed $var The output to escape. - * + * * @return mixed The escaped value. */ public function escape($var) @@ -495,50 +544,50 @@ class Savvy $var = call_user_func($escape, $var); } } + return $var; } - - + // ----------------------------------------------------------------- // // File management // // ----------------------------------------------------------------- - + /** * Get the template path. - * + * * @return array */ - function getTemplatePath() + public function getTemplatePath() { return $this->template_path; } - + /** * * Sets an entire array of search paths for templates or resources. * * @access public - * + * * @param string|array $path The new set of search paths. If null or * false, resets to the current directory only. * * @return Main * */ - + public function setTemplatePath($path = null) { // clear out the prior search dirs, add default $this->template_path = array('./'); - + // actually add the user-specified directories $this->addTemplatePath($path); + return $this; } - - + /** * * Adds to the search path for templates and resources. @@ -550,35 +599,35 @@ class Savvy * @return Main * */ - + public function addTemplatePath($path) { // convert from path string to array of directories if (is_string($path) && !strpos($path, '://')) { - + // the path config is a string, and it's not a stream // identifier (the "://" piece). add it as a path string. $path = explode(PATH_SEPARATOR, $path); - + // typically in path strings, the first one is expected // to be searched first. however, Savvy uses a stack, // so the first would be last. reverse the path string // so that it behaves as expected with path strings. $path = array_reverse($path); - + } else { - + // just force to array settype($path, 'array'); - + } - + // loop through the path directories foreach ($path as $dir) { - + // no surrounding spaces allowed! $dir = trim($dir); - + // add trailing separators as needed if (strpos($dir, '://')) { if (substr($dir, -1) != '/') { @@ -601,26 +650,26 @@ class Savvy ); } } - - + + /** - * + * * Searches the directory paths for a given file. - * + * * @param string $file The file name to look for. - * + * * @return string|bool The full path and file name for the target file, * or boolean false if the file is not found in any of the paths. * */ - + public function findTemplateFile($file) { if (false !== strpos($file, '..')) { // checking for weird path here removes directory traversal threat throw new Savvy_UnexpectedValueException('upper directory reference .. cannot be used in template filename'); } - + // start looping through the path set foreach ($this->template_path as $path) { // get the path to the file @@ -640,79 +689,80 @@ class Savvy // could not find the file in the set of paths throw new Savvy_TemplateException('Could not find the template ' . $file); } - - + + // ----------------------------------------------------------------- // // Template processing // // ----------------------------------------------------------------- - + /** * Render context data through a template. - * + * * This method allows you to render data through a template. Typically one * will pass the model they wish to display through an optional template. * If no template is specified, the ClassToTemplateMapper::map() method * will be called which should return the name of a template to render. - * + * * Arrays will be looped over and rendered through the template specified. - * - * Strings, ints, and doubles will returned if no template parameter is + * + * Strings, ints, and doubles will returned if no template parameter is * present. - * + * * Within templates, two variables will be available, $context and $savvy. * The $context variable will contain the data passed to the render method, * the $savvy object will be an instance of the Main class with which you * can render nested data through partial templates. - * - * @param mixed $mixed Data to display through the template. + * + * @param mixed $mixed Data to display through the template. * @param string $template A template to display data in. - * + * * @return string The template output */ - function render($mixed = null, $template = null) + public function render($mixed = null, $template = null) { $method = 'render'.gettype($mixed); + return $this->$method($mixed, $template); } - + /** * Called when a resource is rendered - * + * * @param resource $resouce The resources * @param string $template Template - * + * * @return void - * + * * @throws UnexpectedValueException */ protected function renderResource($resouce, $template = null) { throw new Savvy_UnexpectedValueException('No way to render a resource!'); } - + protected function renderBoolean($bool, $template = null) { - return $this->renderString((string)$bool, $template); + return $this->renderString((string) $bool, $template); } - + protected function renderDouble($double, $template = null) { return $this->renderString($double, $template); } - + protected function renderInteger($int, $template = null) { return $this->renderString($int, $template); } - + /** * Render string of data - * + * * @param string $string String of data * @param string $template A template to display the string in - * + * * @return string */ protected function renderString($string, $template = null) @@ -720,7 +770,7 @@ class Savvy if ($this->__config['escape']) { $string = $this->escape($string); } - + if ($template) { return $this->fetch($string, $template); } @@ -728,15 +778,16 @@ class Savvy if (!$this->__config['filters']) { return $string; } + return $this->applyFilters($string); } - + /** * Used to render context array - * + * * @param array $array Data to render * @param string $template Template to render - * + * * @return string Rendered output */ protected function renderArray(array $array, $template = null) @@ -745,19 +796,20 @@ class Savvy foreach ($array as $mixed) { $output .= $this->render($mixed, $template); } + return $output; } /** * Render an associative array of data through a template. - * + * * Three parameters will be passed to the closure, the array key, value, * and selective third parameter. - * + * * @param array $array Associative array of data * @param mixed $selected Optional parameter to pass * @param Closure $template A closure that will be called - * + * * @return string */ public function renderAssocArray(array $array, $selected = false, Closure $template) @@ -766,27 +818,29 @@ class Savvy foreach ($array as $key => $element) { $ret .= $template($key, $element, $selected); } + return $ret; } - protected function renderArrayAccess(ArrayAccess $array, $template = null) + protected function renderTraversable(Traversable $array, $template = null) { $ret = ''; foreach ($array as $key => $element) { $ret .= $this->render($element, $template); } + return $ret; } /** * Render an if else conditional template output. - * + * * @param mixed $condition The conditional to evaluate * @param mixed $render Context data to render if condition is true * @param mixed $else Context data to render if condition is false * @param string $rendertemplate If true, render using this template * @param string $elsetemplate If false, render using this template - * + * * @return string */ public function renderElse($condition, $render, $else, $rendertemplate = null, $elsetemplate = null) @@ -797,13 +851,13 @@ class Savvy $this->render($else, $elsetemplate); } } - + /** * Used to render an object through a template. - * + * * @param object $object Model containing data * @param string $template Template to render data through - * + * * @return string Rendered output */ protected function renderObject($object, $template = null) @@ -814,19 +868,22 @@ class Savvy $object = Savvy_ObjectProxy::factory($object, $this); } - if ($object instanceof Savvy_ObjectProxy_ArrayIterator) { - return $this->renderArrayAccess($object); + if ($object instanceof Traversable + && $this->__config['iterate_traversable'] + ) { + return $this->renderTraversable($object->getRawObject(), $template); } } + return $this->fetch($object, $template); } - + /** * Used to render null through an optional template - * + * * @param null $null The null var * @param string $template Template to render null through - * + * * @return string Rendered output */ protected function renderNULL($null, $template = null) @@ -835,8 +892,8 @@ class Savvy return $this->fetch(null, $template); } } - - public function fetch($mixed, $template = null) + + protected function fetch($mixed, $template = null) { if ($template) { $this->template = $template; @@ -858,13 +915,14 @@ class Savvy $this->templateStack[] = $current; $ret = call_user_func(array($this, $this->selected_controller.'OutputController'), $current->context, $current->parent, $current->file, $this); array_pop($this->templateStack); + return $ret; } - + /** * * Compiles a template and returns path to compiled script. - * + * * By default, Savvy does not compile templates, it uses PHP as the * markup language, so the "compiled" template is the same as the source * template. @@ -873,19 +931,19 @@ class Savvy * template script name * * @param string $tpl The template source name to look for. - * + * * @return string The full path to the compiled template script. - * + * * @throws Savvy_UnexpectedValueException * @throws Savvy_Exception - * + * */ - + public function template($tpl = null) { // find the template source. $file = $this->findTemplateFile($tpl); - + // are we compiling source into a script? if ($this->__config['compiler']) { // compile the template source and get the path to the @@ -896,39 +954,39 @@ class Savvy // no compiling requested, use the source path $result = $file; } - + // is there a script from the compiler? if (!$result) { // return an error, along with any error info // generated by the compiler. throw new Savvy_TemplateException('Compiler error for template '.$tpl.'. '.$result ); - + } else { // no errors, the result is a path to a script return $result; } } - - + + // ----------------------------------------------------------------- // // Filter management and processing // // ----------------------------------------------------------------- - - + + /** - * + * * Resets the filter stack to the provided list of callbacks. - * + * * @access protected - * + * * @param array An array of filter callbacks. - * + * * @return void - * + * */ - + public function setFilters() { $this->__config['filters'] = (array) @func_get_args(); @@ -938,20 +996,20 @@ class Savvy $this->selected_controller = 'filter'; } } - - + + /** - * + * * Adds filter callbacks to the stack of filters. - * + * * @access protected - * + * * @param array An array of filter callbacks. - * + * * @return void - * + * */ - + public function addFilters() { // add the new filters to the static config variable @@ -961,26 +1019,27 @@ class Savvy $this->selected_controller = 'filter'; } } - - + + /** - * + * * Runs all filter callbacks on buffered output. - * + * * @access protected - * + * * @param string The template output. - * + * * @return void - * + * */ - + public function applyFilters($buffer) { foreach ($this->__config['filters'] as $callback) { $buffer = call_user_func($callback, $buffer); } + return $buffer; } - + }