From 24660afabd7ddcaf87aa859df396862cecea2ef8 Mon Sep 17 00:00:00 2001 From: Loz Calver Date: Tue, 22 Dec 2015 14:48:04 +0000 Subject: [PATCH] FIX: Parameters passed to includes overwrite all scopes (fixes #2617) --- ...stIncludeScopeInheritanceWithArgsInLoop.ss | 1 + ...udeScopeInheritanceWithArgsInNestedWith.ss | 1 + ...stIncludeScopeInheritanceWithArgsInWith.ss | 1 + tests/view/SSViewerTest.php | 25 ++++++ view/SSViewer.php | 89 ++++++++++++++++++- 5 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 tests/templates/SSViewerTestIncludeScopeInheritanceWithArgsInLoop.ss create mode 100644 tests/templates/SSViewerTestIncludeScopeInheritanceWithArgsInNestedWith.ss create mode 100644 tests/templates/SSViewerTestIncludeScopeInheritanceWithArgsInWith.ss diff --git a/tests/templates/SSViewerTestIncludeScopeInheritanceWithArgsInLoop.ss b/tests/templates/SSViewerTestIncludeScopeInheritanceWithArgsInLoop.ss new file mode 100644 index 000000000..44479e5ea --- /dev/null +++ b/tests/templates/SSViewerTestIncludeScopeInheritanceWithArgsInLoop.ss @@ -0,0 +1 @@ +$Title - <% loop $Items %>$Title<% if not Last %> - <% else %> - {$Top.Title}<% end_if %><% end_loop %> \ No newline at end of file diff --git a/tests/templates/SSViewerTestIncludeScopeInheritanceWithArgsInNestedWith.ss b/tests/templates/SSViewerTestIncludeScopeInheritanceWithArgsInNestedWith.ss new file mode 100644 index 000000000..e9ca46b50 --- /dev/null +++ b/tests/templates/SSViewerTestIncludeScopeInheritanceWithArgsInNestedWith.ss @@ -0,0 +1 @@ +$Top.Title - <% with $Item %>$Title - <% with $NestedItem %>{$Title} - {$Up.Title} - {$Top.Title}<% end_with %><% end_with %> \ No newline at end of file diff --git a/tests/templates/SSViewerTestIncludeScopeInheritanceWithArgsInWith.ss b/tests/templates/SSViewerTestIncludeScopeInheritanceWithArgsInWith.ss new file mode 100644 index 000000000..f4e5f4ac8 --- /dev/null +++ b/tests/templates/SSViewerTestIncludeScopeInheritanceWithArgsInWith.ss @@ -0,0 +1 @@ +$Title - <% with $Item %>$Title - {$Up.Title}<% end_with %> \ No newline at end of file diff --git a/tests/view/SSViewerTest.php b/tests/view/SSViewerTest.php index bb4cd7c44..0421267ef 100644 --- a/tests/view/SSViewerTest.php +++ b/tests/view/SSViewerTest.php @@ -701,6 +701,31 @@ after') '

A

Bar

' ); + $this->assertEquals( + $this->render('<% include SSViewerTestIncludeScopeInheritanceWithArgsInLoop Title="SomeArg" %>', + new ArrayData(array('Items' => new ArrayList(array( + new ArrayData(array('Title' => 'Foo')), + new ArrayData(array('Title' => 'Bar')) + ))))), + 'SomeArg - Foo - Bar - SomeArg' + ); + + $this->assertEquals( + $this->render('<% include SSViewerTestIncludeScopeInheritanceWithArgsInWith Title="A" %>', + new ArrayData(array('Item' => new ArrayData(array('Title' =>'B'))))), + 'A - B - A' + ); + + $this->assertEquals( + $this->render('<% include SSViewerTestIncludeScopeInheritanceWithArgsInNestedWith Title="A" %>', + new ArrayData(array( + 'Item' => new ArrayData(array( + 'Title' =>'B', 'NestedItem' => new ArrayData(array('Title' => 'C')) + ))) + )), + 'A - B - C - B - A' + ); + $data = new ArrayData(array( 'Nested' => new ArrayData(array( 'Object' => new ArrayData(array('Key' => 'A')) diff --git a/view/SSViewer.php b/view/SSViewer.php index fba2c9590..970c0a882 100644 --- a/view/SSViewer.php +++ b/view/SSViewer.php @@ -25,8 +25,8 @@ class SSViewer_Scope { // The stack of previous "global" items - // And array of item, itemIterator, itemIteratorTotal, pop_index, up_index, current_index - private $itemStack = array(); + // And array of item, itemIterator, itemIteratorTotal, pop_index, up_index, current_index, parent_overlay + private $itemStack = array(); // The current "global" item (the one any lookup starts from) protected $item; @@ -181,6 +181,27 @@ class SSViewer_Scope { $this->resetLocalScope(); return $retval; } + + /** + * @return array + */ + protected function getItemStack() { + return $this->itemStack; + } + + /** + * @param array + */ + protected function setItemStack(array $stack) { + $this->itemStack = $stack; + } + + /** + * @return int|null + */ + protected function getUpIndex() { + return $this->upIndex; + } } /** @@ -519,6 +540,70 @@ class SSViewer_DataPresenter extends SSViewer_Scope { } + /** + * Store the current overlay (as it doesn't directly apply to the new scope + * that's being pushed). We want to store the overlay against the next item + * "up" in the stack (hence upIndex), rather than the current item, because + * SSViewer_Scope::obj() has already been called and pushed the new item to + * the stack by this point + * @return SSViewer_Scope + */ + public function pushScope() { + $scope = parent::pushScope(); + + $itemStack = $this->getItemStack(); + $itemStack[$this->getUpIndex()][6] = $this->overlay; + + $this->setItemStack($itemStack); + $this->overlay = array(); + + return $scope; + } + + /** + * Now that we're going to jump up an item in the item stack, we need to + * restore the overlay that was previously stored against the next item "up" + * in the stack from the current one + * @return SSViewer_Scope + */ + public function popScope() { + $itemStack = $this->getItemStack(); + $this->overlay = $itemStack[$this->getUpIndex()][6]; + + return parent::popScope(); + } + + /** + * $Up and $Top need to restore the overlay from the parent and top-level + * scope respectively. + */ + public function obj($name, $arguments = null, $forceReturnedObject = true, $cache = false, $cacheName = null) { + $overlayIndex = false; + + switch($name) { + case 'Up': + $upIndex = $this->getUpIndex(); + if ($upIndex === null) { + user_error('Up called when we\'re already at the top of the scope', E_USER_ERROR); + } + + $overlayIndex = $upIndex; // Parent scope + break; + case 'Top': + $overlayIndex = 0; // Top-level scope + break; + } + + if ($overlayIndex !== false) { + $itemStack = $this->getItemStack(); + if (!$this->overlay && isset($itemStack[$overlayIndex][6])) { + $this->overlay = $itemStack[$overlayIndex][6]; + } + } + + return parent::obj($name, $arguments, $forceReturnedObject, $cache, $cacheName); + } + public function getObj($name, $arguments = null, $forceReturnedObject = true, $cache = false, $cacheName = null) { $result = $this->getInjectedValue($name, (array)$arguments); if($result) return $result['obj'];