Merge pull request #2139 from jthomerson/pulls/template_includes_with_scope

FEATURE: <% include %> inherits scope of parent template
This commit is contained in:
Hamish Friedlander 2013-07-07 14:01:40 -07:00
commit 10b55170ea
7 changed files with 82 additions and 17 deletions

View File

@ -0,0 +1,3 @@
<% loop Items %>
<% include SSViewerTestIncludeScopeInheritanceInclude %>
<% end_loop %>

View File

@ -0,0 +1 @@
$Title <% if ArgA %>- $ArgA <% end_if %>- <%if First %>First-<% end_if %><% if Last %>Last-<% end_if %><%if MultipleOf(2) %>EVEN<% else %>ODD<% end_if %> top:$Top.Title

View File

@ -0,0 +1,3 @@
<% loop Items %>
<% include SSViewerTestIncludeScopeInheritanceInclude ArgA=$Title %>
<% end_loop %>

View File

@ -29,7 +29,57 @@ class SSViewerTest extends SapphireTest {
$result = $data->renderWith("SSViewerTestPartialTemplate");
$this->assertEquals('Test partial template: var value', trim(preg_replace("/<!--.*-->/U",'',$result)));
}
public function testIncludeScopeInheritance() {
$data = $this->getScopeInheritanceTestData();
$expected = array(
'Item 1 - First-ODD top:Item 1',
'Item 2 - EVEN top:Item 2',
'Item 3 - ODD top:Item 3',
'Item 4 - EVEN top:Item 4',
'Item 5 - ODD top:Item 5',
'Item 6 - Last-EVEN top:Item 6',
);
$result = $data->renderWith('SSViewerTestIncludeScopeInheritance');
$this->assertExpectedStrings($result, $expected);
// reset results for the tests that include arguments (the title is passed as an arg)
$expected = array(
'Item 1 - Item 1 - First-ODD top:Item 1',
'Item 2 - Item 2 - EVEN top:Item 2',
'Item 3 - Item 3 - ODD top:Item 3',
'Item 4 - Item 4 - EVEN top:Item 4',
'Item 5 - Item 5 - ODD top:Item 5',
'Item 6 - Item 6 - Last-EVEN top:Item 6',
);
$result = $data->renderWith('SSViewerTestIncludeScopeInheritanceWithArgs');
$this->assertExpectedStrings($result, $expected);
}
private function getScopeInheritanceTestData() {
return new ArrayData(array(
'Title' => 'TopTitleValue',
'Items' => new ArrayList(array(
new ArrayData(array('Title' => 'Item 1')),
new ArrayData(array('Title' => 'Item 2')),
new ArrayData(array('Title' => 'Item 3')),
new ArrayData(array('Title' => 'Item 4')),
new ArrayData(array('Title' => 'Item 5')),
new ArrayData(array('Title' => 'Item 6'))
))
));
}
private function assertExpectedStrings($result, $expected) {
foreach ($expected as $expectedStr) {
$this->assertTrue(
(boolean) preg_match("/{$expectedStr}/", $result),
"Didn't find '{$expectedStr}' in:\n{$result}"
);
}
}
/**
* Small helper to render templates from strings

View File

@ -3242,7 +3242,7 @@ class SSTemplateParser extends Parser {
$arguments = $res['arguments'];
$res['php'] = '$val .= SSViewer::execute_template('.$template.', $scope->getItem(), array(' .
implode(',', $arguments)."));\n";
implode(',', $arguments)."), \$scope);\n";
if($this->includeDebuggingComments) { // Add include filename comments on dev sites
$res['php'] =

View File

@ -701,7 +701,7 @@ class SSTemplateParser extends Parser {
$arguments = $res['arguments'];
$res['php'] = '$val .= SSViewer::execute_template('.$template.', $scope->getItem(), array(' .
implode(',', $arguments)."));\n";
implode(',', $arguments)."), \$scope);\n";
if($this->includeDebuggingComments) { // Add include filename comments on dev sites
$res['php'] =

View File

@ -47,11 +47,17 @@ class SSViewer_Scope {
private $localIndex;
public function __construct($item){
public function __construct($item, $inheritedScope = null) {
$this->item = $item;
$this->localIndex = 0;
$this->localStack = array();
$this->itemStack[] = array($this->item, null, 0, null, null, 0);
if ($inheritedScope instanceof SSViewer_Scope) {
$this->itemIterator = $inheritedScope->itemIterator;
$this->itemIteratorTotal = $inheritedScope->itemIteratorTotal;
$this->itemStack[] = array($this->item, $this->itemIterator, $this->itemIteratorTotal, null, null, 0);
} else {
$this->itemStack[] = array($this->item, null, 0, null, null, 0);
}
}
public function getItem(){
@ -357,8 +363,8 @@ class SSViewer_DataPresenter extends SSViewer_Scope {
*/
protected $underlay;
public function __construct($item, $overlay = null, $underlay = null){
parent::__construct($item);
public function __construct($item, $overlay = null, $underlay = null, $inheritedScope = null) {
parent::__construct($item, $inheritedScope);
// Build up global property providers array only once per request
if (self::$globalProperties === null) {
@ -553,7 +559,7 @@ class SSViewer {
* @var boolean $source_file_comments
*/
private static $source_file_comments = false;
/**
* Set whether HTML comments indicating the source .SS file used to render this page should be
* included in the output. This is enabled by default
@ -895,10 +901,11 @@ class SSViewer {
* @param Object $item - The item to use as the root scope for the template
* @param array|null $overlay - Any variables to layer on top of the scope
* @param array|null $underlay - Any variables to layer underneath the scope
* @param Object $inheritedScope - the current scope of a parent template including a sub-template
*
* @return string - The result of executing the template
*/
protected function includeGeneratedTemplate($cacheFile, $item, $overlay, $underlay) {
protected function includeGeneratedTemplate($cacheFile, $item, $overlay, $underlay, $inheritedScope = null) {
if(isset($_GET['showtemplate']) && $_GET['showtemplate'] && Permission::check('ADMIN')) {
$lines = file($cacheFile);
echo "<h2>Template: $cacheFile</h2>";
@ -910,7 +917,7 @@ class SSViewer {
}
$cache = $this->getPartialCacheStore();
$scope = new SSViewer_DataPresenter($item, $overlay, $underlay);
$scope = new SSViewer_DataPresenter($item, $overlay, $underlay, $inheritedScope);
$val = '';
include($cacheFile);
@ -930,11 +937,12 @@ class SSViewer {
* Note: You can call this method indirectly by {@link ViewableData->renderWith()}.
*
* @param ViewableData $item
* @param SS_Cache $cache Optional cache backend.
* @param array|null $arguments - arguments to an included template
* @param Object $inheritedScope - the current scope of a parent template including a sub-template
*
* @return HTMLText Parsed template output.
*/
public function process($item, $arguments = null) {
public function process($item, $arguments = null, $inheritedScope = null) {
SSViewer::$topLevel[] = $item;
if ($arguments && $arguments instanceof Zend_Cache_Core) {
@ -979,7 +987,7 @@ class SSViewer {
}
}
$output = $this->includeGeneratedTemplate($cacheFile, $item, $arguments, $underlay);
$output = $this->includeGeneratedTemplate($cacheFile, $item, $arguments, $underlay, $inheritedScope);
if($this->includeRequirements) {
$output = Requirements::includeInHTML($template, $output);
@ -1009,11 +1017,11 @@ class SSViewer {
* Execute the given template, passing it the given data.
* Used by the <% include %> template tag to process templates.
*/
public static function execute_template($template, $data, $arguments = null) {
public static function execute_template($template, $data, $arguments = null, $scope = null) {
$v = new SSViewer($template);
$v->includeRequirements(false);
return $v->process($data, $arguments);
return $v->process($data, $arguments, $scope);
}
public static function parseTemplateContent($content, $template="") {
@ -1071,7 +1079,7 @@ class SSViewer_FromString extends SSViewer {
$this->content = $content;
}
public function process($item, $arguments = null) {
public function process($item, $arguments = null, $scope = 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.');
@ -1086,7 +1094,7 @@ class SSViewer_FromString extends SSViewer {
fwrite($fh, $template);
fclose($fh);
$val = $this->includeGeneratedTemplate($tmpFile, $item, $arguments, null);
$val = $this->includeGeneratedTemplate($tmpFile, $item, $arguments, null, $scope);
unlink($tmpFile);
return $val;