mirror of
https://github.com/silverstripe/silverstripe-framework
synced 2024-10-22 14:05:37 +02:00
API-CHANGE: moving iterator support from ViewableData to SSViewer. New set of unit tests for iterator support functions.
This commit is contained in:
parent
927dbbe717
commit
28bb83552a
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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'),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**#@+
|
||||
|
@ -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) {
|
||||
|
29
view/TemplateIteratorProvider.php
Normal file
29
view/TemplateIteratorProvider.php
Normal file
@ -0,0 +1,29 @@
|
||||
<?php
|
||||
/**
|
||||
* Interface that is implemented by any classes that want to expose a method that can be called in a template.
|
||||
* SSViewer_BasicIteratorSupport is an example of this. See also @TemplateGlobalProvider
|
||||
* @package sapphire
|
||||
* @subpackage core
|
||||
*/
|
||||
interface TemplateIteratorProvider {
|
||||
/**
|
||||
* @abstract
|
||||
* @return array Returns an array of strings of the method names of methods on the call that should be exposed
|
||||
* as global variables in the templates. A map (template-variable-name => 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);
|
||||
}
|
||||
|
||||
?>
|
@ -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 -------------------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user