From 30811f32864bfb278cd586d24b082f681491136a Mon Sep 17 00:00:00 2001 From: josephspurrier Date: Sat, 3 May 2014 16:28:12 -0400 Subject: [PATCH] Updated folders and plugin support --- .../SurfStack/Templating/Core/Block.php | 20 +- src/SurfStack/Templating/Core/PluginBase.php | 48 ++++ .../Templating/{Plugin => Core}/Slice.php | 15 +- src/SurfStack/Templating/Plugin/Block.php | 34 --- src/SurfStack/Templating/Plugin/Bold.php | 11 - src/SurfStack/Templating/Plugin/Time.php | 11 - src/SurfStack/Templating/Template_Engine.php | 208 +++++++++++++----- src/SurfStack/Templating/plugin/placeholder | 0 .../Templating/template_cache/placeholder | 0 .../Templating/template_compile/placeholder | 0 tests/SurfStack/Templating/Plugin/Block.php | 34 --- tests/SurfStack/Templating/Plugin/Bold.php | 5 +- tests/SurfStack/Templating/Plugin/Time.php | 3 +- .../Templating/Template_Engine_Test.php | 89 +++++++- tests/SurfStack/Templating/plugin/Blank.php | 12 + .../SurfStack/Templating/plugin/Passthru.php | 12 + tests/SurfStack/Templating/template/block.tpl | 2 +- .../Templating/template/blockVariable.tpl | 1 + tests/SurfStack/Templating/template/child.tpl | 1 + .../Templating/template/grandparent.tpl | 9 + .../SurfStack/Templating/template/parent.tpl | 5 + .../Templating/template/sliceVariable.tpl | 1 + 22 files changed, 338 insertions(+), 183 deletions(-) rename tests/SurfStack/Templating/Plugin/Slice.php => src/SurfStack/Templating/Core/Block.php (53%) create mode 100644 src/SurfStack/Templating/Core/PluginBase.php rename src/SurfStack/Templating/{Plugin => Core}/Slice.php (65%) delete mode 100644 src/SurfStack/Templating/Plugin/Block.php delete mode 100644 src/SurfStack/Templating/Plugin/Bold.php delete mode 100644 src/SurfStack/Templating/Plugin/Time.php create mode 100644 src/SurfStack/Templating/plugin/placeholder create mode 100644 src/SurfStack/Templating/template_cache/placeholder create mode 100644 src/SurfStack/Templating/template_compile/placeholder delete mode 100644 tests/SurfStack/Templating/Plugin/Block.php create mode 100644 tests/SurfStack/Templating/plugin/Blank.php create mode 100644 tests/SurfStack/Templating/plugin/Passthru.php create mode 100644 tests/SurfStack/Templating/template/blockVariable.tpl create mode 100644 tests/SurfStack/Templating/template/child.tpl create mode 100644 tests/SurfStack/Templating/template/grandparent.tpl create mode 100644 tests/SurfStack/Templating/template/parent.tpl create mode 100644 tests/SurfStack/Templating/template/sliceVariable.tpl diff --git a/tests/SurfStack/Templating/Plugin/Slice.php b/src/SurfStack/Templating/Core/Block.php similarity index 53% rename from tests/SurfStack/Templating/Plugin/Slice.php rename to src/SurfStack/Templating/Core/Block.php index 71eb57c..6e7d2d8 100644 --- a/tests/SurfStack/Templating/Plugin/Slice.php +++ b/src/SurfStack/Templating/Core/Block.php @@ -9,25 +9,19 @@ * @license http://www.apache.org/licenses/LICENSE-2.0.html */ -namespace SurfStack\Templating\Plugin; +namespace SurfStack\Templating\Core; /** - * SurfStack Template Engine Slice + * SurfStack Template Engine Block * * Renders content in a template. - * Designated by an open {name} and close {/name} tag. + * Designated by a single {name} tag. */ -abstract class Slice -{ - /** - * Array of settings from Template Engine - * @var array - */ - public $internal = array(); - +abstract class Block extends PluginBase +{ /** * Called by the template, expects a return value - * @param array $arrData + * @param string $strContent Content written between the tags */ - abstract function render($arrData); + abstract function render($strContent); } \ No newline at end of file diff --git a/src/SurfStack/Templating/Core/PluginBase.php b/src/SurfStack/Templating/Core/PluginBase.php new file mode 100644 index 0000000..790edae --- /dev/null +++ b/src/SurfStack/Templating/Core/PluginBase.php @@ -0,0 +1,48 @@ +$key = $value; + } +} \ No newline at end of file diff --git a/src/SurfStack/Templating/Plugin/Slice.php b/src/SurfStack/Templating/Core/Slice.php similarity index 65% rename from src/SurfStack/Templating/Plugin/Slice.php rename to src/SurfStack/Templating/Core/Slice.php index 71eb57c..6ccd8ce 100644 --- a/src/SurfStack/Templating/Plugin/Slice.php +++ b/src/SurfStack/Templating/Core/Slice.php @@ -9,7 +9,7 @@ * @license http://www.apache.org/licenses/LICENSE-2.0.html */ -namespace SurfStack\Templating\Plugin; +namespace SurfStack\Templating\Core; /** * SurfStack Template Engine Slice @@ -17,17 +17,10 @@ * Renders content in a template. * Designated by an open {name} and close {/name} tag. */ -abstract class Slice -{ - /** - * Array of settings from Template Engine - * @var array - */ - public $internal = array(); - +abstract class Slice extends PluginBase +{ /** * Called by the template, expects a return value - * @param array $arrData */ - abstract function render($arrData); + abstract function render(); } \ No newline at end of file diff --git a/src/SurfStack/Templating/Plugin/Block.php b/src/SurfStack/Templating/Plugin/Block.php deleted file mode 100644 index dd36976..0000000 --- a/src/SurfStack/Templating/Plugin/Block.php +++ /dev/null @@ -1,34 +0,0 @@ -'.$strContent.' '.$arrData['name']; - } -} \ No newline at end of file diff --git a/src/SurfStack/Templating/Plugin/Time.php b/src/SurfStack/Templating/Plugin/Time.php deleted file mode 100644 index 98a826b..0000000 --- a/src/SurfStack/Templating/Plugin/Time.php +++ /dev/null @@ -1,11 +0,0 @@ -internal = array( @@ -54,6 +54,7 @@ function __construct($template) 'AlwaysCheckOriginal' => false, ); + $this->setTemplateDir($path); $this->setTemplate($template); } @@ -71,15 +72,6 @@ function setTemplate($template) // Store the template full path $this->template = stream_resolve_include_path($template); - - // Store the template directory - $this->setInternal('TemplateDir', dirname($this->template)); - - if (strstr(get_include_path(), dirname($this->template)) === false) - { - // Set the include path to include the template directory - set_include_path(get_include_path().PATH_SEPARATOR.dirname($this->template)); - } } /** @@ -144,6 +136,30 @@ function setPluginDir($path) $this->setInternal('PluginDir', rtrim(stream_resolve_include_path($path), '/')); } + /** + * Set the path of the template dir + * @param string $path + * @throws \ErrorException + */ + function setTemplateDir($path) + { + if (!is_dir(stream_resolve_include_path($path))) + { + throw new \ErrorException('The path, '.$path.', cannot be found.'); + } + + $realpath = rtrim(stream_resolve_include_path($path), '/'); + + // Store the full path + $this->setInternal('TemplateDir', $realpath); + + if (strstr(get_include_path(), $realpath) === false) + { + // Set the include path to include the template directory + set_include_path(get_include_path().PATH_SEPARATOR.$realpath); + } + } + /** * Enable or disable stripping PHP tags, PHP short tags, PHP echo short tags, and ASP tags * @param bool $bool @@ -535,7 +551,7 @@ protected function embedIncludeRequire(array $matches) if (is_file(stream_resolve_include_path($match))) { // Generate a new instance of this class - $class = new self(stream_resolve_include_path($match)); + $class = new self($this->getInternal('TemplateDir'), stream_resolve_include_path($match)); // Copy over the settings $class->setInternals($this->getInternals()); @@ -632,7 +648,9 @@ protected function buildRenderableArray(array $arr) } else { - $arrOut .= "'$key'=>'$val',"; + $escapedVal = str_replace("'", "\'", $val); + + $arrOut .= "'$key'=>'$escapedVal',"; } } @@ -642,65 +660,85 @@ protected function buildRenderableArray(array $arr) } /** - * Return the render() function so the compiled template can - * call the plugin - * @param array $matches + * Parse a string of name='value' and convert to an array + * @param string $strData + * @return array */ - protected function callPluginDynamic(array $matches) + protected function parsePluginVariables($strData) { - $pluginName = $matches[1]; - $pluginData = $matches[2]; - $arr = array(); - if(preg_match_all('/(\w+=\'[^\']*\'|\w+=\"[^"]*"|\w+=[^\s]*)+/', $pluginData, $m)) + // Extract the variables + if(preg_match_all('/(\w+=\'[^\']*\'|\w+=\"[^"]*"|\w+=[^\s]*)+/', $strData, $m)) { foreach($m[0] as $key => $k) { $arrSplit = explode('=', $k); - + $arr[$arrSplit[0]] = trim(join('', array_slice($arrSplit, 1)), '\'\"'); } } - // Assign variables to the variables - foreach($arr as &$val) + return $arr; + } + + /** + * Escape single quotes + * @param string $string + * @return string + */ + protected function safeString($string) + { + return str_replace("'", '/', $string); + } + + /** + * Get the class and parent classes file paths + * @param string $class + */ + protected function getRequiredClasses($class) + { + $arrRequire = ''; + + do { - if (isset($this->variables[$val])) - { - $val = '$'."$val"; - } - } + $rc = new \ReflectionClass($class); + $arrRequire[] = "require_once '{$this->safeString($rc->getFileName())}';"; + $class = get_parent_class($class); - $arrOut = $this->buildRenderableArray($arr); + } while ($class); + //return ''; + return join(PHP_EOL, array_reverse($arrRequire)); + } + + /** + * Return the render() function so the compiled template can + * call the plugin + * @param array $matches + */ + protected function callPluginDynamic(array $matches) + { + $pluginName = $matches[1]; + $pluginData = $matches[2]; + // Block has content, Slice does not + $pluginContent = (isset($matches[3]) ? "'".addslashes($matches[3])."'" : ''); + + // Get the variables as a renderable array + $sPassed = $this->buildRenderableArray($this->parsePluginVariables($pluginData)); - $arrInternal = $this->buildRenderableArray($this->internal); + // Get the requires classes as strings + $require = $this->getRequiredClasses('\SurfStack\Templating\Plugin\\'.$pluginName); - // Block - if (isset($matches[3])) - { - $pluginContent = $matches[3]; - - return <<< OUTPUT + return <<< OUTPUT internal = $arrInternal; -echo \$class->render('$pluginContent', $arrOut); +$require +\$class = new \SurfStack\Templating\Plugin\\$pluginName(); +\$class->store('arrEngineVariables', \$this->variables); +\$class->store('arrEngineInternals', \$this->internal); +\$class->store('arrPluginVariables', $sPassed); +echo \$class->render($pluginContent); ?> OUTPUT; - } - else - { - return <<< OUTPUT -internal = $arrInternal; -echo \$class->render($arrOut); -?> -OUTPUT; - } } /** @@ -764,12 +802,70 @@ protected function modifyTemplateRegex($content) return preg_replace_callback(array_values($arrPlugins), array($this, 'callPluginDynamic'), $content); //return preg_replace_callback(array_values($arrPlugins), array($this, 'callPluginStatic'), $content); } - + + /** + * Determines if the template has any non-fatal problems + * @return boolean + */ + function isTemplateValid() + { + $this->error = true; + + set_error_handler(function ($errno, $errstr, $errfile, $errline) { + $this->error = false; + return true; + }); + + // If the compile is not current + if (!$this->isCompileCurrent()) + { + // Update the compile + $this->updateCompile(); + } + + // Extract the variables + extract($this->variables); + + ob_start(); + @require $this->getCompiledTemplate(); + ob_end_clean(); + + restore_error_handler(); + + return $this->error; + } + + /** + * Returns an array of error information from error_get_last() for the template + * or an empty array if successful + * @return array + */ + function getCompileTemplateError() + { + // If the compile is not current + if (!$this->isCompileCurrent()) + { + // Update the compile + $this->updateCompile(); + } + + // Extract the variables + extract($this->variables); + + ob_start(); + @require $this->getCompiledTemplate(); + ob_end_clean(); + + $error = error_get_last(); + + return (is_null($error) ? array() : $error); + } + /** * Render the template (compile and caching logic) */ function render() - { + { // Marked them as null for testing purposes $this->setInternal('WasCached', null); $this->setInternal('WasCompiled', null); @@ -823,7 +919,7 @@ function render() } // Else caching is not enabled else - { + { // If the compile is current if ($this->isCompileCurrent()) { @@ -841,7 +937,7 @@ function render() // Extract the variables extract($this->variables); - + // Render the compile require $this->getCompiledTemplate(); } diff --git a/src/SurfStack/Templating/plugin/placeholder b/src/SurfStack/Templating/plugin/placeholder new file mode 100644 index 0000000..e69de29 diff --git a/src/SurfStack/Templating/template_cache/placeholder b/src/SurfStack/Templating/template_cache/placeholder new file mode 100644 index 0000000..e69de29 diff --git a/src/SurfStack/Templating/template_compile/placeholder b/src/SurfStack/Templating/template_compile/placeholder new file mode 100644 index 0000000..e69de29 diff --git a/tests/SurfStack/Templating/Plugin/Block.php b/tests/SurfStack/Templating/Plugin/Block.php deleted file mode 100644 index 49dc034..0000000 --- a/tests/SurfStack/Templating/Plugin/Block.php +++ /dev/null @@ -1,34 +0,0 @@ -'.$strContent.' '.$arrData['name']; + return ''.$strContent.' '.$this->arrPluginVariables['name']; } } \ No newline at end of file diff --git a/tests/SurfStack/Templating/Plugin/Time.php b/tests/SurfStack/Templating/Plugin/Time.php index 98a826b..7e470a7 100644 --- a/tests/SurfStack/Templating/Plugin/Time.php +++ b/tests/SurfStack/Templating/Plugin/Time.php @@ -1,10 +1,11 @@ view = new SurfStack\Templating\Template_Engine(__DIR__.'/template/template.tpl'); + $this->view = new SurfStack\Templating\Template_Engine(__DIR__.'/template/', 'template.tpl'); $this->view->setCompileDir(__DIR__.'/template_compile'); $this->view->setCacheDir(__DIR__.'/template_cache'); - $this->view->setPluginDir(__DIR__.'/Plugin'); + $this->view->setPluginDir(__DIR__.'/plugin'); $this->view->clearCompile(); $this->view->clearCache(); - $this->view->setLoadPlugins(true); + //$this->view->setLoadPlugins(true); $items = array( - 'item1' => 'i1z', - 'item2' => 'i2', + 'item1' => 'hello', + 'item2' => 'world', ); $this->view->assign('items', $items); + + $deep = array( + 'item1' => 'hello', + 'item2' => array( + 'obj' => new stdClass(), + ), + ); + + $this->view->assign('deep', $deep); + + $obj = new stdClass(); + $obj->item1 = 'hello'; + $obj->item2 = 'world'; + + $this->view->assign('obj', $obj); } protected function tearDown() @@ -58,8 +73,6 @@ private function render() public function testCompiling() { - $this->view->clearCompile(); - $this->render(); $this->assertFalse($this->view->wasCompileCurrent()); @@ -102,8 +115,16 @@ public function testUnassign() * @expectedException ErrorException */ public function testTemplateNotExist() + { + $this->view->setTemplate('templateNotExist.tpl'); + } + + /** + * @expectedException ErrorException + */ + public function testTemplateDirMissing() { - $this->view = new SurfStack\Templating\Template_Engine(__DIR__.'/template/templateNotExist.tpl'); + $this->view->setTemplateDir('nowhere'); } /** @@ -249,7 +270,7 @@ public function testConversion() public function testNoLoadPlugins() { - $this->expectOutputString("{Bold name='world'}Hello{/Bold}!"); + $this->expectOutputString("{Bold name='world' class=\$obj array=\$items}Hello{/Bold}!"); $this->view->setTemplate(__DIR__.'/template/block.tpl'); @@ -277,6 +298,25 @@ public function testBlock() $this->view->render(); } + public function testVariableBlock() + { + $this->expectOutputString('Hello world.'); + + $this->view->clear(); + + $this->view->assign('test', 'Hello world'); + + $this->view->setTemplate(__DIR__.'/template/blockVariable.tpl'); + + $this->view->setLoadPlugins(true); + + $this->view->setStripTags(false); + + $this->view->setStripWhitespace(false); + + $this->view->render(); + } + public function testSlice() { $this->expectOutputString(date('Y')); @@ -292,6 +332,37 @@ public function testSlice() $this->view->render(); } + public function testVariableSliceBad() + { + $this->view->setLoadPlugins(true); + + $this->view->setTemplate('sliceVariable.tpl'); + + $this->assertFalse($this->view->isTemplateValid()); + } + + public function testVariableSliceBadMessage() + { + $this->view->setLoadPlugins(true); + + $this->view->setTemplate('sliceVariable.tpl'); + + $arr = $this->view->getCompileTemplateError(); + + $this->assertSame($arr['message'], 'Undefined variable: missing'); + } + + public function testVariableSliceGood() + { + $this->view->setLoadPlugins(true); + + $this->view->assign('missing', 'notmissing'); + + $this->view->setTemplate('sliceVariable.tpl'); + + $this->assertTrue($this->view->isTemplateValid()); + } + public function testNoCacheTemplates() { $this->view->setCacheTemplates(false); diff --git a/tests/SurfStack/Templating/plugin/Blank.php b/tests/SurfStack/Templating/plugin/Blank.php new file mode 100644 index 0000000..bb6644d --- /dev/null +++ b/tests/SurfStack/Templating/plugin/Blank.php @@ -0,0 +1,12 @@ +arrPluginVariables['pass']; + } +} \ No newline at end of file diff --git a/tests/SurfStack/Templating/template/block.tpl b/tests/SurfStack/Templating/template/block.tpl index d726b48..fb0d083 100644 --- a/tests/SurfStack/Templating/template/block.tpl +++ b/tests/SurfStack/Templating/template/block.tpl @@ -1 +1 @@ -{Bold name='world'}Hello{/Bold}! \ No newline at end of file +{Bold name='world' class=$obj array=$items}Hello{/Bold}! \ No newline at end of file diff --git a/tests/SurfStack/Templating/template/blockVariable.tpl b/tests/SurfStack/Templating/template/blockVariable.tpl new file mode 100644 index 0000000..da9a785 --- /dev/null +++ b/tests/SurfStack/Templating/template/blockVariable.tpl @@ -0,0 +1 @@ +{Passthru pass=$test}Variable: {/Passthru}. \ No newline at end of file diff --git a/tests/SurfStack/Templating/template/child.tpl b/tests/SurfStack/Templating/template/child.tpl new file mode 100644 index 0000000..079b80b --- /dev/null +++ b/tests/SurfStack/Templating/template/child.tpl @@ -0,0 +1 @@ +{extend file='parent.tpl'} \ No newline at end of file diff --git a/tests/SurfStack/Templating/template/grandparent.tpl b/tests/SurfStack/Templating/template/grandparent.tpl new file mode 100644 index 0000000..7186b78 --- /dev/null +++ b/tests/SurfStack/Templating/template/grandparent.tpl @@ -0,0 +1,9 @@ +

Sweet

+ +{section name='test'} +Replace from grandparent +{/section} + +{section name='safe'} +leave from grandparent +{/section} \ No newline at end of file diff --git a/tests/SurfStack/Templating/template/parent.tpl b/tests/SurfStack/Templating/template/parent.tpl new file mode 100644 index 0000000..01472f9 --- /dev/null +++ b/tests/SurfStack/Templating/template/parent.tpl @@ -0,0 +1,5 @@ +{extend file='grandparent.tpl'} + +{section name='test'} +Test from parent +{/section} \ No newline at end of file diff --git a/tests/SurfStack/Templating/template/sliceVariable.tpl b/tests/SurfStack/Templating/template/sliceVariable.tpl new file mode 100644 index 0000000..17d5fdd --- /dev/null +++ b/tests/SurfStack/Templating/template/sliceVariable.tpl @@ -0,0 +1 @@ +{Blank var=$missing} \ No newline at end of file