diff --git a/tests/view/SSViewerTest.php b/tests/view/SSViewerTest.php index 15003e9d9..5e7589165 100644 --- a/tests/view/SSViewerTest.php +++ b/tests/view/SSViewerTest.php @@ -1074,7 +1074,7 @@ after') $origEnv = Config::inst()->get('Director', 'environment_type'); Config::inst()->update('Director', 'environment_type', 'dev'); Config::inst()->update('SSViewer', 'source_file_comments', true); - $f = FRAMEWORK_PATH . '/tests/templates/SSViewerTestComments'; + $f = FRAMEWORK_PATH . '/tests/templates/SSViewerTestComments'; $templates = array( array( 'name' => 'SSViewerTestCommentsFullSource', @@ -1090,7 +1090,8 @@ after') array( 'name' => 'SSViewerTestCommentsFullSourceHTML4Doctype', 'expected' => "" - . "" + . "" . "" . "" . "\t" @@ -1209,6 +1210,45 @@ after') "tests/forms/RequirementsTest_a.js" )); } + + public function testCallsWithArguments() { + $data = new ArrayData(array( + 'Set' => new ArrayList(array( + new SSViewerTest_Object("1"), + new SSViewerTest_Object("2"), + new SSViewerTest_Object("3"), + new SSViewerTest_Object("4"), + new SSViewerTest_Object("5"), + )), + 'Level' => new SSViewerTest_LevelTest(1), + 'Nest' => array( + 'Level' => new SSViewerTest_LevelTest(2), + ), + )); + + $tests = array( + '$Level.output(1)' => '1-1', + '$Nest.Level.output($Set.First.Number)' => '2-1', + '<% with $Set %>$Up.Level.output($First.Number)<% end_with %>' => '1-1', + '<% with $Set %>$Top.Nest.Level.output($First.Number)<% end_with %>' => '2-1', + '<% loop $Set %>$Up.Nest.Level.output($Number)<% end_loop %>' => '2-12-22-32-42-5', + '<% loop $Set %>$Top.Level.output($Number)<% end_loop %>' => '1-11-21-31-41-5', + '<% with $Nest %>$Level.output($Top.Set.First.Number)<% end_with %>' => '2-1', + '<% with $Level %>$output($Up.Set.Last.Number)<% end_with %>' => '1-5', + '<% with $Level.forWith($Set.Last.Number) %>$output("hi")<% end_with %>' => '5-hi', + '<% loop $Level.forLoop($Set.First.Number) %>$Number<% end_loop %>' => '!0', + '<% with $Nest %> + <% with $Level.forWith($Up.Set.First.Number) %>$output("hi")<% end_with %> + <% end_with %>' => '1-hi', + '<% with $Nest %> + <% loop $Level.forLoop($Top.Set.Last.Number) %>$Number<% end_loop %> + <% end_with %>' => '!0!1!2!3!4', + ); + + foreach($tests as $template => $expected) { + $this->assertEquals($expected, trim($this->render($template, $data))); + } + } } /** @@ -1347,3 +1387,28 @@ class SSViewerTest_GlobalProvider implements TemplateGlobalProvider, TestOnly { } } + +class SSViewerTest_LevelTest extends ViewableData implements TestOnly { + protected $depth; + + public function __construct($depth = 1) { + $this->depth = $depth; + } + + public function output($val) { + return "$this->depth-$val"; + } + + public function forLoop($number) { + $ret = array(); + for($i = 0; $i < (int)$number; ++$i) { + $ret[] = new SSViewerTest_Object("!$i"); + } + return new ArrayList($ret); + } + + public function forWith($number) { + return new self($number); + } +} + diff --git a/view/SSTemplateParser.php b/view/SSTemplateParser.php index c3e3532b6..efe7e27ea 100644 --- a/view/SSTemplateParser.php +++ b/view/SSTemplateParser.php @@ -615,7 +615,7 @@ class SSTemplateParser extends Parser { function Lookup__construct(&$res) { - $res['php'] = '$scope'; + $res['php'] = '$scope->locally()'; $res['LookupSteps'] = array(); } diff --git a/view/SSTemplateParser.php.inc b/view/SSTemplateParser.php.inc index 68249b3ed..3c1349efe 100644 --- a/view/SSTemplateParser.php.inc +++ b/view/SSTemplateParser.php.inc @@ -157,7 +157,7 @@ class SSTemplateParser extends Parser { */ function Lookup__construct(&$res) { - $res['php'] = '$scope'; + $res['php'] = '$scope->locally()'; $res['LookupSteps'] = array(); } diff --git a/view/SSViewer.php b/view/SSViewer.php index 220d59d87..40df8f510 100644 --- a/view/SSViewer.php +++ b/view/SSViewer.php @@ -49,18 +49,34 @@ class SSViewer_Scope { public function __construct($item){ $this->item = $item; - $this->localIndex=0; + $this->localIndex = 0; + $this->localStack = array(); $this->itemStack[] = array($this->item, null, 0, null, null, 0); } public function getItem(){ return $this->itemIterator ? $this->itemIterator->current() : $this->item; } - - public function resetLocalScope(){ + + /** Called at the start of every lookup chain by SSTemplateParser to indicate a new lookup from local scope */ + public function locally() { list($this->item, $this->itemIterator, $this->itemIteratorTotal, $this->popIndex, $this->upIndex, $this->currentIndex) = $this->itemStack[$this->localIndex]; - array_splice($this->itemStack, $this->localIndex+1); + + // Remember any un-completed (resetLocalScope hasn't been called) lookup chain. Even if there isn't an + // un-completed chain we need to store an empty item, as resetLocalScope doesn't know the difference later + $this->localStack[] = array_splice($this->itemStack, $this->localIndex+1); + + return $this; + } + + public function resetLocalScope(){ + $previousLocalState = $this->localStack ? array_pop($this->localStack) : null; + + array_splice($this->itemStack, $this->localIndex+1, count($this->itemStack), $previousLocalState); + + list($this->item, $this->itemIterator, $this->itemIteratorTotal, $this->popIndex, $this->upIndex, + $this->currentIndex) = end($this->itemStack); } public function getObj($name, $arguments = null, $forceReturnedObject = true, $cache = false, $cacheName = null) {