From 24660afabd7ddcaf87aa859df396862cecea2ef8 Mon Sep 17 00:00:00 2001 From: Loz Calver Date: Tue, 22 Dec 2015 14:48:04 +0000 Subject: [PATCH 1/2] 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']; From f6f3c89dc822da00800434408e89d45cd197883e Mon Sep 17 00:00:00 2001 From: Loz Calver Date: Tue, 22 Dec 2015 14:59:04 +0000 Subject: [PATCH 2/2] Add SSViewer_Scope constants to make it easier to work with item stacks --- view/SSViewer.php | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/view/SSViewer.php b/view/SSViewer.php index 970c0a882..34c3b7148 100644 --- a/view/SSViewer.php +++ b/view/SSViewer.php @@ -24,8 +24,16 @@ */ class SSViewer_Scope { + const ITEM = 0; + const ITEM_ITERATOR = 1; + const ITEM_ITERATOR_TOTAL = 2; + const POP_INDEX = 3; + const UP_INDEX = 4; + const CURRENT_INDEX = 5; + const ITEM_OVERLAY = 6; + // The stack of previous "global" items - // And array of item, itemIterator, itemIteratorTotal, pop_index, up_index, current_index, parent_overlay + // An indexed array of item, item iterator, item iterator total, pop index, up index, current index & parent overlay private $itemStack = array(); // The current "global" item (the one any lookup starts from) @@ -135,12 +143,12 @@ class SSViewer_Scope { public function pushScope(){ $newLocalIndex = count($this->itemStack)-1; - $this->popIndex = $this->itemStack[$newLocalIndex][3] = $this->localIndex; + $this->popIndex = $this->itemStack[$newLocalIndex][SSViewer_Scope::POP_INDEX] = $this->localIndex; $this->localIndex = $newLocalIndex; // We normally keep any previous itemIterator around, so local $Up calls reference the right element. But // once we enter a new global scope, we need to make sure we use a new one - $this->itemIterator = $this->itemStack[$newLocalIndex][1] = null; + $this->itemIterator = $this->itemStack[$newLocalIndex][SSViewer_Scope::ITEM_ITERATOR] = null; return $this; } @@ -159,9 +167,9 @@ class SSViewer_Scope { if (is_array($this->item)) $this->itemIterator = new ArrayIterator($this->item); else $this->itemIterator = $this->item->getIterator(); - $this->itemStack[$this->localIndex][1] = $this->itemIterator; + $this->itemStack[$this->localIndex][SSViewer_Scope::ITEM_ITERATOR] = $this->itemIterator; $this->itemIteratorTotal = iterator_count($this->itemIterator); //count the total number of items - $this->itemStack[$this->localIndex][2] = $this->itemIteratorTotal; + $this->itemStack[$this->localIndex][SSViewer_Scope::ITEM_ITERATOR_TOTAL] = $this->itemIteratorTotal; $this->itemIterator->rewind(); } else { @@ -552,7 +560,7 @@ class SSViewer_DataPresenter extends SSViewer_Scope { $scope = parent::pushScope(); $itemStack = $this->getItemStack(); - $itemStack[$this->getUpIndex()][6] = $this->overlay; + $itemStack[$this->getUpIndex()][SSViewer_Scope::ITEM_OVERLAY] = $this->overlay; $this->setItemStack($itemStack); $this->overlay = array(); @@ -568,7 +576,7 @@ class SSViewer_DataPresenter extends SSViewer_Scope { */ public function popScope() { $itemStack = $this->getItemStack(); - $this->overlay = $itemStack[$this->getUpIndex()][6]; + $this->overlay = $itemStack[$this->getUpIndex()][SSViewer_Scope::ITEM_OVERLAY]; return parent::popScope(); } @@ -596,8 +604,8 @@ class SSViewer_DataPresenter extends SSViewer_Scope { if ($overlayIndex !== false) { $itemStack = $this->getItemStack(); - if (!$this->overlay && isset($itemStack[$overlayIndex][6])) { - $this->overlay = $itemStack[$overlayIndex][6]; + if (!$this->overlay && isset($itemStack[$overlayIndex][SSViewer_Scope::ITEM_OVERLAY])) { + $this->overlay = $itemStack[$overlayIndex][SSViewer_Scope::ITEM_OVERLAY]; } }