Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

ENHANCEMENT: Allow arguments to be passed to templates via an array p…

…assed to SSViewer#process and via keyword=value pairs in the <% include %> tag
  • Loading branch information...
commit e4a043ac0b642bc624a54550a9e484154bdb7d29 1 parent 40ca21e
Hamish Friedlander hafriedlander authored
1  tests/templates/SSViewerTestIncludeWIthArguments.ss
View
@@ -0,0 +1 @@
+<p>$Arg1</p><p>$Arg2</p>
27 tests/view/SSViewerTest.php
View
@@ -443,6 +443,33 @@ function testBaseTagGeneration() {
$this->assertRegExp('/<head><base href=".*" \/><\/head>/', $response->getBody());
}
+ function testIncludeWithArguments() {
+ $this->assertEquals(
+ $this->render('<% include SSViewerTestIncludeWithArguments %>'),
+ '<p>[out:Arg1]</p><p>[out:Arg2]</p>'
+ );
+
+ $this->assertEquals(
+ $this->render('<% include SSViewerTestIncludeWithArguments Arg1=A %>'),
+ '<p>A</p><p>[out:Arg2]</p>'
+ );
+
+ $this->assertEquals(
+ $this->render('<% include SSViewerTestIncludeWithArguments Arg1=A, Arg2=B %>'),
+ '<p>A</p><p>B</p>'
+ );
+
+ $this->assertEquals(
+ $this->render('<% include SSViewerTestIncludeWithArguments Arg1=A Bare String, Arg2=B Bare String %>'),
+ '<p>A Bare String</p><p>B Bare String</p>'
+ );
+
+ $this->assertEquals(
+ $this->render('<% include SSViewerTestIncludeWithArguments Arg1="A", Arg2=$B %>', new ArrayData(array('B' => 'Bar'))),
+ '<p>A</p><p>Bar</p>'
+ );
+ }
+
function testRecursiveInclude() {
$view = new SSViewer(array('SSViewerTestRecursiveInclude'));
3,058 view/SSTemplateParser.php
View
1,637 additions, 1,421 deletions not shown
79 view/SSTemplateParser.php.inc
View
@@ -99,7 +99,7 @@ class SSTemplateParser extends Parser {
# Template is any structurally-complete portion of template (a full nested level in other words). It's the primary matcher,
# and is used by all enclosing blocks, as well as a base for the top level
- Template: (Comment | If | Require | CacheBlock | UncachedBlock | OldI18NTag | ClosedBlock | OpenBlock | MalformedBlock | Injection | Text)+
+ Template: (Comment | If | Require | CacheBlock | UncachedBlock | OldI18NTag | Include | ClosedBlock | OpenBlock | MalformedBlock | Injection | Text)+
*/
function Template_STR(&$res, $sub) {
$res['php'] .= $sub['php'] . PHP_EOL ;
@@ -264,7 +264,7 @@ class SSTemplateParser extends Parser {
function Argument_FreeString(&$res, $sub) {
$res['ArgumentMode'] = 'string';
- $res['php'] = "'" . str_replace("'", "\\'", $sub['text']) . "'";
+ $res['php'] = "'" . str_replace("'", "\\'", rtrim($sub['text'])) . "'";
}
/*!*
@@ -573,7 +573,55 @@ class SSTemplateParser extends Parser {
function OldI18NTag_STR(&$res, $sub) {
$res['php'] = '$val .= ' . $sub['php'] . ';';
}
-
+
+ /*!*
+
+ # An argument that can be passed through to an included template
+
+ NamedArgument: Name:Word "=" Value:Argument
+
+ */
+ function NamedArgument_Name(&$res, $sub) {
+ $res['php'] = "'" . $sub['text'] . "' => ";
+ }
+
+ function NamedArgument_Value(&$res, $sub) {
+ $res['php'] .= ($sub['ArgumentMode'] == 'default') ? $sub['string_php'] : str_replace('$$FINAL', 'XML_val', $sub['php']);
+ }
+
+ /*!*
+
+ # The include tag
+
+ Include: "<%" < "include" < Template:Word < (NamedArgument ( < "," < NamedArgument )*)? > "%>"
+
+ */
+ function Include__construct(&$res){
+ $res['arguments'] = array();
+ }
+
+ function Include_Template(&$res, $sub){
+ $res['template'] = "'" . $sub['text'] . "'";
+ }
+
+ function Include_NamedArgument(&$res, $sub){
+ $res['arguments'][] = $sub['php'];
+ }
+
+ function Include__finalise(&$res){
+ $template = $res['template'];
+ $arguments = $res['arguments'];
+
+ $res['php'] = '$val .= SSViewer::execute_template('.$template.', $scope->getItem(), array('.implode(',', $arguments)."));\n";
+
+ if($this->includeDebuggingComments) { // Add include filename comments on dev sites
+ $res['php'] =
+ '$val .= \'<!-- include '.addslashes($template).' -->\';'. "\n".
+ $res['php'].
+ '$val .= \'<!-- end include '.addslashes($template).' -->\';'. "\n";
+ }
+ }
+
/*!*
# To make the block support reasonably extendable, we don't explicitly define each closed block and it's structure,
@@ -586,7 +634,7 @@ class SSTemplateParser extends Parser {
# NotBlockTag matches against any word that might come after a "<%" that the generic open and closed block handlers
# shouldn't attempt to match against, because they're handled by more explicit matchers
- NotBlockTag: "end_" | (("if" | "else_if" | "else" | "require" | "cached" | "uncached" | "cacheblock") ] )
+ NotBlockTag: "end_" | (("if" | "else_if" | "else" | "require" | "cached" | "uncached" | "cacheblock" | "include") ] )
# Match against closed blocks - blocks with an opening and a closing tag that surround some internal portion of
# template
@@ -718,28 +766,7 @@ class SSTemplateParser extends Parser {
throw new SSTemplateParseException('Unknown open block "'.$blockname.'" encountered. Perhaps you missed the closing tag or have mis-spelled it?', $this);
}
}
-
- /**
- * This is an open block handler, for the <% include %> tag
- */
- function OpenBlock_Handle_Include(&$res) {
- if ($res['ArgumentCount'] != 1) throw new SSTemplateParseException('Include takes exactly one argument', $this);
-
- $arg = $res['Arguments'][0];
- $php = ($arg['ArgumentMode'] == 'default') ? $arg['string_php'] : $arg['php'];
-
- if($this->includeDebuggingComments) { // Add include filename comments on dev sites
- return
- '$val .= \'<!-- include '.addslashes($php).' -->\';'. "\n".
- '$val .= SSViewer::execute_template('.$php.', $scope->getItem());'. "\n".
- '$val .= \'<!-- end include '.addslashes($php).' -->\';'. "\n";
- }
- else {
- return
- '$val .= SSViewer::execute_template('.$php.', $scope->getItem());'. "\n";
- }
- }
-
+
/**
* This is an open block handler, for the <% debug %> utility tag
*/
130 view/SSViewer.php
View
@@ -300,7 +300,7 @@ class SSViewer_DataPresenter extends SSViewer_Scope {
protected $extras;
- function __construct($item, $extras = array()){
+ function __construct($item, $extras = null){
parent::__construct($item);
// Build up global property providers array only once per request
@@ -317,7 +317,7 @@ function __construct($item, $extras = array()){
$this->createCallableArray(self::$iteratorProperties, "TemplateIteratorProvider", "get_template_iterator_variables", true); //call non-statically
}
- $this->extras = $extras;
+ $this->extras = $extras ? $extras : array();
}
protected function createCallableArray(&$extraArray, $interfaceToQuery, $variableMethod, $createObject = false) {
@@ -504,7 +504,7 @@ static function get_source_file_comments() {
* @var string
*/
protected static $current_custom_theme = null;
-
+
/**
* Create a template from a string instead of a .ss file
*
@@ -697,7 +697,59 @@ static function flush_template_cache() {
self::$flushed = true;
}
}
-
+
+ /**
+ * @var Zend_Cache_Core
+ */
+ protected $partialCacheStore = null;
+
+ /**
+ * Set the cache object to use when storing / retrieving partial cache blocks.
+ * @param Zend_Cache_Core $cache
+ */
+ public function setPartialCacheStore($cache) {
+ $this->partialCacheStore = $cache;
+ }
+
+ /**
+ * Get the cache object to use when storing / retrieving partial cache blocks
+ * @return Zend_Cache_Core
+ */
+ public function getPartialCacheStore() {
+ return $this->partialCacheStore ? $this->partialCacheStore : SS_Cache::factory('cacheblock');
+ }
+
+ /**
+ * An internal utility function to set up variables in preparation for including a compiled
+ * template, then do the include
+ *
+ * Effectively this is the common code that both SSViewer#process and SSViewer_FromString#process call
+ *
+ * @param string $cacheFile - The path to the file that contains the template compiled to PHP
+ * @param Object $item - The item to use as the root scope for the template
+ * @param array|null $arguments - Any variables to layer into the root scope
+ * @return string - The result of executing the template
+ */
+ protected function includeGeneratedTemplate($cacheFile, $item, $arguments) {
+ if(isset($_GET['showtemplate']) && $_GET['showtemplate']) {
+ $lines = file($cacheFile);
+ echo "<h2>Template: $cacheFile</h2>";
+ echo "<pre>";
+ foreach($lines as $num => $line) {
+ echo str_pad($num+1,5) . htmlentities($line, ENT_COMPAT, 'UTF-8');
+ }
+ echo "</pre>";
+ }
+
+ $cache = $this->getPartialCacheStore();
+ $scope = new SSViewer_DataPresenter($item, $arguments ? $arguments : array());
+ $val = '';
+
+ include($cacheFile);
+
+ return $val;
+ }
+
/**
* The process() method handles the "meat" of the template processing.
* It takes care of caching the output (via {@link SS_Cache}),
@@ -711,11 +763,15 @@ static function flush_template_cache() {
* @param SS_Cache $cache Optional cache backend
* @return String Parsed template output.
*/
- public function process($item, $cache = null) {
+ public function process($item, $arguments = null) {
SSViewer::$topLevel[] = $item;
-
- if (!$cache) $cache = SS_Cache::factory('cacheblock');
-
+
+ if ($arguments && $arguments instanceof Zend_Cache_Core) {
+ Deprecation::notice('3.0', 'Use setPartialCacheStore to override the partial cache storage backend, the second argument to process is now an array of variables.');
+ $this->setPartialCacheStore($arguments);
+ $arguments = null;
+ }
+
if(isset($this->chosenTemplates['main'])) {
$template = $this->chosenTemplates['main'];
} else {
@@ -739,34 +795,21 @@ public function process($item, $cache = null) {
if(isset($_GET['debug_profile'])) Profiler::unmark("SSViewer::process - compile", " for $template");
}
-
-
- if(isset($_GET['showtemplate']) && !Director::isLive()) {
- $lines = file($cacheFile);
- echo "<h2>Template: $cacheFile</h2>";
- echo "<pre>";
- foreach($lines as $num => $line) {
- echo str_pad($num+1,5) . htmlentities($line, ENT_COMPAT, 'UTF-8');
- }
- echo "</pre>";
- }
-
+
+ $internalArguments = array('I18NNamespace' => basename($template));
+
// Makes the rendered sub-templates available on the parent item,
// through $Content and $Layout placeholders.
foreach(array('Content', 'Layout') as $subtemplate) {
if(isset($this->chosenTemplates[$subtemplate])) {
$subtemplateViewer = new SSViewer($this->chosenTemplates[$subtemplate]);
- $item = $item->customise(array(
- $subtemplate => $subtemplateViewer->process($item, $cache)
- ));
+ $subtemplateViewer->setPartialCacheStore($this->getPartialCacheStore());
+
+ $internalArguments[$subtemplate] = $subtemplateViewer->process($item);
}
}
- $scope = new SSViewer_DataPresenter($item, array('I18NNamespace' => basename($template)));
- $val = "";
-
- include($cacheFile);
-
+ $val = $this->includeGeneratedTemplate($cacheFile, $item, $arguments ? array_merge($internalArguments, $arguments) : $internalArguments);
$output = Requirements::includeInHTML($template, $val);
array_pop(SSViewer::$topLevel);
@@ -792,9 +835,9 @@ public function process($item, $cache = null) {
* Execute the given template, passing it the given data.
* Used by the <% include %> template tag to process templates.
*/
- static function execute_template($template, $data) {
+ static function execute_template($template, $data, $arguments = null) {
$v = new SSViewer($template);
- return $v->process($data);
+ return $v->process($data, $arguments);
}
static function parseTemplateContent($content, $template="") {
@@ -848,7 +891,13 @@ public function __construct($content) {
$this->content = $content;
}
- public function process($item, $cache = null) {
+ public function process($item, $arguments = null) {
+ if ($arguments && $arguments instanceof Zend_Cache_Core) {
+ Deprecation::notice('3.0', 'Use setPartialCacheStore to override the partial cache storage backend, the second argument to process is now an array of variables.');
+ $this->setPartialCacheStore($arguments);
+ $arguments = null;
+ }
+
$template = SSViewer::parseTemplateContent($this->content, "string sha1=".sha1($this->content));
$tmpFile = tempnam(TEMP_FOLDER,"");
@@ -856,26 +905,9 @@ public function process($item, $cache = null) {
fwrite($fh, $template);
fclose($fh);
- if(isset($_GET['showtemplate']) && $_GET['showtemplate']) {
- $lines = file($tmpFile);
- echo "<h2>Template: $tmpFile</h2>";
- echo "<pre>";
- foreach($lines as $num => $line) {
- echo str_pad($num+1,5) . htmlentities($line, ENT_COMPAT, 'UTF-8');
- }
- echo "</pre>";
- }
+ $val = $this->includeGeneratedTemplate($tmpFile, $item, $arguments);
- $scope = new SSViewer_DataPresenter($item);
- $val = "";
- $valStack = array();
-
- $cache = SS_Cache::factory('cacheblock');
-
- include($tmpFile);
unlink($tmpFile);
-
-
return $val;
}
}
4 view/ViewableData.php
View
@@ -328,12 +328,12 @@ public function renderWith($template, $customFields = null) {
$data = ($this->customisedObject) ? $this->customisedObject : $this;
- if(is_array($customFields) || $customFields instanceof ViewableData) {
+ if($customFields instanceof ViewableData) {
$data = $data->customise($customFields);
}
if($template instanceof SSViewer) {
- return $template->process($data);
+ return $template->process($data, is_array($customFields) ? $customFields : null);
}
throw new UnexpectedValueException (
Please sign in to comment.
Something went wrong with that request. Please try again.