From 28bb83552a2b252056390abcb3df0ceae045f291 Mon Sep 17 00:00:00 2001 From: Hamish Friedlander Date: Sat, 11 Feb 2012 15:26:26 +1300 Subject: [PATCH] API-CHANGE: moving iterator support from ViewableData to SSViewer. New set of unit tests for iterator support functions. --- admin/code/LeftAndMain.php | 3 - forms/gridfield/GridField.php | 11 +- forms/gridfield/GridFieldFilter.php | 4 +- tests/core/ArrayDataTest.php | 15 -- tests/view/SSViewerTest.php | 157 ++++++++++++++++++-- tests/view/ViewableDataTest.php | 17 --- view/SSViewer.php | 223 ++++++++++++++++++++++++++-- view/TemplateIteratorProvider.php | 29 ++++ view/ViewableData.php | 127 +--------------- 9 files changed, 393 insertions(+), 193 deletions(-) create mode 100644 view/TemplateIteratorProvider.php diff --git a/admin/code/LeftAndMain.php b/admin/code/LeftAndMain.php index 13cfa82c3..6ca31522b 100644 --- a/admin/code/LeftAndMain.php +++ b/admin/code/LeftAndMain.php @@ -532,9 +532,6 @@ class LeftAndMain extends Controller { } } - // TODO Remove once ViewableData->First()/Last() is fixed - foreach($items as $i => $item) $item->iteratorProperties($i, $items->Count()); - return $items; } diff --git a/forms/gridfield/GridField.php b/forms/gridfield/GridField.php index b2f574727..64dc27023 100755 --- a/forms/gridfield/GridField.php +++ b/forms/gridfield/GridField.php @@ -319,8 +319,9 @@ class GridField extends FormField { } } + $total = $list->count(); + foreach($list as $idx => $record) { - $record->iteratorProperties($idx, $list->count()); $rowContent = ''; foreach($columns as $column) { $colContent = $this->getColumnContent($record, $column); @@ -329,10 +330,16 @@ class GridField extends FormField { $colAttributes = $this->getColumnAttributes($record, $column); $rowContent .= $this->createTag('td', $colAttributes, $colContent); } + + $classes = array('ss-gridfield-item'); + if ($idx == 0) $classes[] = 'first'; + if ($idx == $total-1) $classes[] = 'last'; + $classes[] = ($idx % 2) ? 'even' : 'odd'; + $row = $this->createTag( 'tr', array( - "class" => 'ss-gridfield-item ' . $record->FirstLast() . " " . $record->EvenOdd(), + "class" => implode(' ', $classes), 'data-id' => $record->ID, // TODO Allow per-row customization similar to GridFieldDefaultColumns 'data-class' => $record->ClassName, diff --git a/forms/gridfield/GridFieldFilter.php b/forms/gridfield/GridFieldFilter.php index 4a8165de5..e27e4e829 100644 --- a/forms/gridfield/GridFieldFilter.php +++ b/forms/gridfield/GridFieldFilter.php @@ -85,7 +85,7 @@ class GridFieldFilter implements GridField_HTMLProvider, GridField_DataManipulat $field = new LiteralField('', ''); } - $field->iteratorProperties($currentColumn-1, count($columns)); + $forTemplate->Fields->push($field); } @@ -93,4 +93,4 @@ class GridFieldFilter implements GridField_HTMLProvider, GridField_DataManipulat 'header' => $forTemplate->renderWith('GridFieldFilter_Row'), ); } -} \ No newline at end of file +} diff --git a/tests/core/ArrayDataTest.php b/tests/core/ArrayDataTest.php index 04d1b460c..a6879e79d 100644 --- a/tests/core/ArrayDataTest.php +++ b/tests/core/ArrayDataTest.php @@ -23,15 +23,6 @@ class ArrayDataTest extends SapphireTest { $this->assertFalse($arrayData->hasField('c')); } - function testWrappingAnEmptyObjectWorks() { - $object = (object) array(); - $this->assertTrue(is_object($object)); - - $arrayData = new ArrayData($object); - - $this->assertEquals(null, $arrayData->TotalItems()); // (tobych) Shouldn't we get 0? - } - function testWrappingAnAssociativeArrayWorks() { $array = array("A" => "Alpha", "B" => "Beta"); $this->assertTrue(ArrayLib::is_associative($array)); @@ -43,12 +34,6 @@ class ArrayDataTest extends SapphireTest { $this->assertEquals("Beta", $arrayData->getField("B")); } - function testWrappingAnEmptyArrayWorks() { - $arrayData = new ArrayData(array()); - - $this->assertEquals(null, $arrayData->TotalItems()); // (tobych) Shouldn't we get 0? - } - function testRefusesToWrapAnIndexedArray() { $array = array(0 => "One", 1 => "Two"); $this->assertFalse(ArrayLib::is_associative($array)); diff --git a/tests/view/SSViewerTest.php b/tests/view/SSViewerTest.php index 784f13c5e..78f2e8695 100644 --- a/tests/view/SSViewerTest.php +++ b/tests/view/SSViewerTest.php @@ -97,32 +97,45 @@ SS $this->assertEquals(i18n::get_locale(), $this->render('{$i18nLocale}'), 'i18n template functions result correct result'); $this->assertEquals(i18n::get_locale(), $this->render('{$get_locale}'), 'i18n template functions result correct result'); - $this->assertEquals((string)Controller::curr(), $this->render('{$CurrentPage}'), 'i18n template functions result correct result'); - $this->assertEquals((string)Controller::curr(), $this->render('{$currentPage}'), 'i18n template functions result correct result'); + //only run this test if we have a controller - i.e. if we are running this test from a web-browser + if (Controller::has_curr()) $this->assertEquals((string)Controller::curr(), $this->render('{$CurrentPage}'), 'i18n template functions result correct result'); + if (Controller::has_curr()) $this->assertEquals((string)Controller::curr(), $this->render('{$currentPage}'), 'i18n template functions result correct result'); - $this->assertEquals(Member::currentUser(), $this->render('{$CurrentMember}'), 'Member template functions result correct result'); - $this->assertEquals(Member::currentUser(), $this->render('{$CurrentUser}'), 'Member template functions result correct result'); - $this->assertEquals(Member::currentUser(), $this->render('{$currentMember}'), 'Member template functions result correct result'); - $this->assertEquals(Member::currentUser(), $this->render('{$currentUser}'), 'Member template functions result correct result'); + $this->assertEquals((string)Member::currentUser(), $this->render('{$CurrentMember}'), 'Member template functions result correct result'); + $this->assertEquals((string)Member::currentUser(), $this->render('{$CurrentUser}'), 'Member template functions result correct result'); + $this->assertEquals((string)Member::currentUser(), $this->render('{$currentMember}'), 'Member template functions result correct result'); + $this->assertEquals((string)Member::currentUser(), $this->render('{$currentUser}'), 'Member template functions result correct result'); $this->assertEquals(SecurityToken::getSecurityID(), $this->render('{$getSecurityID}'), 'SecurityToken template functions result correct result'); $this->assertEquals(SecurityToken::getSecurityID(), $this->render('{$SecurityID}'), 'SecurityToken template functions result correct result'); } function testGlobalVariableCallsWithArguments() { - $this->assertEquals(Permission::check("ADMIN"), $this->render('{$HasPerm(\'ADMIN\')}'), 'Permissions template functions result correct result'); - $this->assertEquals(Permission::check("ADMIN"), $this->render('{$hasPerm(\'ADMIN\')}'), 'Permissions template functions result correct result'); + $this->assertEquals(Permission::check("ADMIN"), (bool)$this->render('{$HasPerm(\'ADMIN\')}'), 'Permissions template functions result correct result'); + $this->assertEquals(Permission::check("ADMIN"), (bool)$this->render('{$hasPerm(\'ADMIN\')}'), 'Permissions template functions result correct result'); } - /* //TODO: enable this test - function testLocalFunctionsTakePriorityOverGlobals() { + function testLocalFunctionsTakePriorityOverGlobals() { $data = new ArrayData(array( 'Page' => new SSViewerTest_Page() )); + //call method with lots of arguments + $result = $this->render('<% control Page %>$lotsOfArguments11("a","b","c","d","e","f","g","h","i","j","k")<% end_control %>',$data); + $this->assertEquals("abcdefghijk",$result, "Function can accept up to 11 arguments"); + + //call method that does not exist + $result = $this->render('<% control Page %><% if IDoNotExist %>hello<% end_if %><% end_control %>',$data); + $this->assertEquals("",$result, "Method does not exist - empty result"); + + //call if that does not exist + $result = $this->render('<% control Page %>$IDoNotExist("hello")<% end_control %>',$data); + $this->assertEquals("",$result, "Method does not exist - empty result"); + + //call method with same name as a global method (local call should take priority) $result = $this->render('<% control Page %>$absoluteBaseURL<% end_control %>',$data); - $this->assertEquals("testPageCalled",$result, "Local Object's function called. Did not return the actual baseURL of the current site"); - }*/ + $this->assertEquals("testLocalFunctionPriorityCalled",$result, "Local Object's function called. Did not return the actual baseURL of the current site"); + } function testObjectDotArguments() { $this->assertEquals( @@ -376,7 +389,7 @@ after') function testRecursiveInclude() { $view = new SSViewer(array('SSViewerTestRecursiveInclude')); - + $data = new ArrayData(array( 'Title' => 'A', 'Children' => new ArrayList(array( @@ -462,6 +475,107 @@ after') ); } + function testSSViewerBasicIteratorSupport() { + $data = new ArrayData(array( + 'Set' => new DataObjectSet(array( + new SSViewerTest_Page("1"), + new SSViewerTest_Page("2"), + new SSViewerTest_Page("3"), + new SSViewerTest_Page("4"), + new SSViewerTest_Page("5"), + new SSViewerTest_Page("6"), + new SSViewerTest_Page("7"), + new SSViewerTest_Page("8"), + new SSViewerTest_Page("9"), + new SSViewerTest_Page("10"), + )) + )); + + //base test + $result = $this->render('<% loop Set %>$Number<% end_loop %>',$data); + $this->assertEquals("12345678910",$result,"Numbers rendered in order"); + + //test First + $result = $this->render('<% loop Set %><% if First %>$Number<% end_if %><% end_loop %>',$data); + $this->assertEquals("1",$result,"Only the first number is rendered"); + + //test Last + $result = $this->render('<% loop Set %><% if Last %>$Number<% end_if %><% end_loop %>',$data); + $this->assertEquals("10",$result,"Only the last number is rendered"); + + //test Even + $result = $this->render('<% loop Set %><% if Even() %>$Number<% end_if %><% end_loop %>',$data); + $this->assertEquals("246810",$result,"Even numbers rendered in order"); + + //test Even with quotes + $result = $this->render('<% loop Set %><% if Even("1") %>$Number<% end_if %><% end_loop %>',$data); + $this->assertEquals("246810",$result,"Even numbers rendered in order"); + + //test Even without quotes + $result = $this->render('<% loop Set %><% if Even(1) %>$Number<% end_if %><% end_loop %>',$data); + $this->assertEquals("246810",$result,"Even numbers rendered in order"); + + //test Even with zero-based start index + $result = $this->render('<% loop Set %><% if Even("0") %>$Number<% end_if %><% end_loop %>',$data); + $this->assertEquals("13579",$result,"Even (with zero-based index) numbers rendered in order"); + + //test Odd + $result = $this->render('<% loop Set %><% if Odd %>$Number<% end_if %><% end_loop %>',$data); + $this->assertEquals("13579",$result,"Odd numbers rendered in order"); + + //test FirstLast + $result = $this->render('<% loop Set %><% if FirstLast %>$Number$FirstLast<% end_if %><% end_loop %>',$data); + $this->assertEquals("1first10last",$result,"First and last numbers rendered in order"); + + //test Middle + $result = $this->render('<% loop Set %><% if Middle %>$Number<% end_if %><% end_loop %>',$data); + $this->assertEquals("23456789",$result,"Middle numbers rendered in order"); + + //test MiddleString + $result = $this->render('<% loop Set %><% if MiddleString == "middle" %>$Number$MiddleString<% end_if %><% end_loop %>',$data); + $this->assertEquals("2middle3middle4middle5middle6middle7middle8middle9middle",$result,"Middle numbers rendered in order"); + + //test EvenOdd + $result = $this->render('<% loop Set %>$EvenOdd<% end_loop %>',$data); + $this->assertEquals("oddevenoddevenoddevenoddevenoddeven",$result,"Even and Odd is returned in sequence numbers rendered in order"); + + //test Pos + $result = $this->render('<% loop Set %>$Pos<% end_loop %>',$data); + $this->assertEquals("12345678910",$result,"Even and Odd is returned in sequence numbers rendered in order"); + + //test Total + $result = $this->render('<% loop Set %>$TotalItems<% end_loop %>',$data); + $this->assertEquals("10101010101010101010",$result,"10 total items X 10 are returned"); + + //test Modulus + $result = $this->render('<% loop Set %>$Modulus(2,1)<% end_loop %>',$data); + $this->assertEquals("1010101010",$result,"1-indexed pos modular divided by 2 rendered in order"); + + //test MultipleOf 3 + $result = $this->render('<% loop Set %><% if MultipleOf(3) %>$Number<% end_if %><% end_loop %>',$data); + $this->assertEquals("369",$result,"Only numbers that are multiples of 3 are returned"); + + //test MultipleOf 4 + $result = $this->render('<% loop Set %><% if MultipleOf(4) %>$Number<% end_if %><% end_loop %>',$data); + $this->assertEquals("48",$result,"Only numbers that are multiples of 4 are returned"); + + //test MultipleOf 5 + $result = $this->render('<% loop Set %><% if MultipleOf(5) %>$Number<% end_if %><% end_loop %>',$data); + $this->assertEquals("510",$result,"Only numbers that are multiples of 5 are returned"); + + //test MultipleOf 10 + $result = $this->render('<% loop Set %><% if MultipleOf(10,1) %>$Number<% end_if %><% end_loop %>',$data); + $this->assertEquals("10",$result,"Only numbers that are multiples of 10 (with 1-based indexing) are returned"); + + //test MultipleOf 9 zero-based + $result = $this->render('<% loop Set %><% if MultipleOf(9,0) %>$Number<% end_if %><% end_loop %>',$data); + $this->assertEquals("110",$result,"Only numbers that are multiples of 9 with zero-based indexing are returned. I.e. the first and last item"); + + //test MultipleOf 11 + $result = $this->render('<% loop Set %><% if MultipleOf(11) %>$Number<% end_if %><% end_loop %>',$data); + $this->assertEquals("",$result,"Only numbers that are multiples of 11 are returned. I.e. nothing returned"); + } + /** * Test $Up works when the scope $Up refers to was entered with a "with" block */ @@ -797,7 +911,22 @@ class SSViewerTest_Controller extends Controller { class SSViewerTest_Page extends SiteTree { + public $number = null; + + function __construct($number = null) { + parent::__construct(); + $this->number = $number; + } + + function Number() { + return $this->number; + } + function absoluteBaseURL() { - return "testPageCalled"; + return "testLocalFunctionPriorityCalled"; + } + + function lotsOfArguments11($a, $b, $c, $d, $e, $f, $g, $h, $i, $j, $k) { + return $a. $b. $c. $d. $e. $f. $g. $h. $i. $j. $k; } } diff --git a/tests/view/ViewableDataTest.php b/tests/view/ViewableDataTest.php index c011280ce..0e3bc81cc 100644 --- a/tests/view/ViewableDataTest.php +++ b/tests/view/ViewableDataTest.php @@ -120,23 +120,6 @@ class ViewableDataTest extends SapphireTest { ); } } - - function testFirstLast() { - $vd = new ViewableData(); - - $vd->iteratorProperties(0, 3); - $this->assertEquals('first', $vd->FirstLast()); - - $vd->iteratorProperties(1, 3); - $this->assertEquals(null, $vd->FirstLast()); - - $vd->iteratorProperties(2, 3); - $this->assertEquals('last', $vd->FirstLast()); - - $vd->iteratorProperties(0, 1); - $this->assertEquals('first last', $vd->FirstLast()); - } - } /**#@+ diff --git a/view/SSViewer.php b/view/SSViewer.php index 881c1c31c..314e23f7a 100644 --- a/view/SSViewer.php +++ b/view/SSViewer.php @@ -26,15 +26,17 @@ class SSViewer_Scope { // And array of item, itemIterator, pop_index, up_index, current_index private $itemStack = array(); - private $item; // The current "global" item (the one any lookup starts from) - private $itemIterator; // If we're looping over the current "global" item, here's the iterator that tracks with item we're up to + protected $item; // The current "global" item (the one any lookup starts from) + protected $itemIterator; // If we're looping over the current "global" item, here's the iterator that tracks with item we're up to + protected $itemIteratorTotal; //Total number of items in the iterator private $popIndex; // A pointer into the item stack for which item should be scope on the next pop call private $upIndex; // A pointer into the item stack for which item is "up" from this one private $currentIndex; // A pointer into the item stack for which item is this one (or null if not in stack yet) private $localIndex; - + + function __construct($item){ $this->item = $item; $this->localIndex=0; @@ -100,11 +102,12 @@ class SSViewer_Scope { function next(){ if (!$this->item) return false; - if (!$this->itemIterator) { + if (!$this->itemIterator) { 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->itemIteratorTotal = iterator_count($this->itemIterator); //count the total number of items $this->itemIterator->rewind(); } else { @@ -112,7 +115,7 @@ class SSViewer_Scope { } $this->resetLocalScope(); - + if (!$this->itemIterator->valid()) return false; return $this->itemIterator->key(); } @@ -126,6 +129,163 @@ class SSViewer_Scope { } } +class SSViewer_BasicIteratorSupport implements TemplateIteratorProvider { + + protected $iteratorPos; + protected $iteratorTotalItems; + + public static function getExposedVariables() { + return array( + 'First', + 'Last', + 'FirstLast', + 'Middle', + 'MiddleString', + 'Even', + 'Odd', + 'EvenOdd', + 'Pos', + 'TotalItems', + 'Modulus', + 'MultipleOf', + ); + } + + /** + * Set the current iterator properties - where we are on the iterator. + * + * @param int $pos position in iterator + * @param int $totalItems total number of items + */ + public function iteratorProperties($pos, $totalItems) { + $this->iteratorPos = $pos; + $this->iteratorTotalItems = $totalItems; + } + + /** + * Returns true if this object is the first in a set. + * + * @return bool + */ + public function First() { + return $this->iteratorPos == 0; + } + + /** + * Returns true if this object is the last in a set. + * + * @return bool + */ + public function Last() { + return $this->iteratorPos == $this->iteratorTotalItems - 1; + } + + /** + * Returns 'first' or 'last' if this is the first or last object in the set. + * + * @return string|null + */ + public function FirstLast() { + if($this->First() && $this->Last()) return 'first last'; + if($this->First()) return 'first'; + if($this->Last()) return 'last'; + } + + /** + * Return true if this object is between the first & last objects. + * + * @return bool + */ + public function Middle() { + return !$this->First() && !$this->Last(); + } + + /** + * Return 'middle' if this object is between the first & last objects. + * + * @return string|null + */ + public function MiddleString() { + if($this->Middle()) return 'middle'; + } + + /** + * Return true if this object is an even item in the set. + * The count starts from $startIndex, which defaults to 1. + * + * @param int $startIndex Number to start count from. + * @return bool + */ + public function Even($startIndex = 1) { + return !$this->Odd($startIndex); + } + + /** + * Return true if this is an odd item in the set. + * + * @param int $startIndex Number to start count from. + * @return bool + */ + public function Odd($startIndex = 1) { + return (bool) (($this->iteratorPos+$startIndex) % 2); + } + + /** + * Return 'even' or 'odd' if this object is in an even or odd position in the set respectively. + * + * @param int $startIndex Number to start count from. + * @return string + */ + public function EvenOdd($startIndex = 1) { + return ($this->Even($startIndex)) ? 'even' : 'odd'; + } + + /** + * Return the numerical position of this object in the container set. The count starts at $startIndex. + * The default is the give the position using a 1-based index. + * + * @param int $startIndex Number to start count from. + * @return int + */ + public function Pos($startIndex = 1) { + return $this->iteratorPos + $startIndex; + } + + /** + * Return the total number of "sibling" items in the dataset. + * + * @return int + */ + public function TotalItems() { + return $this->iteratorTotalItems; + } + + /** + * Returns the modulus of the numerical position of the item in the data set. + * The count starts from $startIndex, which defaults to 1. + * @param int $Mod The number to perform Mod operation to. + * @param int $startIndex Number to start count from. + * @return int + */ + public function Modulus($mod, $startIndex = 1) { + return ($this->iteratorPos + $startIndex) % $mod; + } + + /** + * Returns true or false depending on if the pos of the iterator is a multiple of a specific number. + * So, <% if MultipleOf(3) %> would return true on indexes: 3,6,9,12,15, etc. + * The count starts from $offset, which defaults to 1. + * @param int $factor The multiple of which to return + * @param int $offset Number to start count from. + * @return bool + */ + public function MultipleOf($factor, $offset = 1) { + return (bool) ($this->Modulus($factor, $offset) == 0); + } + + + +} /** * This extends SSViewer_Scope to mix in data on top of what the item provides. This can be "global" * data that is scope-independant (like BaseURL), or type-specific data that is layered on top cross-cut like @@ -136,27 +296,39 @@ class SSViewer_Scope { class SSViewer_DataPresenter extends SSViewer_Scope { private static $extras = array(); + private static $iteratorSupport = array(); function __construct($item){ parent::__construct($item); + if (count(self::$iteratorSupport) == 0) { //build up extras array only once per request + $this->createCallableArray(self::$iteratorSupport, "TemplateIteratorProvider", true); //call non-statically + } + if (count(self::$extras) == 0) { //build up extras array only once per request //get all the exposed variables from all classes that implement the TemplateGlobalProvider interface - $implementers = ClassInfo::implementorsOf("TemplateGlobalProvider"); + $this->createCallableArray(self::$extras, "TemplateGlobalProvider"); + } + } + + protected function createCallableArray(&$extraArray, $interfaceToQuery, $createObject = false) { + $implementers = ClassInfo::implementorsOf($interfaceToQuery); + if ($implementers && count($implementers) > 0) { foreach($implementers as $implementer) { + if ($createObject) $implementer = new $implementer(); //create a new instance of the object for method calls $exposedVariables = $implementer::getExposedVariables(); //get the exposed variables foreach($exposedVariables as $varName => $methodName) { if (!$varName || is_numeric($varName)) $varName = $methodName; //array has just a single value, use it for both key and value //e.g. "array(Director, absoluteBaseURL)" means call "Director::absoluteBaseURL()" - self::$extras[$varName] = array($implementer, $methodName); + $extraArray[$varName] = array($implementer, $methodName); $firstCharacter = substr($varName, 0, 1); if ((strtoupper($firstCharacter) === $firstCharacter)) { //is uppercase, so save the lowercase version, too - self::$extras[lcfirst($varName)] = array($implementer, $methodName); //callable array + $extraArray[lcfirst($varName)] = array($implementer, $methodName); //callable array } else { //is lowercase, save a version so it also works uppercase - self::$extras[ucfirst($varName)] = array($implementer, $methodName); + $extraArray[ucfirst($varName)] = array($implementer, $methodName); } } } @@ -164,17 +336,40 @@ class SSViewer_DataPresenter extends SSViewer_Scope { } function __call($name, $arguments) { - //TODO: make local functions take priority over global functions + //extract the method name and parameters + $property = $arguments[0]; //the name of the function being called + if ($arguments[1]) $params = $arguments[1]; //the function parameters in an array + else $params = array(); - $property = $arguments[0]; - if (array_key_exists($property, self::$extras)) { + //check if the method to-be-called exists on the target object + $on = $this->itemIterator ? $this->itemIterator->current() : $this->item; + if (method_exists($on, $property)) { //return the result immediately without trying global functions + return parent::__call($name, $arguments); + } + + //attempt to call a "global" functions + if (array_key_exists($property, self::$extras) || array_key_exists($property, self::$iteratorSupport)) { $this->resetLocalScope(); //if we are inside a chain (e.g. $A.B.C.Up.E) break out to the beginning of it - $value = self::$extras[$property]; //get the method call + //special case for the iterator, which need current index and total number of items + if (array_key_exists($property, self::$iteratorSupport)) { + $value = self::$iteratorSupport[$property]; + + if ($this->itemIterator) { + // Set the current iterator position and total (the object instance is the first item in the callable array) + $value[0]->iteratorProperties($this->itemIterator->key(), $this->itemIteratorTotal); + } else { + // If we don't actually have an iterator at the moment, act like a list of length 1 + $value[0]->iteratorProperties(0, 1); + } + } else { //normal case of extras call + $value = self::$extras[$property]; //get the method call + } //only call callable functions if (is_callable($value)) { - $value = call_user_func_array($value, array_slice($arguments, 1)); + //$value = call_user_func_array($value, array_slice($arguments, 1)); + $value = call_user_func_array($value, $params); } switch ($name) { diff --git a/view/TemplateIteratorProvider.php b/view/TemplateIteratorProvider.php new file mode 100644 index 000000000..21fca7345 --- /dev/null +++ b/view/TemplateIteratorProvider.php @@ -0,0 +1,29 @@ + method-name) can optionally be supplied + * if the template variable name is different from the name of the method to call. The case of the first character + * in the method name called from the template does not matter, although names specified in the map should + * correspond to the actual method name in the relevant class. + * Note that the template renderer must be able to call these methods statically. + */ + public static function getExposedVariables(); + + /** + * Set the current iterator properties - where we are on the iterator. + * @abstract + * @param int $pos position in iterator + * @param int $totalItems total number of items + */ + public function iteratorProperties($pos, $totalItems); +} + +?> \ No newline at end of file diff --git a/view/ViewableData.php b/view/ViewableData.php index a81d2cdac..ed41e2ad8 100644 --- a/view/ViewableData.php +++ b/view/ViewableData.php @@ -40,12 +40,7 @@ class ViewableData extends Object implements IteratorAggregate { private static $casting_cache = array(); // ----------------------------------------------------------------------------------------------------------------- - - /** - * @var int - */ - protected $iteratorPos, $iteratorTotalItems; - + /** * A failover object to attempt to get data from if it is not present on this object. * @@ -510,126 +505,6 @@ class ViewableData extends Object implements IteratorAggregate { return new ArrayIterator(array($this)); } - /** - * Set the current iterator properties - where we are on the iterator. - * - * @param int $pos position in iterator - * @param int $totalItems total number of items - */ - public function iteratorProperties($pos, $totalItems) { - $this->iteratorPos = $pos; - $this->iteratorTotalItems = $totalItems; - } - - /** - * Returns true if this object is the first in a set. - * - * @return bool - */ - public function First() { - return $this->iteratorPos == 0; - } - - /** - * Returns true if this object is the last in a set. - * - * @return bool - */ - public function Last() { - return $this->iteratorPos == $this->iteratorTotalItems - 1; - } - - /** - * Returns 'first' or 'last' if this is the first or last object in the set. - * - * @return string|null - */ - public function FirstLast() { - if($this->First() && $this->Last()) return 'first last'; - if($this->First()) return 'first'; - if($this->Last()) return 'last'; - } - - /** - * Return true if this object is between the first & last objects. - * - * @return bool - */ - public function Middle() { - return !$this->First() && !$this->Last(); - } - - /** - * Return 'middle' if this object is between the first & last objects. - * - * @return string|null - */ - public function MiddleString() { - if($this->Middle()) return 'middle'; - } - - /** - * Return true if this object is an even item in the set. - * - * @return bool - */ - public function Even() { - return (bool) ($this->iteratorPos % 2); - } - - /** - * Return true if this is an odd item in the set. - * - * @return bool - */ - public function Odd() { - return !$this->Even(); - } - - /** - * Return 'even' or 'odd' if this object is in an even or odd position in the set respectively. - * - * @return string - */ - public function EvenOdd() { - return ($this->Even()) ? 'even' : 'odd'; - } - - /** - * Return the numerical position of this object in the container set. The count starts at $startIndex. - * - * @param int $startIndex Number to start count from. - * @return int - */ - public function Pos($startIndex = 1) { - return $this->iteratorPos + $startIndex; - } - - /** - * Return the total number of "sibling" items in the dataset. - * - * @return int - */ - public function TotalItems() { - return $this->iteratorTotalItems; - } - - /** - * Returns the modulus of the numerical position of the item in the data set. - * The count starts from $startIndex, which defaults to 1. - * @param int $Mod The number to perform Mod operation to. - * @param int $startIndex Number to start count from. - * @return int - */ - public function Modulus($mod, $startIndex = 1) { - return ($this->iteratorPos + $startIndex) % $mod; - } - - public function MultipleOf($factor, $offset = 1) { - return ($this->Modulus($factor, $offset) == 0); - } - - // UTILITY METHODS ------------------------------------------------------------------------------------------------- /**