From e78419cef1e1dc0f74f53ed0b47da1428e9a48de Mon Sep 17 00:00:00 2001 From: Sean Harvey Date: Fri, 9 Mar 2012 16:47:35 +1300 Subject: [PATCH 01/12] MINOR Fixed tests for GridFieldToolbarHeaderTest --- .../gridfield/GridFieldToolbarHeaderTest.php | 20 +++++++++++++++---- .../gridfield/GridFieldToolbarHeaderTest.yml | 5 +++++ 2 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 tests/forms/gridfield/GridFieldToolbarHeaderTest.yml diff --git a/tests/forms/gridfield/GridFieldToolbarHeaderTest.php b/tests/forms/gridfield/GridFieldToolbarHeaderTest.php index 2f67df433..f4634522b 100644 --- a/tests/forms/gridfield/GridFieldToolbarHeaderTest.php +++ b/tests/forms/gridfield/GridFieldToolbarHeaderTest.php @@ -1,6 +1,11 @@ logInWithPermission('ADMIN'); @@ -8,7 +13,7 @@ class GridFieldToolbarHeaderTest extends SapphireTest { $config = new GridFieldConfig(); $config->addComponent($titleField = new GridFieldToolbarHeader()); $actions = new FieldList(); - $grid = new GridField('TestField', 'Test Field', new DataList('Company'),$config); + $grid = new GridField('TestField', 'Test Field', new DataList('GridFieldDetailFormTest_Company'),$config); $fields = new FieldList($rootTab = new TabSet("Root",$tabMain = new Tab('Main',$grid))); $form = new Form(Controller::curr(), "TestForm", $fields, $actions); @@ -23,7 +28,7 @@ class GridFieldToolbarHeaderTest extends SapphireTest { $config = new GridFieldConfig(); $config->addComponent($titleField = new GridFieldToolbarHeader()); $actions = new FieldList(); - $grid = new GridField('TestField', 'Test Field', new DataList('Company'),$config); + $grid = new GridField('TestField', 'Test Field', new DataList('GridFieldDetailFormTest_Company'),$config); $fields = new FieldList($rootTab = new TabSet("Root",$tabMain = new Tab('Main',$grid))); $form = new Form(Controller::curr(), "TestForm", $fields, $actions); @@ -36,11 +41,18 @@ class GridFieldToolbarHeaderTest extends SapphireTest { if(Member::currentUser()) { Member::currentUser()->logOut(); } $config = new GridFieldConfig(); $config->addComponent($titleField = new GridFieldToolbarHeader()); - $grid = new GridField('TestField', 'Test Field', new DataList('Company'),$config); + $grid = new GridField('TestField', 'Test Field', new DataList('GridFieldDetailFormTest_Company'),$config); $fields = new FieldList(new TabSet("Root",$tabMain = new Tab('Main',$grid))); $form = new Form(Controller::curr(), "TestForm", $fields, new FieldList()); $html = $form->forTemplate(); $this->assertNotContains('data-icon="add"', $html, "HTML should not contain the 'add new' button"); } -} \ No newline at end of file +} +class GridFieldDetailFormTest_Company extends DataObject implements TestOnly { + + public static $db = array( + 'Name' => 'Varchar(100)' + ); + +} diff --git a/tests/forms/gridfield/GridFieldToolbarHeaderTest.yml b/tests/forms/gridfield/GridFieldToolbarHeaderTest.yml new file mode 100644 index 000000000..574d87a62 --- /dev/null +++ b/tests/forms/gridfield/GridFieldToolbarHeaderTest.yml @@ -0,0 +1,5 @@ +GridFieldDetailFormTest_Company: + test-company-1: + Name: Test company 1 + test-company-2: + Name: Test company 2 From 033a1e593ae30755e7e0fead06fe12a829e86fea Mon Sep 17 00:00:00 2001 From: Sean Harvey Date: Fri, 9 Mar 2012 16:55:20 +1300 Subject: [PATCH 02/12] MINOR Fixed naming of test classes --- tests/forms/gridfield/GridFieldToolbarHeaderTest.php | 10 +++++----- tests/forms/gridfield/GridFieldToolbarHeaderTest.yml | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/forms/gridfield/GridFieldToolbarHeaderTest.php b/tests/forms/gridfield/GridFieldToolbarHeaderTest.php index f4634522b..ae6558e1d 100644 --- a/tests/forms/gridfield/GridFieldToolbarHeaderTest.php +++ b/tests/forms/gridfield/GridFieldToolbarHeaderTest.php @@ -4,7 +4,7 @@ class GridFieldToolbarHeaderTest extends SapphireTest { static $fixture_file = 'GridFieldToolbarHeaderTest.yml'; protected $extraDataObjects = array( - 'GridFieldDetailFormTest_Company', + 'GridFieldToolbarHeaderTest_Company', ); public function testGridTitleAddNewEnabled() { @@ -13,7 +13,7 @@ class GridFieldToolbarHeaderTest extends SapphireTest { $config = new GridFieldConfig(); $config->addComponent($titleField = new GridFieldToolbarHeader()); $actions = new FieldList(); - $grid = new GridField('TestField', 'Test Field', new DataList('GridFieldDetailFormTest_Company'),$config); + $grid = new GridField('TestField', 'Test Field', new DataList('GridFieldToolbarHeaderTest_Company'),$config); $fields = new FieldList($rootTab = new TabSet("Root",$tabMain = new Tab('Main',$grid))); $form = new Form(Controller::curr(), "TestForm", $fields, $actions); @@ -28,7 +28,7 @@ class GridFieldToolbarHeaderTest extends SapphireTest { $config = new GridFieldConfig(); $config->addComponent($titleField = new GridFieldToolbarHeader()); $actions = new FieldList(); - $grid = new GridField('TestField', 'Test Field', new DataList('GridFieldDetailFormTest_Company'),$config); + $grid = new GridField('TestField', 'Test Field', new DataList('GridFieldToolbarHeaderTest_Company'),$config); $fields = new FieldList($rootTab = new TabSet("Root",$tabMain = new Tab('Main',$grid))); $form = new Form(Controller::curr(), "TestForm", $fields, $actions); @@ -41,7 +41,7 @@ class GridFieldToolbarHeaderTest extends SapphireTest { if(Member::currentUser()) { Member::currentUser()->logOut(); } $config = new GridFieldConfig(); $config->addComponent($titleField = new GridFieldToolbarHeader()); - $grid = new GridField('TestField', 'Test Field', new DataList('GridFieldDetailFormTest_Company'),$config); + $grid = new GridField('TestField', 'Test Field', new DataList('GridFieldToolbarHeaderTest_Company'),$config); $fields = new FieldList(new TabSet("Root",$tabMain = new Tab('Main',$grid))); $form = new Form(Controller::curr(), "TestForm", $fields, new FieldList()); @@ -49,7 +49,7 @@ class GridFieldToolbarHeaderTest extends SapphireTest { $this->assertNotContains('data-icon="add"', $html, "HTML should not contain the 'add new' button"); } } -class GridFieldDetailFormTest_Company extends DataObject implements TestOnly { +class GridFieldToolbarHeaderTest_Company extends DataObject implements TestOnly { public static $db = array( 'Name' => 'Varchar(100)' diff --git a/tests/forms/gridfield/GridFieldToolbarHeaderTest.yml b/tests/forms/gridfield/GridFieldToolbarHeaderTest.yml index 574d87a62..88156d5df 100644 --- a/tests/forms/gridfield/GridFieldToolbarHeaderTest.yml +++ b/tests/forms/gridfield/GridFieldToolbarHeaderTest.yml @@ -1,4 +1,4 @@ -GridFieldDetailFormTest_Company: +GridFieldToolbarHeaderTest_Company: test-company-1: Name: Test company 1 test-company-2: From a55e06f6b55ef7d1e2bb3be6e43c2c7b5b52db49 Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Fri, 9 Mar 2012 14:02:37 +1300 Subject: [PATCH 03/12] API CHANGE: Introduce SS_Limitable class for adding to SS_Lists that have limit capability. API CHANGE: Deprecated SS_List::getRange() in favour of SS_Limitable::limit(). API CHANGE: Introduce SS_Limitable::limit($limit, $offset = 0) as the only modern way of specifying limits; deprecate all others. --- core/PaginatedList.php | 2 +- forms/TableListField.php | 2 +- forms/gridfield/GridFieldPaginator.php | 16 +++------------- model/ArrayList.php | 23 ++++++++++++++++++++++- model/DataList.php | 16 ++++++++++------ model/DataQuery.php | 4 ++-- model/Limitable.php | 19 +++++++++++++++++++ model/List.php | 9 --------- model/ListDecorator.php | 10 +++++----- model/SQLQuery.php | 8 ++++---- tests/model/ArrayListTest.php | 4 ++-- tests/model/DataListTest.php | 2 +- tests/model/DataObjectTest.php | 16 +++++----------- tests/model/SQLQueryTest.php | 10 ++-------- 14 files changed, 77 insertions(+), 64 deletions(-) create mode 100644 model/Limitable.php diff --git a/core/PaginatedList.php b/core/PaginatedList.php index 779021d6e..6a2718bc4 100644 --- a/core/PaginatedList.php +++ b/core/PaginatedList.php @@ -149,7 +149,7 @@ class PaginatedList extends SS_ListDecorator { */ public function getIterator() { return new IteratorIterator( - $this->list->getRange($this->getPageStart(), $this->pageLength) + $this->list->limit($this->pageLength, $this->getPageStart()) ); } diff --git a/forms/TableListField.php b/forms/TableListField.php index bd20da389..05cc6b5bd 100644 --- a/forms/TableListField.php +++ b/forms/TableListField.php @@ -430,7 +430,7 @@ JS $SQL_start = 0; } - $items = $items->getRange($SQL_start, $SQL_limit); + $items = $items->limit($SQL_limit, $SQL_start); } return $items; diff --git a/forms/gridfield/GridFieldPaginator.php b/forms/gridfield/GridFieldPaginator.php index 7805f227d..76629be09 100755 --- a/forms/gridfield/GridFieldPaginator.php +++ b/forms/gridfield/GridFieldPaginator.php @@ -64,14 +64,14 @@ class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipu if(!is_int($state->currentPage)) $state->currentPage = 1; - if(!$this->getListPaginatable($dataList)) { + if(!($dataList instanceof SS_Limitable)) { return $dataList; } if(!$state->currentPage) { - return $dataList->getRange(0, (int)$this->itemsPerPage); + return $dataList->limit((int)$this->itemsPerPage); } $startRow = $this->itemsPerPage * ($state->currentPage - 1); - return $dataList->getRange((int)$startRow, (int)$this->itemsPerPage); + return $dataList->limit((int)$this->itemsPerPage, (int)$startRow); } /** @@ -159,14 +159,4 @@ class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipu return $this->itemsPerPage; } - /** Duck check to see if list support methods we need to paginate */ - protected function getListPaginatable(SS_List $list) { - // If no list yet, not paginatable - if (!$list) return false; - // Check for methods we use - if(!method_exists($list, 'getRange')) return false; - if(!method_exists($list, 'limit')) return false; - // Default it true - return true; - } } diff --git a/model/ArrayList.php b/model/ArrayList.php index 0df9e7c2f..14f01c4b5 100644 --- a/model/ArrayList.php +++ b/model/ArrayList.php @@ -5,7 +5,7 @@ * @package sapphire * @subpackage model */ -class ArrayList extends ViewableData implements SS_List { +class ArrayList extends ViewableData implements SS_List, SS_Limitable { /** * Holds the items in the list @@ -119,6 +119,18 @@ class ArrayList extends ViewableData implements SS_List { * @return ArrayList */ public function getRange($offset, $length) { + Deprecation::notice("3.0", 'getRange($offset, $length) is deprecated. Use limit($length, $offset) instead. Note the new argument order.'); + return $this->limit($length, $offset); + } + + /** + * Get a sub-range of this dataobjectset as an array + * + * @param int $offset + * @param int $length + * @return ArrayList + */ + public function limit($length, $offset = 0) { return new ArrayList(array_slice($this->items, $offset, $length)); } @@ -357,6 +369,15 @@ class ArrayList extends ViewableData implements SS_List { call_user_func_array('array_multisort', $multisortArgs); return $this; } + + /** + * Returns true if the given column can be used to filter the records. + * + * It works by checking the fields available in the first record of the list. + */ + public function canFilterBy($by) { + return array_key_exists($by, $this->first()); + } /** * Filter the list to include items with these charactaristics diff --git a/model/DataList.php b/model/DataList.php index 5e83a5d7a..7ca91002d 100644 --- a/model/DataList.php +++ b/model/DataList.php @@ -6,7 +6,7 @@ * @package sapphire * @subpackage model */ -class DataList extends ViewableData implements SS_List { +class DataList extends ViewableData implements SS_List, SS_Limitable { /** * The DataObject class name that this data list is querying * @@ -145,8 +145,11 @@ class DataList extends ViewableData implements SS_List { * * @param string $limit */ - public function limit($limit) { - $this->dataQuery->limit($limit); + public function limit($limit, $offset = 0) { + if(!is_numeric($limit)) { + Deprecation::notice('3.0', 'Please pass limits as 2 arguments, rather than an array or SQL fragment.'); + } + $this->dataQuery->limit($limit, $offset); return $this; } @@ -545,7 +548,8 @@ class DataList extends ViewableData implements SS_List { * @return DataList */ public function getRange($offset, $length) { - return $this->limit(array('start' => $offset, 'limit' => $length)); + Deprecation::notice("3.0", 'getRange($offset, $length) is deprecated. Use limit($length, $offset) instead. Note the new argument order.'); + return $this->limit($length, $offset); } /** @@ -813,7 +817,7 @@ class DataList extends ViewableData implements SS_List { * @return bool */ public function offsetExists($key) { - return ($this->getRange($key, 1)->First() != null); + return ($this->limit(1,$key)->First() != null); } /** @@ -823,7 +827,7 @@ class DataList extends ViewableData implements SS_List { * @return DataObject */ public function offsetGet($key) { - return $this->getRange($key, 1)->First(); + return $this->limit(1, $key)->First(); } /** diff --git a/model/DataQuery.php b/model/DataQuery.php index a11dca4e0..de6514e5a 100644 --- a/model/DataQuery.php +++ b/model/DataQuery.php @@ -373,9 +373,9 @@ class DataQuery { /** * Set the limit of this query */ - function limit($limit) { + function limit($limit, $offset = 0) { $clone = $this; - $clone->query->limit($limit); + $clone->query->limit($limit, $offset); return $clone; } diff --git a/model/Limitable.php b/model/Limitable.php new file mode 100644 index 000000000..1c0560b66 --- /dev/null +++ b/model/Limitable.php @@ -0,0 +1,19 @@ +list->remove($itemObject); } - public function getRange($offset, $length) { - return $this->list->getRange($offset, $length); - } - public function getIterator() { return $this->list->getIterator(); } @@ -136,6 +132,10 @@ abstract class SS_ListDecorator extends ViewableData implements SS_List { return call_user_func_array(array($this->list, 'filter'), $args); } + public function limit($length, $offset) { + return $this->list->getRange($length, $offset); + } + /** * Exclude the list to not contain items with these charactaristics * diff --git a/model/SQLQuery.php b/model/SQLQuery.php index f818ceff1..c283474a5 100644 --- a/model/SQLQuery.php +++ b/model/SQLQuery.php @@ -252,10 +252,10 @@ class SQLQuery { * @param string|array $limit * @return SQLQuery This instance */ - public function limit($limit) { + public function limit($limit, $offset = 0) { if($limit && is_numeric($limit)) { $this->limit = array( - 'start' => 0, + 'start' => $offset, 'limit' => $limit, ); } else if($limit && is_string($limit)) { @@ -667,7 +667,7 @@ class SQLQuery { function firstRow() { $query = clone $this; $offset = $this->limit ? $this->limit['start'] : 0; - $query->limit(array('start' => $offset, 'limit' => 1)); + $query->limit(1, $offset); return $query; } @@ -677,7 +677,7 @@ class SQLQuery { function lastRow() { $query = clone $this; $offset = $this->limit ? $this->limit['start'] : 0; - $query->limit(array('start' => $this->count() + $offset - 1, 'limit' => 1)); + $query->limit(1, $this->count() + $offset - 1); return $query; } diff --git a/tests/model/ArrayListTest.php b/tests/model/ArrayListTest.php index bf84fff9f..f06f1a312 100644 --- a/tests/model/ArrayListTest.php +++ b/tests/model/ArrayListTest.php @@ -61,11 +61,11 @@ class ArrayListTest extends SapphireTest { )); } - public function testGetRange() { + public function testLimit() { $list = new ArrayList(array( array('Key' => 1), array('Key' => 2), array('Key' => 3) )); - $this->assertEquals($list->getRange(1, 2)->toArray(), array( + $this->assertEquals($list->limit(2,1)->toArray(), array( array('Key' => 2), array('Key' => 3) )); } diff --git a/tests/model/DataListTest.php b/tests/model/DataListTest.php index 86850b26d..bec99455c 100755 --- a/tests/model/DataListTest.php +++ b/tests/model/DataListTest.php @@ -50,7 +50,7 @@ class DataListTest extends SapphireTest { $this->assertEquals(array('Bob', 'Joe', 'Phil'), $list->column('Name')); // We can also restrict the output to a range - $this->assertEquals(array('Joe', 'Phil'), $list->getRange(1,2)->column('Name')); + $this->assertEquals(array('Joe', 'Phil'), $list->limit(2, 1)->column('Name')); } function testDataClass() { diff --git a/tests/model/DataObjectTest.php b/tests/model/DataObjectTest.php index 6e44a1b92..056c8f2c9 100644 --- a/tests/model/DataObjectTest.php +++ b/tests/model/DataObjectTest.php @@ -197,18 +197,12 @@ class DataObjectTest extends SapphireTest { // There's 4 records in total $this->assertEquals(4, $players->count()); - // Testing "## offset ##" syntax - $this->assertEquals(4, $players->limit("20 OFFSET 0")->count()); - $this->assertEquals(0, $players->limit("20 OFFSET 20")->count()); - $this->assertEquals(2, $players->limit("2 OFFSET 0")->count()); - $this->assertEquals(1, $players->limit("5 OFFSET 3")->count()); - // Testing "##, ##" syntax - $this->assertEquals(4, $players->limit("20")->count()); - $this->assertEquals(4, $players->limit("0, 20")->count()); - $this->assertEquals(0, $players->limit("20, 20")->count()); - $this->assertEquals(2, $players->limit("0, 2")->count()); - $this->assertEquals(1, $players->limit("3, 5")->count()); + $this->assertEquals(4, $players->limit(20)->count()); + $this->assertEquals(4, $players->limit(20, 0)->count()); + $this->assertEquals(0, $players->limit(20, 20)->count()); + $this->assertEquals(2, $players->limit(2, 0)->count()); + $this->assertEquals(1, $players->limit(5, 3)->count()); } /** diff --git a/tests/model/SQLQueryTest.php b/tests/model/SQLQueryTest.php index 8d92021b4..556bdef8e 100755 --- a/tests/model/SQLQueryTest.php +++ b/tests/model/SQLQueryTest.php @@ -87,19 +87,13 @@ class SQLQueryTest extends SapphireTest { // numeric limit $query = new SQLQuery(); $query->from[] = "MyTable"; - $query->limit("99"); + $query->limit(99); $this->assertEquals("SELECT * FROM MyTable LIMIT 99", $query->sql()); - // array limit - $query = new SQLQuery(); - $query->from[] = "MyTable"; - $query->limit(array('limit'=>99)); - $this->assertEquals("SELECT * FROM MyTable LIMIT 99", $query->sql()); - // array limit with start (MySQL specific) $query = new SQLQuery(); $query->from[] = "MyTable"; - $query->limit(array('limit'=>99, 'start'=>97)); + $query->limit(99, 97); $this->assertEquals("SELECT * FROM MyTable LIMIT 99 OFFSET 97", $query->sql()); } } From f000a47813ca94766aab727893bf7a2985a22058 Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Fri, 9 Mar 2012 14:26:27 +1300 Subject: [PATCH 04/12] API CHANGE: Added SS_Filterable, an extra interface to apply to lists. --- model/ArrayList.php | 2 +- model/DataList.php | 2 +- model/Filterable.php | 40 ++++++++++++++++++++++++++++++++++++++++ model/List.php | 41 ----------------------------------------- model/ListDecorator.php | 6 +++++- 5 files changed, 47 insertions(+), 44 deletions(-) create mode 100644 model/Filterable.php diff --git a/model/ArrayList.php b/model/ArrayList.php index 14f01c4b5..1de211f40 100644 --- a/model/ArrayList.php +++ b/model/ArrayList.php @@ -5,7 +5,7 @@ * @package sapphire * @subpackage model */ -class ArrayList extends ViewableData implements SS_List, SS_Limitable { +class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Limitable { /** * Holds the items in the list diff --git a/model/DataList.php b/model/DataList.php index 7ca91002d..bb9e2bfa4 100644 --- a/model/DataList.php +++ b/model/DataList.php @@ -6,7 +6,7 @@ * @package sapphire * @subpackage model */ -class DataList extends ViewableData implements SS_List, SS_Limitable { +class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Limitable { /** * The DataObject class name that this data list is querying * diff --git a/model/Filterable.php b/model/Filterable.php new file mode 100644 index 000000000..5fcdda99b --- /dev/null +++ b/model/Filterable.php @@ -0,0 +1,40 @@ +filter('Name', 'bob'); // only bob in the list + * @example $list->filter('Name', array('aziz', 'bob'); // aziz and bob in list + * @example $list->filter(array('Name'=>'bob, 'Age'=>21)); // bob with the age 21 + * @example $list->filter(array('Name'=>'bob, 'Age'=>array(21, 43))); // bob with the Age 21 or 43 + * @example $list->filter(array('Name'=>array('aziz','bob'), 'Age'=>array(21, 43))); // aziz with the age 21 or 43 and bob with the Age 21 or 43 + */ + public function filter(); + + /** + * Exclude the list to not contain items with these charactaristics + * + * @example $list->exclude('Name', 'bob'); // exclude bob from list + * @example $list->exclude('Name', array('aziz', 'bob'); // exclude aziz and bob from list + * @example $list->exclude(array('Name'=>'bob, 'Age'=>21)); // exclude bob that has Age 21 + * @example $list->exclude(array('Name'=>'bob, 'Age'=>array(21, 43))); // exclude bob with Age 21 or 43 + * @example $list->exclude(array('Name'=>array('bob','phil'), 'Age'=>array(21, 43))); // bob age 21 or 43, phil age 21 or 43 would be excluded + */ + public function exclude(); + +} \ No newline at end of file diff --git a/model/List.php b/model/List.php index 5d2b0d176..970c8a23c 100644 --- a/model/List.php +++ b/model/List.php @@ -78,45 +78,4 @@ interface SS_List extends ArrayAccess, Countable, IteratorAggregate { */ public function column($colName = "ID"); - /** - * Returns TRUE if the list can be sorted by a field. - * - * @param string $by - * @return bool - */ - public function canSortBy($by); - - /** - * Sorts this list by one or more fields. You can either pass in a single - * field name and direction, or a map of field names to sort directions. - * - * @example $list->sort('Name'); // default ASC sorting - * @example $list->sort('Name DESC'); // DESC sorting - * @example $list->sort('Name', 'ASC'); - * @example $list->sort(array('Name'=>'ASC,'Age'=>'DESC')); - */ - public function sort(); - - /** - * Filter the list to include items with these charactaristics - * - * @example $list->filter('Name', 'bob'); // only bob in the list - * @example $list->filter('Name', array('aziz', 'bob'); // aziz and bob in list - * @example $list->filter(array('Name'=>'bob, 'Age'=>21)); // bob with the age 21 - * @example $list->filter(array('Name'=>'bob, 'Age'=>array(21, 43))); // bob with the Age 21 or 43 - * @example $list->filter(array('Name'=>array('aziz','bob'), 'Age'=>array(21, 43))); // aziz with the age 21 or 43 and bob with the Age 21 or 43 - */ - public function filter(); - - /** - * Exclude the list to not contain items with these charactaristics - * - * @example $list->exclude('Name', 'bob'); // exclude bob from list - * @example $list->exclude('Name', array('aziz', 'bob'); // exclude aziz and bob from list - * @example $list->exclude(array('Name'=>'bob, 'Age'=>21)); // exclude bob that has Age 21 - * @example $list->exclude(array('Name'=>'bob, 'Age'=>array(21, 43))); // exclude bob with Age 21 or 43 - * @example $list->exclude(array('Name'=>array('bob','phil'), 'Age'=>array(21, 43))); // bob age 21 or 43, phil age 21 or 43 would be excluded - */ - public function exclude(); - } \ No newline at end of file diff --git a/model/ListDecorator.php b/model/ListDecorator.php index 1cd1d47c8..46efb202b 100644 --- a/model/ListDecorator.php +++ b/model/ListDecorator.php @@ -7,7 +7,7 @@ * @package sapphire * @subpackage model */ -abstract class SS_ListDecorator extends ViewableData implements SS_List, S_Limitable { +abstract class SS_ListDecorator extends ViewableData implements SS_List, SS_Filterable, SS_Limitable { protected $list; @@ -119,6 +119,10 @@ abstract class SS_ListDecorator extends ViewableData implements SS_List, S_Limit return call_user_func_array(array($this->list, 'sort'), $args); } + public function canFilterBy($by) { + return $this->list->canFilterBy($by); + } + /** * Filter the list to include items with these charactaristics * From e9e7655867152d55fc3d83635cc427b25f0d895a Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Fri, 9 Mar 2012 14:28:14 +1300 Subject: [PATCH 05/12] API CHANGE: Added SS_Sortable, an extra interface to apply to SS_Lists. --- model/ArrayList.php | 2 +- model/DataList.php | 2 +- model/ListDecorator.php | 2 +- model/Sortable.php | 29 +++++++++++++++++++++++++++++ 4 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 model/Sortable.php diff --git a/model/ArrayList.php b/model/ArrayList.php index 1de211f40..fe7bd91f2 100644 --- a/model/ArrayList.php +++ b/model/ArrayList.php @@ -5,7 +5,7 @@ * @package sapphire * @subpackage model */ -class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Limitable { +class ArrayList extends ViewableData implements SS_List, SS_Filterable, SS_Sortable, SS_Limitable { /** * Holds the items in the list diff --git a/model/DataList.php b/model/DataList.php index bb9e2bfa4..92af851e1 100644 --- a/model/DataList.php +++ b/model/DataList.php @@ -6,7 +6,7 @@ * @package sapphire * @subpackage model */ -class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Limitable { +class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortable, SS_Limitable { /** * The DataObject class name that this data list is querying * diff --git a/model/ListDecorator.php b/model/ListDecorator.php index 46efb202b..53da6a0a0 100644 --- a/model/ListDecorator.php +++ b/model/ListDecorator.php @@ -7,7 +7,7 @@ * @package sapphire * @subpackage model */ -abstract class SS_ListDecorator extends ViewableData implements SS_List, SS_Filterable, SS_Limitable { +abstract class SS_ListDecorator extends ViewableData implements SS_List, SS_Sortable, SS_Filterable, SS_Limitable { protected $list; diff --git a/model/Sortable.php b/model/Sortable.php new file mode 100644 index 000000000..e916f3529 --- /dev/null +++ b/model/Sortable.php @@ -0,0 +1,29 @@ +sort('Name'); // default ASC sorting + * @example $list->sort('Name DESC'); // DESC sorting + * @example $list->sort('Name', 'ASC'); + * @example $list->sort(array('Name'=>'ASC,'Age'=>'DESC')); + */ + public function sort(); + +} \ No newline at end of file From 426f16764c3d1d2abc34ea9fcb0975bcf3894a3d Mon Sep 17 00:00:00 2001 From: Sam Minnee Date: Fri, 9 Mar 2012 14:32:16 +1300 Subject: [PATCH 06/12] ENHANCEMENT: GridFieldComponents will now use SS_Filterable, SS_Sortable, and SS_Limitable to determine which features are applicable to the List provided, and either throw an error, or silently disable the feature. API CHANGE: Added throwExceptionOnBadDataType() to GridFilterFilter, GridFieldPaginator, and GridFieldSortableHeader. --- forms/gridfield/GridField.php | 2 +- forms/gridfield/GridFieldConfig.php | 32 +++++++++++----- forms/gridfield/GridFieldFilterHeader.php | 41 +++++++++++++++++++++ forms/gridfield/GridFieldPaginator.php | 41 +++++++++++++++++++++ forms/gridfield/GridFieldSortableHeader.php | 41 +++++++++++++++++++++ 5 files changed, 146 insertions(+), 11 deletions(-) diff --git a/forms/gridfield/GridField.php b/forms/gridfield/GridField.php index ac1fbbd39..9aef66d79 100755 --- a/forms/gridfield/GridField.php +++ b/forms/gridfield/GridField.php @@ -323,7 +323,7 @@ class GridField extends FormField { foreach($this->components as $item) { if($item instanceof GridField_HTMLProvider) { $fragments = $item->getHTMLFragments($this); - foreach($fragments as $k => $v) { + if($fragments) foreach($fragments as $k => $v) { $k = strtolower($k); if(!isset($content[$k])) $content[$k] = ""; $content[$k] .= $v . "\n"; diff --git a/forms/gridfield/GridFieldConfig.php b/forms/gridfield/GridFieldConfig.php index 76ad37a6b..b6da4213f 100755 --- a/forms/gridfield/GridFieldConfig.php +++ b/forms/gridfield/GridFieldConfig.php @@ -132,10 +132,14 @@ class GridFieldConfig_Base extends GridFieldConfig { */ public function __construct($itemsPerPage=null) { $this->addComponent(new GridFieldToolbarHeader()); - $this->addComponent(new GridFieldSortableHeader()); - $this->addComponent(new GridFieldFilterHeader()); + $this->addComponent($sort = new GridFieldSortableHeader()); + $this->addComponent($filter = new GridFieldFilterHeader()); $this->addComponent(new GridFieldDataColumns()); - $this->addComponent(new GridFieldPaginator($itemsPerPage)); + $this->addComponent($pagination = new GridFieldPaginator($itemsPerPage)); + + $sort->throwExceptionOnBadDataType(false); + $filter->throwExceptionOnBadDataType(false); + $pagination->throwExceptionOnBadDataType(false); } } @@ -159,13 +163,17 @@ class GridFieldConfig_RecordEditor extends GridFieldConfig { */ public function __construct($itemsPerPage=null) { $this->addComponent(new GridFieldToolbarHeader()); - $this->addComponent(new GridFieldSortableHeader()); - $this->addComponent(new GridFieldFilterHeader()); + $this->addComponent($sort = new GridFieldSortableHeader()); + $this->addComponent($filter = new GridFieldFilterHeader()); $this->addComponent(new GridFieldDataColumns()); $this->addComponent(new GridFieldEditButton()); $this->addComponent(new GridFieldDeleteAction()); - $this->addComponent(new GridFieldPaginator($itemsPerPage)); + $this->addComponent($pagination = new GridFieldPaginator($itemsPerPage)); $this->addComponent(new GridFieldDetailForm()); + + $sort->throwExceptionOnBadDataType(false); + $filter->throwExceptionOnBadDataType(false); + $pagination->throwExceptionOnBadDataType(false); } } @@ -202,12 +210,16 @@ class GridFieldConfig_RelationEditor extends GridFieldConfig { public function __construct($itemsPerPage=null) { $this->addComponent(new GridFieldToolbarHeader()); $this->addComponent(new GridFieldAddExistingAutocompleter()); - $this->addComponent(new GridFieldSortableHeader()); - $this->addComponent(new GridFieldFilterHeader()); + $this->addComponent($sort = new GridFieldSortableHeader()); + $this->addComponent($filter = new GridFieldFilterHeader()); $this->addComponent(new GridFieldDataColumns()); $this->addComponent(new GridFieldEditButton()); - $this->addComponent(new GridFieldDeleteAction(true)); - $this->addComponent(new GridFieldPaginator($itemsPerPage)); + $this->addComponent(new GridFieldDeleteAction()); + $this->addComponent($pagination = new GridFieldPaginator($itemsPerPage)); $this->addComponent(new GridFieldDetailForm()); + + $sort->throwExceptionOnBadDataType(false); + $filter->throwExceptionOnBadDataType(false); + $pagination->throwExceptionOnBadDataType(false); } } diff --git a/forms/gridfield/GridFieldFilterHeader.php b/forms/gridfield/GridFieldFilterHeader.php index 4990efe60..b59c38ee6 100644 --- a/forms/gridfield/GridFieldFilterHeader.php +++ b/forms/gridfield/GridFieldFilterHeader.php @@ -8,17 +8,54 @@ * @subpackage fields-relational */ class GridFieldFilterHeader implements GridField_HTMLProvider, GridField_DataManipulator, GridField_ActionProvider { + + /** + * See {@link throwExceptionOnBadDataType()} + */ + protected $throwExceptionOnBadDataType = true; + /** + * Determine what happens when this component is used with a list that isn't {@link SS_Filterable}. + * + * - true: An exception is thrown + * - false: This component will be ignored - it won't make any changes to the GridField. + * + * By default, this is set to true so that it's clearer what's happening, but the predefined + * {@link GridFieldConfig} subclasses set this to false for flexibility. + */ + public function throwExceptionOnBadDataType($throwExceptionOnBadDataType) { + $this->throwExceptionOnBadDataType = $throwExceptionOnBadDataType; + } + + /** + * Check that this dataList is of the right data type. + * Returns false if it's a bad data type, and if appropriate, throws an exception. + */ + protected function checkDataType($dataList) { + if($dataList instanceof SS_Filterable) { + return true; + } else { + if($this->throwExceptionOnBadDataType) { + throw new LogicException(get_class($this) . " expects an SS_Filterable list to be passed to the GridField."); + } + return false; + } + } + /** * * @param GridField $gridField * @return array */ public function getActions($gridField) { + if(!$this->checkDataType($gridField->getList())) return; + return array('filter', 'reset'); } function handleAction(GridField $gridField, $actionName, $arguments, $data) { + if(!$this->checkDataType($gridField->getList())) return; + $state = $gridField->State->GridFieldFilterHeader; if($actionName === 'filter') { if(isset($data['filter'])){ @@ -39,6 +76,8 @@ class GridFieldFilterHeader implements GridField_HTMLProvider, GridField_DataMan * @return SS_List */ public function getManipulatedData(GridField $gridField, SS_List $dataList) { + if(!$this->checkDataType($dataList)) return $dataList; + $state = $gridField->State->GridFieldFilterHeader; if(!isset($state->Columns)) { return $dataList; @@ -54,6 +93,8 @@ class GridFieldFilterHeader implements GridField_HTMLProvider, GridField_DataMan } public function getHTMLFragments($gridField) { + if(!$this->checkDataType($gridField->getList())) return; + $forTemplate = new ArrayData(array()); $forTemplate->Fields = new ArrayList; diff --git a/forms/gridfield/GridFieldPaginator.php b/forms/gridfield/GridFieldPaginator.php index 76629be09..7ee32008d 100755 --- a/forms/gridfield/GridFieldPaginator.php +++ b/forms/gridfield/GridFieldPaginator.php @@ -20,6 +20,11 @@ class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipu */ protected $itemClass = 'GridFieldPaginator_Row'; + /** + * See {@link throwExceptionOnBadDataType()} + */ + protected $throwExceptionOnBadDataType = true; + /** * * @param int $itemsPerPage - How many items should be displayed per page @@ -27,6 +32,34 @@ class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipu public function __construct($itemsPerPage=null) { if($itemsPerPage) $this->itemsPerPage = $itemsPerPage; } + + /** + * Determine what happens when this component is used with a list that isn't {@link SS_Filterable}. + * + * - true: An exception is thrown + * - false: This component will be ignored - it won't make any changes to the GridField. + * + * By default, this is set to true so that it's clearer what's happening, but the predefined + * {@link GridFieldConfig} subclasses set this to false for flexibility. + */ + public function throwExceptionOnBadDataType($throwExceptionOnBadDataType) { + $this->throwExceptionOnBadDataType = $throwExceptionOnBadDataType; + } + + /** + * Check that this dataList is of the right data type. + * Returns false if it's a bad data type, and if appropriate, throws an exception. + */ + protected function checkDataType($dataList) { + if($dataList instanceof SS_Limitable) { + return true; + } else { + if($this->throwExceptionOnBadDataType) { + throw new LogicException(get_class($this) . " expects an SS_Limitable list to be passed to the GridField."); + } + return false; + } + } /** * @@ -34,6 +67,8 @@ class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipu * @return array */ public function getActions($gridField) { + if(!$this->checkDataType($gridField->getList())) return; + return array('paginate'); } @@ -46,6 +81,8 @@ class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipu * @return void */ public function handleAction(GridField $gridField, $actionName, $arguments, $data) { + if(!$this->checkDataType($gridField->getList())) return; + if($actionName !== 'paginate') { return; } @@ -60,6 +97,8 @@ class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipu * @return SS_List */ public function getManipulatedData(GridField $gridField, SS_List $dataList) { + if(!$this->checkDataType($dataList)) return $dataList; + $state = $gridField->State->GridFieldPaginator; if(!is_int($state->currentPage)) $state->currentPage = 1; @@ -80,6 +119,8 @@ class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipu * @return array */ public function getHTMLFragments($gridField) { + if(!$this->checkDataType($gridField->getList())) return; + $state = $gridField->State->GridFieldPaginator; if(!is_int($state->currentPage)) $state->currentPage = 1; diff --git a/forms/gridfield/GridFieldSortableHeader.php b/forms/gridfield/GridFieldSortableHeader.php index 130cadc46..e52138864 100644 --- a/forms/gridfield/GridFieldSortableHeader.php +++ b/forms/gridfield/GridFieldSortableHeader.php @@ -8,11 +8,46 @@ * @subpackage fields-relational */ class GridFieldSortableHeader implements GridField_HTMLProvider, GridField_DataManipulator, GridField_ActionProvider { + + /** + * See {@link throwExceptionOnBadDataType()} + */ + protected $throwExceptionOnBadDataType = true; + + /** + * Determine what happens when this component is used with a list that isn't {@link SS_Filterable}. + * + * - true: An exception is thrown + * - false: This component will be ignored - it won't make any changes to the GridField. + * + * By default, this is set to true so that it's clearer what's happening, but the predefined + * {@link GridFieldConfig} subclasses set this to false for flexibility. + */ + public function throwExceptionOnBadDataType($throwExceptionOnBadDataType) { + $this->throwExceptionOnBadDataType = $throwExceptionOnBadDataType; + } + + /** + * Check that this dataList is of the right data type. + * Returns false if it's a bad data type, and if appropriate, throws an exception. + */ + protected function checkDataType($dataList) { + if($dataList instanceof SS_Sortable) { + return true; + } else { + if($this->throwExceptionOnBadDataType) { + throw new LogicException(get_class($this) . " expects an SS_Sortable list to be passed to the GridField."); + } + return false; + } + } /** * Returns the header row providing titles with sort buttons */ public function getHTMLFragments($gridField) { + if(!$this->checkDataType($gridField->getList())) return; + $forTemplate = new ArrayData(array()); $forTemplate->Fields = new ArrayList; @@ -58,10 +93,14 @@ class GridFieldSortableHeader implements GridField_HTMLProvider, GridField_DataM * @return array */ public function getActions($gridField) { + if(!$this->checkDataType($gridField->getList())) return; + return array('sortasc', 'sortdesc'); } function handleAction(GridField $gridField, $actionName, $arguments, $data) { + if(!$this->checkDataType($gridField->getList())) return; + $state = $gridField->State->GridFieldSortableHeader; switch($actionName) { case 'sortasc': @@ -77,6 +116,8 @@ class GridFieldSortableHeader implements GridField_HTMLProvider, GridField_DataM } public function getManipulatedData(GridField $gridField, SS_List $dataList) { + if(!$this->checkDataType($dataList)) return $dataList; + $state = $gridField->State->GridFieldSortableHeader; if ($state->SortColumn == "") { return $dataList; From 2cb67efe074263253baefa204c77896ad7d9aec7 Mon Sep 17 00:00:00 2001 From: Stig Lindqvist Date: Fri, 9 Mar 2012 15:15:55 +1300 Subject: [PATCH 07/12] API CHANGE DataObject::getComponents() should use default value for limit as NULL --- model/DataList.php | 5 ++++- model/DataObject.php | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/model/DataList.php b/model/DataList.php index 92af851e1..1984af0df 100644 --- a/model/DataList.php +++ b/model/DataList.php @@ -146,7 +146,10 @@ class DataList extends ViewableData implements SS_List, SS_Filterable, SS_Sortab * @param string $limit */ public function limit($limit, $offset = 0) { - if(!is_numeric($limit)) { + if(!$limit && !$offset) { + return $this; + } + if($limit && !is_numeric($limit)) { Deprecation::notice('3.0', 'Please pass limits as 2 arguments, rather than an array or SQL fragment.'); } $this->dataQuery->limit($limit, $offset); diff --git a/model/DataObject.php b/model/DataObject.php index ca972f6b2..f11ae58e2 100644 --- a/model/DataObject.php +++ b/model/DataObject.php @@ -1309,7 +1309,7 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity * * @return HasManyList The components of the one-to-many relationship. */ - public function getComponents($componentName, $filter = "", $sort = "", $join = "", $limit = "") { + public function getComponents($componentName, $filter = "", $sort = "", $join = "", $limit = null) { $result = null; if(!$componentClass = $this->has_many($componentName)) { From 0a82b903bcbb62c2bb5e7ed76e8b3f7461ae6168 Mon Sep 17 00:00:00 2001 From: Stig Lindqvist Date: Fri, 9 Mar 2012 15:28:11 +1300 Subject: [PATCH 08/12] MINOR ListDecorator::limit must exactly match SS_Limitable::limit() --- model/ListDecorator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model/ListDecorator.php b/model/ListDecorator.php index 53da6a0a0..19fb11150 100644 --- a/model/ListDecorator.php +++ b/model/ListDecorator.php @@ -136,7 +136,7 @@ abstract class SS_ListDecorator extends ViewableData implements SS_List, SS_Sort return call_user_func_array(array($this->list, 'filter'), $args); } - public function limit($length, $offset) { + public function limit($limit, $offset = 0) { return $this->list->getRange($length, $offset); } From 34e7e9a05e42e7af712b59ce866c69c58d22501b Mon Sep 17 00:00:00 2001 From: Stig Lindqvist Date: Fri, 9 Mar 2012 15:44:38 +1300 Subject: [PATCH 09/12] MINOR Fix of GridFieldDefaultConfigTest --- tests/forms/GridFieldTest.php | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/forms/GridFieldTest.php b/tests/forms/GridFieldTest.php index 8012554d6..24334b0ed 100644 --- a/tests/forms/GridFieldTest.php +++ b/tests/forms/GridFieldTest.php @@ -30,12 +30,15 @@ class GridFieldTest extends SapphireTest { $expectedComponents = new ArrayList(array( new GridFieldToolbarHeader(), - new GridFieldSortableHeader, - new GridFieldFilterHeader, - new GridFieldDataColumns, - new GridFieldPaginator, - new GridState_Component, + $sort = new GridFieldSortableHeader(), + $filter = new GridFieldFilterHeader(), + new GridFieldDataColumns(), + $pagination = new GridFieldPaginator(), + new GridState_Component(), )); + $sort->throwExceptionOnBadDataType(false); + $filter->throwExceptionOnBadDataType(false); + $pagination->throwExceptionOnBadDataType(false); $this->assertEquals($expectedComponents, $obj->getConfig()->getComponents(), 'Testing default Config'); } From b6803f6141f8372aaa3bd355efb18098fc83cc4f Mon Sep 17 00:00:00 2001 From: Stig Lindqvist Date: Fri, 9 Mar 2012 15:46:45 +1300 Subject: [PATCH 10/12] MINOR Check that component getActions returns an array before using it --- forms/gridfield/GridField.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/forms/gridfield/GridField.php b/forms/gridfield/GridField.php index 9aef66d79..7f9628ebe 100755 --- a/forms/gridfield/GridField.php +++ b/forms/gridfield/GridField.php @@ -650,7 +650,7 @@ class GridField extends FormField { continue; } - if(in_array($actionName, array_map('strtolower', $component->getActions($this)))) { + if(is_array($component->getActions($this)) && in_array($actionName, array_map('strtolower', $component->getActions($this)))) { return $component->handleAction($this, $actionName, $args, $data); } } From 4f1da0a5e8176fcb2caae7612f31961fd1fb127a Mon Sep 17 00:00:00 2001 From: Stig Lindqvist Date: Fri, 9 Mar 2012 16:18:27 +1300 Subject: [PATCH 11/12] MINOR If the limit clause on DataObject::get() passes a SQL type of limit, modify it for SS_List::limit() --- model/DataObject.php | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/model/DataObject.php b/model/DataObject.php index f11ae58e2..c684ea7af 100644 --- a/model/DataObject.php +++ b/model/DataObject.php @@ -2479,11 +2479,17 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity * * @return mixed The objects matching the filter, in the class specified by $containerClass */ - public static function get($callerClass, $filter = "", $sort = "", $join = "", $limit = "", $containerClass = "DataList") { + public static function get($callerClass, $filter = "", $sort = "", $join = "", $limit = null, $containerClass = "DataList") { // Todo: Determine if we can deprecate for 3.0.0 and use DI or something instead // Todo: Make the $containerClass method redundant if($containerClass != "DataList") user_error("The DataObject::get() \$containerClass argument has been deprecated", E_USER_NOTICE); - $result = DataList::create($callerClass)->where($filter)->sort($sort)->limit($limit); + $result = DataList::create($callerClass)->where($filter)->sort($sort); + if($limit && strpos($limit, ',') !== false) { + $limitArguments = explode(',', $limit); + $result->limit($limitArguments[1],$limitArguments[0]); + } elseif($limit) { + $result->limit($limit); + } if($join) $result = $result->join($join); $result->setModel(DataModel::inst()); return $result; From 6d380cd54691d9a18ffaee674e74b1e179cc23f2 Mon Sep 17 00:00:00 2001 From: Stig Lindqvist Date: Fri, 9 Mar 2012 17:06:21 +1300 Subject: [PATCH 12/12] BUGFIX GridFieldPaginator did not work correctly after SS_Limitable was introduced --- forms/gridfield/GridFieldPaginator.php | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/forms/gridfield/GridFieldPaginator.php b/forms/gridfield/GridFieldPaginator.php index 7ee32008d..282d50495 100755 --- a/forms/gridfield/GridFieldPaginator.php +++ b/forms/gridfield/GridFieldPaginator.php @@ -89,6 +89,8 @@ class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipu $state = $gridField->State->GridFieldPaginator; $state->currentPage = (int)$arguments; } + + protected $totalItems = 0; /** * @@ -99,9 +101,12 @@ class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipu public function getManipulatedData(GridField $gridField, SS_List $dataList) { if(!$this->checkDataType($dataList)) return $dataList; + $this->totalItems = $gridField->getList()->count(); + $state = $gridField->State->GridFieldPaginator; - if(!is_int($state->currentPage)) + if(!is_int($state->currentPage)) { $state->currentPage = 1; + } if(!($dataList instanceof SS_Limitable)) { return $dataList; @@ -126,8 +131,7 @@ class GridFieldPaginator implements GridField_HTMLProvider, GridField_DataManipu $state->currentPage = 1; // Figure out which page and record range we're on - $countList = clone $gridField->List; - $totalRows = $countList->limit(null)->count(); + $totalRows = $this->totalItems; if(!$totalRows) return array(); $totalPages = ceil($totalRows/$this->itemsPerPage);