From 135b248bee2ddec6ba776008fbb9838d63982646 Mon Sep 17 00:00:00 2001 From: helpfulrobot Date: Tue, 16 Feb 2016 12:18:40 +1300 Subject: [PATCH 01/23] Added standard code of conduct file --- code-of-conduct.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 code-of-conduct.md diff --git a/code-of-conduct.md b/code-of-conduct.md new file mode 100644 index 0000000..53bf39c --- /dev/null +++ b/code-of-conduct.md @@ -0,0 +1 @@ +When having discussions about this module in issues or pull request please adhere to the [SilverStripe Community Code of Conduct](https://docs.silverstripe.org/en/contributing/code_of_conduct). From 30a4f34fbfebf0f1219b6a2ecff034314c9621ae Mon Sep 17 00:00:00 2001 From: Martin Portevin Date: Sun, 14 Jan 2018 08:44:17 +0100 Subject: [PATCH 02/23] Fix stopPropagation on click if no GridFieldEditableColumns stopPropagation shouldn't fire if no GridFieldEditableColumns was added. It forces user to click on edit button on the far right instead of directly being able to click anywhere on the row. --- javascript/GridFieldExtensions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/GridFieldExtensions.js b/javascript/GridFieldExtensions.js index d961716..2c2f478 100644 --- a/javascript/GridFieldExtensions.js +++ b/javascript/GridFieldExtensions.js @@ -250,7 +250,7 @@ * GridFieldEditableColumns */ - $('.grid-field .ss-gridfield-item').entwine({ + $('.ss-gridfield-editable .ss-gridfield-item').entwine({ onclick: function(e) { // Prevent the default row click action when clicking a cell that contains a field if (this.find('.editable-column-field').length) { From 14c008085358a4fc783eddb72f9cfa93d0619fe2 Mon Sep 17 00:00:00 2001 From: Robbie Averill Date: Thu, 18 Jan 2018 11:45:47 +1300 Subject: [PATCH 03/23] Update branch alias for 3.x-dev --- composer.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/composer.json b/composer.json index 9a7228c..e7bd283 100644 --- a/composer.json +++ b/composer.json @@ -29,6 +29,9 @@ }, "extra": { "installer-name": "gridfieldextensions", + "branch-alias": { + "dev-master": "3.x-dev" + }, "screenshots": [ "docs/en/_images/editable-rows.png", "docs/en/_images/add-existing-search.png" From 6bf1a05975c88f207438819b6752f845b9e27e3c Mon Sep 17 00:00:00 2001 From: Robbie Averill Date: Fri, 26 Jan 2018 12:06:49 +1300 Subject: [PATCH 04/23] Update branch alias for 4.x-dev --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index c43221d..80d7faa 100644 --- a/composer.json +++ b/composer.json @@ -37,7 +37,7 @@ "javascript" ], "branch-alias": { - "3.x-dev": "3.2.x-dev" + "dev-master": "4.x-dev" } }, "replace": { From d1021ace5161f29547a86ec028f84d383e179bd8 Mon Sep 17 00:00:00 2001 From: Priyashantha Date: Wed, 31 Jan 2018 02:10:59 +0530 Subject: [PATCH 05/23] Fixed GridFieldOrderableRows issue when data class is Versioned and relation is has_many (#243) * Fixed GridFieldOrderableRows issue when data class is Versioned and relation is has_many * just compare table names rather than updating existing getSortTable() func --- src/GridFieldOrderableRows.php | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/src/GridFieldOrderableRows.php b/src/GridFieldOrderableRows.php index 91cb6d3..3cd7aa9 100755 --- a/src/GridFieldOrderableRows.php +++ b/src/GridFieldOrderableRows.php @@ -18,8 +18,9 @@ use SilverStripe\ORM\DB; use SilverStripe\ORM\DataList; use SilverStripe\ORM\DataObjectInterface; use SilverStripe\ORM\ManyManyList; +use SilverStripe\ORM\Map; use SilverStripe\ORM\SS_List; -use SilverStripe\ORM\SS_Map; +use SilverStripe\Versioned\Versioned; use SilverStripe\View\ViewableData; use Exception; @@ -180,24 +181,19 @@ class GridFieldOrderableRows extends RequestHandler implements public function getSortTable(SS_List $list) { $field = $this->getSortField(); - if ($list instanceof ManyManyList) { $extra = $list->getExtraFields(); $table = $list->getJoinTable(); - if ($extra && array_key_exists($field, $extra)) { return $table; } } - $classes = ClassInfo::dataClassesFor($list->dataClass()); - foreach ($classes as $class) { if (singleton($class)->hasDataBaseField($field)) { return DataObject::getSchema()->tableName($class); } } - throw new \Exception("Couldn't find the sort field '$field'"); } @@ -499,15 +495,15 @@ class GridFieldOrderableRows extends RequestHandler implements /** @var SS_List $map */ $map = $list->map('ID', $sortField); //fix for versions of SS that return inconsistent types for `map` function - if ($map instanceof SS_Map) { + if ($map instanceof Map) { $map = $map->toArray(); } // If not a ManyManyList and using versioning, detect it. $isVersioned = false; $class = $list->dataClass(); - if ($class == $this->getSortTable($list)) { - $isVersioned = $class::has_extension('SilverStripe\\ORM\\Versioning\\Versioned'); + if (DataObject::getSchema()->tableName($class) == $this->getSortTable($list)) { + $isVersioned = $class::has_extension(Versioned::class); } // Loop through each item, and update the sort values which do not From 9d4c87f5c7ad789074ff44846fd4ae4cf3bf2ccd Mon Sep 17 00:00:00 2001 From: Reece Alexander Date: Sun, 11 Feb 2018 05:48:09 +1000 Subject: [PATCH 06/23] Added installation instruction --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 20a4e06..76e98ef 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,11 @@ This module provides a number of useful grid field components: This branch will aim for compatibility with SilverStripe 4.x. +## Installation +```bash +composer require symbiote/silverstripe-gridfieldextensions "^3" +``` + For SilverStripe 3.x, please see the [compatible branch](https://github.com/symbiote/silverstripe-gridfieldextensions/tree/2). From e7825cd0a34d0738880a8a9baeead3bc6f010599 Mon Sep 17 00:00:00 2001 From: Bernard Hamlin Date: Tue, 27 Feb 2018 16:13:06 +1300 Subject: [PATCH 07/23] Update CMS preview when re-ordering rows --- javascript/GridFieldExtensions.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/javascript/GridFieldExtensions.js b/javascript/GridFieldExtensions.js index d196464..e776941 100644 --- a/javascript/GridFieldExtensions.js +++ b/javascript/GridFieldExtensions.js @@ -325,6 +325,12 @@ self.find('.sortable-header th:last').html(content); } + // update CMS preview + var preview = $('.cms-preview'); + if (preview.length) { + preview.entwine('.ss.preview')._initialiseFromContent(); + } + form.removeClass('loading'); if (successCallback) { successCallback.apply(this, arguments); From 9009a83ae138317a64e2dddb3d3c74e1044dafaa Mon Sep 17 00:00:00 2001 From: Michael Goldsmith Date: Tue, 6 Mar 2018 14:45:29 +1300 Subject: [PATCH 08/23] Revert back to previous version check to allow many many version objects to pass through the non-ORM method --- src/GridFieldOrderableRows.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/GridFieldOrderableRows.php b/src/GridFieldOrderableRows.php index f0aefd8..310dcee 100755 --- a/src/GridFieldOrderableRows.php +++ b/src/GridFieldOrderableRows.php @@ -531,8 +531,12 @@ class GridFieldOrderableRows extends RequestHandler implements // If not a ManyManyList and using versioning, detect it. $this->validateSortField($list); + $isVersioned = false; $class = $list->dataClass(); - $isVersioned = $class::has_extension(Versioned::class); + + if (DataObject::getSchema()->tableName($class) == $this->getSortTable($list)) { + $isVersioned = $class::has_extension(Versioned::class); + } // Loop through each item, and update the sort values which do not // match to order the objects. From e15f1766bf474a78b9dba7ce4a93bfa5b6f9d4e3 Mon Sep 17 00:00:00 2001 From: Nathan SD Date: Tue, 24 Apr 2018 14:25:13 +1200 Subject: [PATCH 09/23] Allow onAfterreorderItems extra variables. --- src/GridFieldOrderableRows.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GridFieldOrderableRows.php b/src/GridFieldOrderableRows.php index 310dcee..bcb4777 100755 --- a/src/GridFieldOrderableRows.php +++ b/src/GridFieldOrderableRows.php @@ -586,7 +586,7 @@ class GridFieldOrderableRows extends RequestHandler implements } } - $this->extend('onAfterReorderItems', $list); + $this->extend('onAfterReorderItems', $list, $values, $sortedIDs); } protected function populateSortValues(DataList $list) From 6b47b6d63ac4398ca01fc5ecd4944f669c8a1e0f Mon Sep 17 00:00:00 2001 From: Mojmir Fendek Date: Thu, 26 Apr 2018 15:17:05 +1200 Subject: [PATCH 10/23] Corrected a case when sort column is located in a table that belongs to ancestor class of the item. --- src/GridFieldOrderableRows.php | 16 +++- tests/GridFieldOrderableRowsTest.php | 88 ++++++++++++++++++++- tests/GridFieldOrderableRowsTest.yml | 27 ++++++- tests/Stub/StubOrderedVersioned.php | 33 ++++++++ tests/Stub/StubParent.php | 3 +- tests/Stub/StubSubclassOrderedVersioned.php | 33 ++++++++ 6 files changed, 193 insertions(+), 7 deletions(-) create mode 100644 tests/Stub/StubOrderedVersioned.php create mode 100644 tests/Stub/StubSubclassOrderedVersioned.php diff --git a/src/GridFieldOrderableRows.php b/src/GridFieldOrderableRows.php index 310dcee..2d25109 100755 --- a/src/GridFieldOrderableRows.php +++ b/src/GridFieldOrderableRows.php @@ -534,8 +534,20 @@ class GridFieldOrderableRows extends RequestHandler implements $isVersioned = false; $class = $list->dataClass(); - if (DataObject::getSchema()->tableName($class) == $this->getSortTable($list)) { - $isVersioned = $class::has_extension(Versioned::class); + // check if sort column is present on the model provided by dataClass() and if it's versioned + // cases: + // Model has sort column and is versioned - handle as versioned + // Model has sort column and is NOT versioned - handle as NOT versioned + // Model doesn't have sort column because sort column is on ManyManyList - handle as NOT versioned + + // try to match table name, note that we have to cover the case where the table which has the sort column + // belongs to ancestor of the object which is populating the list + $classes = ClassInfo::ancestry($class, true); + foreach ($classes as $currentClass) { + if (DataObject::getSchema()->tableName($currentClass) == $this->getSortTable($list)) { + $isVersioned = $class::has_extension(Versioned::class); + break; + } } // Loop through each item, and update the sort values which do not diff --git a/tests/GridFieldOrderableRowsTest.php b/tests/GridFieldOrderableRowsTest.php index d49674d..dc300a1 100644 --- a/tests/GridFieldOrderableRowsTest.php +++ b/tests/GridFieldOrderableRowsTest.php @@ -9,8 +9,10 @@ use SilverStripe\Forms\GridField\GridFieldConfig_RelationEditor; use Symbiote\GridFieldExtensions\GridFieldOrderableRows; use Symbiote\GridFieldExtensions\Tests\Stub\StubOrderableChild; use Symbiote\GridFieldExtensions\Tests\Stub\StubOrdered; +use Symbiote\GridFieldExtensions\Tests\Stub\StubOrderedVersioned; use Symbiote\GridFieldExtensions\Tests\Stub\StubParent; use Symbiote\GridFieldExtensions\Tests\Stub\StubSubclass; +use Symbiote\GridFieldExtensions\Tests\Stub\StubSubclassOrderedVersioned; use Symbiote\GridFieldExtensions\Tests\Stub\StubUnorderable; /** @@ -18,17 +20,22 @@ use Symbiote\GridFieldExtensions\Tests\Stub\StubUnorderable; */ class GridFieldOrderableRowsTest extends SapphireTest { - - protected $usesDatabase = true; - + /** + * @var string + */ protected static $fixture_file = 'GridFieldOrderableRowsTest.yml'; + /** + * @var array + */ protected static $extra_dataobjects = [ StubParent::class, StubOrdered::class, StubSubclass::class, StubUnorderable::class, StubOrderableChild::class, + StubOrderedVersioned::class, + StubSubclassOrderedVersioned::class, ]; public function testReorderItems() @@ -125,5 +132,80 @@ class GridFieldOrderableRowsTest extends SapphireTest 'StubParent_MyManyMany', $orderable->setSortField('ManyManySort')->getSortTable($parent->MyManyMany()) ); + + $this->assertEquals( + 'StubOrderedVersioned', + $orderable->setSortField('Sort')->getSortTable($parent->MyHasManySubclassOrderedVersioned()) + ); + } + + public function testReorderItemsSubclassVersioned() + { + $orderable = new GridFieldOrderableRows('Sort'); + $reflection = new ReflectionMethod($orderable, 'executeReorder'); + $reflection->setAccessible(true); + + $parent = $this->objFromFixture(StubParent::class, 'parent-subclass-ordered-versioned'); + + // make sure all items are published + foreach ($parent->MyHasManySubclassOrderedVersioned() as $item) { + $item->publishRecursive(); + } + + // there should be no difference between stages at this point + $differenceFound = false; + foreach ($parent->MyHasManySubclassOrderedVersioned() as $item) { + /** @var StubSubclassOrderedVersioned|Versioned $item */ + if ($item->stagesDiffer()) { + $differenceFound = true; + break; + } + } + + $this->assertFalse($differenceFound); + + // reorder items + $config = new GridFieldConfig_RelationEditor(); + $config->addComponent($orderable); + + $grid = new GridField( + 'TestField', + 'TestField', + $parent->MyHasManySubclassOrderedVersioned()->sort('Sort', 'ASC'), + $config + ); + + $originalOrder = $parent->MyHasManySubclassOrderedVersioned() + ->sort('Sort', 'ASC') + ->column('ID'); + + $desiredOrder = []; + + // Make order non-contiguous, and 1-based + foreach (array_reverse($originalOrder) as $index => $id) { + $desiredOrder[$index * 2 + 1] = $id; + } + + $this->assertNotEquals($originalOrder, $desiredOrder); + + $reflection->invoke($orderable, $grid, $desiredOrder); + + $newOrder = $parent->MyHasManySubclassOrderedVersioned() + ->sort('Sort', 'ASC') + ->map('Sort', 'ID') + ->toArray(); + + $this->assertEquals($desiredOrder, $newOrder); + + // reorder should have been handled as versioned - there should be a difference between stages now + $differenceFound = false; + foreach ($parent->MyHasManySubclassOrderedVersioned() as $item) { + if ($item->stagesDiffer()) { + $differenceFound = true; + break; + } + } + + $this->assertTrue($differenceFound); } } diff --git a/tests/GridFieldOrderableRowsTest.yml b/tests/GridFieldOrderableRowsTest.yml index 20d5c34..8efc5ec 100644 --- a/tests/GridFieldOrderableRowsTest.yml +++ b/tests/GridFieldOrderableRowsTest.yml @@ -21,7 +21,26 @@ Symbiote\GridFieldExtensions\Tests\Stub\StubOrdered: item6: Sort: 6 nestedtest: - Children: =>Symbiote\GridFieldExtensions\Tests\Stub\StubOrderableChild.item1,=>Symbiote\GridFieldExtensions\Tests\Stub\StubOrderableChild.item2,=>Symbiote\GridFieldExtensions\Tests\Stub\StubOrderableChild.item3,=>Symbiote\GridFieldExtensions\Tests\Stub\StubOrderableChild.item4 + Children: + - =>Symbiote\GridFieldExtensions\Tests\Stub\StubOrderableChild.item1 + - =>Symbiote\GridFieldExtensions\Tests\Stub\StubOrderableChild.item2 + - =>Symbiote\GridFieldExtensions\Tests\Stub\StubOrderableChild.item3 + - =>Symbiote\GridFieldExtensions\Tests\Stub\StubOrderableChild.item4 + +Symbiote\GridFieldExtensions\Tests\Stub\StubSubclassOrderedVersioned: + item1: + ExtraField: 1 + Sort: 1 + item2: + ExtraField: 2 + Sort: 2 + item3: + ExtraField: 3 + Sort: 3 + item4: + ExtraField: 4 + Sort: 4 + Symbiote\GridFieldExtensions\Tests\Stub\StubParent: parent: MyManyMany: @@ -37,3 +56,9 @@ Symbiote\GridFieldExtensions\Tests\Stub\StubParent: ManyManySort: 108 - =>Symbiote\GridFieldExtensions\Tests\Stub\StubOrdered.item6: ManyManySort: 108 + parent-subclass-ordered-versioned: + MyHasManySubclassOrderedVersioned: + - =>Symbiote\GridFieldExtensions\Tests\Stub\StubSubclassOrderedVersioned.item1 + - =>Symbiote\GridFieldExtensions\Tests\Stub\StubSubclassOrderedVersioned.item2 + - =>Symbiote\GridFieldExtensions\Tests\Stub\StubSubclassOrderedVersioned.item3 + - =>Symbiote\GridFieldExtensions\Tests\Stub\StubSubclassOrderedVersioned.item4 diff --git a/tests/Stub/StubOrderedVersioned.php b/tests/Stub/StubOrderedVersioned.php new file mode 100644 index 0000000..83bc009 --- /dev/null +++ b/tests/Stub/StubOrderedVersioned.php @@ -0,0 +1,33 @@ + 'Int', + ]; +} diff --git a/tests/Stub/StubParent.php b/tests/Stub/StubParent.php index 7d60a11..4193c1c 100644 --- a/tests/Stub/StubParent.php +++ b/tests/Stub/StubParent.php @@ -9,7 +9,8 @@ class StubParent extends DataObject implements TestOnly { private static $has_many = array( 'MyHasMany' => StubOrdered::class, - 'MyHasManySubclass' => StubSubclass::class + 'MyHasManySubclass' => StubSubclass::class, + 'MyHasManySubclassOrderedVersioned' => StubSubclassOrderedVersioned::class, ); private static $many_many = array( diff --git a/tests/Stub/StubSubclassOrderedVersioned.php b/tests/Stub/StubSubclassOrderedVersioned.php new file mode 100644 index 0000000..e0e112d --- /dev/null +++ b/tests/Stub/StubSubclassOrderedVersioned.php @@ -0,0 +1,33 @@ + 'Int', + ]; + + /** + * @var array + */ + private static $has_one = [ + 'Parent' => StubParent::class, + ]; +} From 9fa9ef8903852f35a701737caca76615c5be3b21 Mon Sep 17 00:00:00 2001 From: Dylan Wagstaff Date: Fri, 4 May 2018 17:31:00 +1200 Subject: [PATCH 11/23] NEW Add support for ManyManyThrough relations Previously relationships defiend as many_many came in a special type of RelationList - however now this can be one of two types of RelationList depending on the type of definition, with both being valid many_many relationships. This had the unfortunate side effect of seeing the OrderableRows component in (the least) cease functioning correctly. No longer. This also has the fortunate bonus of allowing a many_many relationship to be versioned; where previously while each item in the relationship could be versioned, the relationship itself could not. --- src/GridFieldOrderableRows.php | 177 +++++++++++++++++++++++++-------- 1 file changed, 133 insertions(+), 44 deletions(-) diff --git a/src/GridFieldOrderableRows.php b/src/GridFieldOrderableRows.php index 2d25109..dc92ee3 100755 --- a/src/GridFieldOrderableRows.php +++ b/src/GridFieldOrderableRows.php @@ -2,6 +2,7 @@ namespace Symbiote\GridFieldExtensions; +use Exception; use SilverStripe\Control\Controller; use SilverStripe\Control\RequestHandler; use SilverStripe\Core\ClassInfo; @@ -17,9 +18,11 @@ use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\DataList; use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObjectInterface; +use SilverStripe\ORM\DataObjectSchema; use SilverStripe\ORM\DB; use SilverStripe\ORM\ManyManyList; -use SilverStripe\ORM\Map; +use SilverStripe\ORM\ManyManyThroughList; +use SilverStripe\ORM\ManyManyThroughQueryManipulator; use SilverStripe\ORM\SS_List; use SilverStripe\ORM\FieldType\DBDatetime; use SilverStripe\Versioned\Versioned; @@ -141,6 +144,16 @@ class GridFieldOrderableRows extends RequestHandler implements return $this->extraSortFields; } + /** + * Checks to see if the relationship list is for a type of many_many + * + * @param SS_List $list + */ + protected function isManyMany(SS_List $list) + { + return $list instanceof ManyManyList || $list instanceof ManyManyThroughList; + } + /** * Sets extra sort fields to apply before the sort field. * @@ -183,12 +196,19 @@ class GridFieldOrderableRows extends RequestHandler implements { $field = $this->getSortField(); + // Check extra fields on many many relation types if ($list instanceof ManyManyList) { $extra = $list->getExtraFields(); if ($extra && array_key_exists($field, $extra)) { return; } + } elseif ($list instanceof ManyManyThroughList) { + $manipulator = $this->getManyManyInspector($list); + $fieldTable = DataObject::getSchema()->tableForField($manipulator->getJoinClass(), $field); + if ($fieldTable) { + return; + } } $classes = ClassInfo::dataClassesFor($list->dataClass()); @@ -199,7 +219,7 @@ class GridFieldOrderableRows extends RequestHandler implements } } - throw new \Exception("Couldn't find the sort field '" . $field . "'"); + throw new Exception("Couldn't find the sort field '" . $field . "'"); } /** @@ -217,6 +237,8 @@ class GridFieldOrderableRows extends RequestHandler implements if ($extra && array_key_exists($field, $extra)) { return $table; } + } elseif ($list instanceof ManyManyThroughList) { + return $this->getManyManyInspector($list)->getJoinAlias(); } $classes = ClassInfo::dataClassesFor($list->dataClass()); foreach ($classes as $class) { @@ -224,7 +246,7 @@ class GridFieldOrderableRows extends RequestHandler implements return DataObject::getSchema()->tableName($class); } } - throw new \Exception("Couldn't find the sort field '$field'"); + throw new Exception("Couldn't find the sort field '$field'"); } public function getURLHandlers($grid) @@ -340,9 +362,10 @@ class GridFieldOrderableRows extends RequestHandler implements } $list = $grid->getList(); $modelClass = $grid->getModelClass(); - if ($list instanceof ManyManyList && !singleton($modelClass)->canView()) { + $isManyMany = $this->isManyMany($list); + if ($isManyMany && !singleton($modelClass)->canView()) { $this->httpError(403); - } elseif (!($list instanceof ManyManyList) && !singleton($modelClass)->canEdit()) { + } elseif (!$isManyMany && !singleton($modelClass)->canEdit()) { $this->httpError(403); } @@ -364,10 +387,10 @@ class GridFieldOrderableRows extends RequestHandler implements } /** - * Get mapping of sort value to ID from posted data + * Get mapping of sort value to item ID from posted data (gridfield list state), ordered by sort value. * * @param array $data Raw posted data - * @return array + * @return array [sortIndex => recordID] */ protected function getSortedIDs($data) { @@ -473,7 +496,7 @@ class GridFieldOrderableRows extends RequestHandler implements if (!is_array($sortedIDs)) { return false; } - $field = $this->getSortField(); + $sortField = $this->getSortField(); $sortterm = ''; if ($this->extraSortFields) { @@ -486,7 +509,7 @@ class GridFieldOrderableRows extends RequestHandler implements } } $list = $grid->getList(); - $sortterm .= '"'.$this->getSortTable($list).'"."'.$field.'"'; + $sortterm .= '"'.$this->getSortTable($list).'"."'.$sortField.'"'; $items = $list->filter('ID', $sortedIDs)->sort($sortterm); // Ensure that each provided ID corresponded to an actual object. @@ -507,11 +530,21 @@ class GridFieldOrderableRows extends RequestHandler implements if (isset($record->_SortColumn0)) { $current[$record->ID] = $record->_SortColumn0; } else { - $current[$record->ID] = $record->$field; + $current[$record->ID] = $record->$sortField; } } + } elseif ($items instanceof ManyManyThroughList) { + $manipulator = $this->getManyManyInspector($list); + $joinClass = $manipulator->getJoinClass(); + $fromRelationName = $manipulator->getForeignKey(); + $toRelationName = $manipulator->getLocalKey(); + $sortlist = DataList::create($joinClass)->filter([ + $toRelationName => $items->column('ID'), + $fromRelationName => $items->first()->getJoin()->$fromRelationName, + ]); + $current = $sortlist->map($toRelationName, $sortField)->toArray(); } else { - $current = $items->map('ID', $field)->toArray(); + $current = $items->map('ID', $sortField)->toArray(); } // Perform the actual re-ordering. @@ -519,35 +552,51 @@ class GridFieldOrderableRows extends RequestHandler implements return true; } + /** + * @param SS_List $list + * @param array $values **UNUSED** [listItemID => currentSortValue]; + * @param array $sortedIDs [newSortValue => listItemID] + */ protected function reorderItems($list, array $values, array $sortedIDs) { + // setup $sortField = $this->getSortField(); - /** @var SS_List $map */ - $map = $list->map('ID', $sortField); - //fix for versions of SS that return inconsistent types for `map` function - if ($map instanceof Map) { - $map = $map->toArray(); - } - - // If not a ManyManyList and using versioning, detect it. - $this->validateSortField($list); - $isVersioned = false; $class = $list->dataClass(); + // The problem is that $sortedIDs is a list of the _related_ item IDs, which causes trouble + // with ManyManyThrough, where we need the ID of the _join_ item in order to set the value. + $itemToSortReference = ($list instanceof ManyManyThroughList) ? 'getJoin' : 'Me'; + $currentSortList = $list->map('ID', $itemToSortReference)->toArray(); + // sanity check. + $this->validateSortField($list); + + $isVersioned = false; // check if sort column is present on the model provided by dataClass() and if it's versioned // cases: // Model has sort column and is versioned - handle as versioned // Model has sort column and is NOT versioned - handle as NOT versioned // Model doesn't have sort column because sort column is on ManyManyList - handle as NOT versioned + // Model doesn't have sort column because sort column is on ManyManyThroughList... + // - Related item is not versioned: + // - Through object is versioned: THROW an error. + // - Through object is NOT versioned: handle as NOT versioned + // - Related item is versioned... + // - Through object is versioned: handle as versioned + // - Through object is NOT versioned: THROW an error. + $isManyMany = $this->isManyMany($list); + if ($isManyMany && $list instanceof ManyManyThroughList) { + $listClassVersioned = $class::create()->hasExtension(Versioned::class); + // We'll be updating the join class, not the relation class. + $class = $this->getManyManyInspector($list)->getJoinClass(); + $isVersioned = $class::create()->hasExtension(Versioned::class); - // try to match table name, note that we have to cover the case where the table which has the sort column - // belongs to ancestor of the object which is populating the list - $classes = ClassInfo::ancestry($class, true); - foreach ($classes as $currentClass) { - if (DataObject::getSchema()->tableName($currentClass) == $this->getSortTable($list)) { - $isVersioned = $class::has_extension(Versioned::class); - break; + if ($listClassVersioned xor $isVersioned) { + throw new Exception( + 'ManyManyThrough cannot mismatch Versioning between join class and related class' + ); } + } elseif (!$isManyMany) { + $isVersioned = $class::create()->hasExtension(Versioned::class); } // Loop through each item, and update the sort values which do not @@ -556,22 +605,22 @@ class GridFieldOrderableRows extends RequestHandler implements $sortTable = $this->getSortTable($list); $now = DBDatetime::now()->Rfc2822(); $additionalSQL = ''; - $baseTable = DataObject::getSchema()->baseDataTable($list->dataClass()); + $baseTable = DataObject::getSchema()->baseDataTable($class); $isBaseTable = ($baseTable == $sortTable); if (!$list instanceof ManyManyList && $isBaseTable) { $additionalSQL = ", \"LastEdited\" = '$now'"; } - foreach ($sortedIDs as $sortValue => $id) { - if ($map[$id] != $sortValue) { + foreach ($sortedIDs as $newSortValue => $targetRecordID) { + if ($currentSortList[$targetRecordID]->$sortField != $newSortValue) { DB::query(sprintf( 'UPDATE "%s" SET "%s" = %d%s WHERE %s', $sortTable, $sortField, - $sortValue, + $newSortValue, $additionalSQL, - $this->getSortTableClauseForIds($list, $id) + $this->getSortTableClauseForIds($list, $targetRecordID) )); if (!$isBaseTable && !$list instanceof ManyManyList) { @@ -579,20 +628,22 @@ class GridFieldOrderableRows extends RequestHandler implements 'UPDATE "%s" SET "LastEdited" = \'%s\' WHERE %s', $baseTable, $now, - $this->getSortTableClauseForIds($list, $id) + $this->getSortTableClauseForIds($list, $targetRecordID) )); } } } } else { // For versioned objects, modify them with the ORM so that the - // *_versions table is updated. This ensures re-ordering works + // *_Versions table is updated. This ensures re-ordering works // similar to the SiteTree where you change the position, and then // you go into the record and publish it. - foreach ($sortedIDs as $sortValue => $id) { - if ($map[$id] != $sortValue) { - $record = $class::get()->byID($id); - $record->$sortField = $sortValue; + foreach ($sortedIDs as $newSortValue => $targetRecordID) { + // either the list data class (has_many, (belongs_)many_many) + // or the intermediary join class (many_many through) + $record = $currentSortList[$targetRecordID]; + if ($record->$sortField != $newSortValue) { + $record->$sortField = $newSortValue; $record->write(); } } @@ -629,7 +680,7 @@ class GridFieldOrderableRows extends RequestHandler implements $this->getSortTableClauseForIds($list, $id) )); - if (!$isBaseTable && !$list instanceof ManyManyList) { + if (!$isBaseTable && !$this->isManyMany($list)) { DB::query(sprintf( 'UPDATE "%s" SET "LastEdited" = \'%s\' WHERE %s', $baseTable, @@ -640,6 +691,18 @@ class GridFieldOrderableRows extends RequestHandler implements } } + /** + * Forms a WHERE clause for the table the sort column is defined on. + * e.g. ID = 5 + * e.g. ID IN(5, 8, 10) + * e.g. SortOrder = 5 AND RelatedThing.ID = 3 + * e.g. SortOrder IN(5, 8, 10) AND RelatedThing.ID = 3 + * + * @param DataList $list + * @param int|string|array $ids a single number, or array of numbers + * + * @return string + */ protected function getSortTableClauseForIds(DataList $list, $ids) { if (is_array($ids)) { @@ -648,10 +711,13 @@ class GridFieldOrderableRows extends RequestHandler implements $value = '= ' . (int) $ids; } - if ($list instanceof ManyManyList) { - $extra = $list->getExtraFields(); - $key = $list->getLocalKey(); - $foreignKey = $list->getForeignKey(); + if ($this->isManyMany($list)) { + $intropector = $this->getManyManyInspector($list); + $extra = $list instanceof ManyManyList ? + $intropector->getExtraFields() : + DataObjectSchema::create()->fieldSpecs($intropector->getJoinClass(), DataObjectSchema::DB_ONLY); + $key = $intropector->getLocalKey(); + $foreignKey = $intropector->getForeignKey(); $foreignID = (int) $list->getForeignID(); if ($extra && array_key_exists($this->getSortField(), $extra)) { @@ -667,4 +733,27 @@ class GridFieldOrderableRows extends RequestHandler implements return "\"ID\" $value"; } + + /** + * A ManyManyList defines functions such as getLocalKey, however on ManyManyThroughList + * these functions are moved to ManyManyThroughQueryManipulator, but otherwise retain + * the same signature. + * + * @param ManyManyList|ManyManyThroughList + * + * @return ManyManyList|ManyManyThroughQueryManipulator + */ + protected function getManyManyInspector($list) + { + $inspector = $list; + if ($list instanceof ManyManyThroughList) { + foreach ($list->dataQuery()->getDataQueryManipulators() as $manipulator) { + if ($manipulator instanceof ManyManyThroughQueryManipulator) { + $inspector = $manipulator; + break; + } + } + } + return $inspector; + } } From 939415a444a2bceb0ee974dbc881fc23a3faf197 Mon Sep 17 00:00:00 2001 From: Dylan Wagstaff Date: Fri, 15 Jun 2018 17:53:40 +1200 Subject: [PATCH 12/23] Add supported module badge to readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 76e98ef..b93284f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # SilverStripe Grid Field Extensions Module [![Build Status](https://travis-ci.org/symbiote/silverstripe-gridfieldextensions.svg?branch=master)](https://travis-ci.org/symbiote/silverstripe-gridfieldextensions) +[![SilverStripe supported module](https://img.shields.io/badge/silverstripe-supported-0071C4.svg)](https://www.silverstripe.org/software/addons/silverstripe-commercially-supported-module-list/) [![Latest Stable Version](https://poser.pugx.org/symbiote/silverstripe-gridfieldextensions/version.svg)](https://github.com/symbiote/silverstripe-gridfieldextensions/releases) [![Latest Unstable Version](https://poser.pugx.org/symbiote/silverstripe-gridfieldextensions/v/unstable.svg)](https://packagist.org/packages/symbiote/silverstripe-gridfieldextensions) [![Total Downloads](https://poser.pugx.org/symbiote/silverstripe-gridfieldextensions/downloads.svg)](https://packagist.org/packages/symbiote/silverstripe-gridfieldextensions) From b6130c4e11a492bfc28aca403c4882efe32fb24e Mon Sep 17 00:00:00 2001 From: Dylan Wagstaff Date: Mon, 25 Jun 2018 12:22:27 +1200 Subject: [PATCH 13/23] Add unit tests for new ManyManyThrough support The previous commit (9fa9ef89) added support for the new SilverStripe 4 feature of Many Many relationships through an intermediary object. After much head scratching and community testing, the solution was proven to work, however had no automated tests to confirm as such. This commit rectifies that by testing both versioned and unversioned DataObjects in a many_many through style relationship. Some minor tidy and comments were also added as per feedback on the functionality code changes. --- src/GridFieldOrderableRows.php | 23 ++-- tests/GridFieldOrderableRowsTest.php | 43 +++++-- tests/OrderableRowsThroughTest.yml | 30 +++++ tests/OrderableRowsThroughVersionedTest.php | 126 ++++++++++++++++++++ tests/Stub/StubParent.php | 16 +-- tests/Stub/ThroughBelongs.php | 15 +++ tests/Stub/ThroughDefiner.php | 23 ++++ tests/Stub/ThroughIntermediary.php | 21 ++++ 8 files changed, 268 insertions(+), 29 deletions(-) create mode 100644 tests/OrderableRowsThroughTest.yml create mode 100644 tests/OrderableRowsThroughVersionedTest.php create mode 100644 tests/Stub/ThroughBelongs.php create mode 100644 tests/Stub/ThroughDefiner.php create mode 100644 tests/Stub/ThroughIntermediary.php diff --git a/src/GridFieldOrderableRows.php b/src/GridFieldOrderableRows.php index dc92ee3..492d96f 100755 --- a/src/GridFieldOrderableRows.php +++ b/src/GridFieldOrderableRows.php @@ -148,6 +148,8 @@ class GridFieldOrderableRows extends RequestHandler implements * Checks to see if the relationship list is for a type of many_many * * @param SS_List $list + * + * @return bool */ protected function isManyMany(SS_List $list) { @@ -493,7 +495,7 @@ class GridFieldOrderableRows extends RequestHandler implements */ protected function executeReorder(GridField $grid, $sortedIDs) { - if (!is_array($sortedIDs)) { + if (!is_array($sortedIDs) || empty($sortedIDs)) { return false; } $sortField = $this->getSortField(); @@ -540,6 +542,7 @@ class GridFieldOrderableRows extends RequestHandler implements $toRelationName = $manipulator->getLocalKey(); $sortlist = DataList::create($joinClass)->filter([ $toRelationName => $items->column('ID'), + // first() is safe as there are earlier checks to ensure our list to sort is valid $fromRelationName => $items->first()->getJoin()->$fromRelationName, ]); $current = $sortlist->map($toRelationName, $sortField)->toArray(); @@ -583,19 +586,19 @@ class GridFieldOrderableRows extends RequestHandler implements // - Related item is versioned... // - Through object is versioned: handle as versioned // - Through object is NOT versioned: THROW an error. - $isManyMany = $this->isManyMany($list); - if ($isManyMany && $list instanceof ManyManyThroughList) { + if ($list instanceof ManyManyThroughList) { $listClassVersioned = $class::create()->hasExtension(Versioned::class); // We'll be updating the join class, not the relation class. $class = $this->getManyManyInspector($list)->getJoinClass(); $isVersioned = $class::create()->hasExtension(Versioned::class); + // If one side of the relationship is versioned and the other is not, throw an error. if ($listClassVersioned xor $isVersioned) { throw new Exception( 'ManyManyThrough cannot mismatch Versioning between join class and related class' ); } - } elseif (!$isManyMany) { + } elseif (!$this->isManyMany($list)) { $isVersioned = $class::create()->hasExtension(Versioned::class); } @@ -712,12 +715,12 @@ class GridFieldOrderableRows extends RequestHandler implements } if ($this->isManyMany($list)) { - $intropector = $this->getManyManyInspector($list); + $introspector = $this->getManyManyInspector($list); $extra = $list instanceof ManyManyList ? - $intropector->getExtraFields() : - DataObjectSchema::create()->fieldSpecs($intropector->getJoinClass(), DataObjectSchema::DB_ONLY); - $key = $intropector->getLocalKey(); - $foreignKey = $intropector->getForeignKey(); + $introspector->getExtraFields() : + DataObjectSchema::create()->fieldSpecs($introspector->getJoinClass(), DataObjectSchema::DB_ONLY); + $key = $introspector->getLocalKey(); + $foreignKey = $introspector->getForeignKey(); $foreignID = (int) $list->getForeignID(); if ($extra && array_key_exists($this->getSortField(), $extra)) { @@ -739,7 +742,7 @@ class GridFieldOrderableRows extends RequestHandler implements * these functions are moved to ManyManyThroughQueryManipulator, but otherwise retain * the same signature. * - * @param ManyManyList|ManyManyThroughList + * @param ManyManyList|ManyManyThroughList $list * * @return ManyManyList|ManyManyThroughQueryManipulator */ diff --git a/tests/GridFieldOrderableRowsTest.php b/tests/GridFieldOrderableRowsTest.php index 487d601..024e791 100644 --- a/tests/GridFieldOrderableRowsTest.php +++ b/tests/GridFieldOrderableRowsTest.php @@ -14,6 +14,9 @@ use Symbiote\GridFieldExtensions\Tests\Stub\StubParent; use Symbiote\GridFieldExtensions\Tests\Stub\StubSubclass; use Symbiote\GridFieldExtensions\Tests\Stub\StubSubclassOrderedVersioned; use Symbiote\GridFieldExtensions\Tests\Stub\StubUnorderable; +use Symbiote\GridFieldExtensions\Tests\Stub\ThroughDefiner; +use Symbiote\GridFieldExtensions\Tests\Stub\ThroughIntermediary; +use Symbiote\GridFieldExtensions\Tests\Stub\ThroughBelongs; /** * Tests for the {@link GridFieldOrderableRows} component. @@ -23,7 +26,10 @@ class GridFieldOrderableRowsTest extends SapphireTest /** * @var string */ - protected static $fixture_file = 'GridFieldOrderableRowsTest.yml'; + protected static $fixture_file = [ + 'GridFieldOrderableRowsTest.yml', + 'OrderableRowsThroughTest.yml' + ]; /** * @var array @@ -36,27 +42,42 @@ class GridFieldOrderableRowsTest extends SapphireTest StubOrderableChild::class, StubOrderedVersioned::class, StubSubclassOrderedVersioned::class, + ThroughDefiner::class, + ThroughIntermediary::class, + ThroughBelongs::class, ]; - public function testReorderItems() + public function reorderItemsProvider() { - $orderable = new GridFieldOrderableRows('ManyManySort'); + return [ + [StubParent::class . '.parent', 'MyManyMany', 'ManyManySort'], + [ThroughDefiner::class . '.DefinerOne', 'Belongings', 'Sort'], + ]; + } + + /** + * @dataProvider reorderItemsProvider + */ + public function testReorderItems($fixtureID, $relationName, $sortName) + { + $orderable = new GridFieldOrderableRows($sortName); $reflection = new ReflectionMethod($orderable, 'executeReorder'); $reflection->setAccessible(true); - $parent = $this->objFromFixture(StubParent::class, 'parent'); - $config = new GridFieldConfig_RelationEditor(); $config->addComponent($orderable); + list($parentClass, $parentInstanceID) = explode('.', $fixtureID); + $parent = $this->objFromFixture($parentClass, $parentInstanceID); + $grid = new GridField( - 'MyManyMany', - 'My Many Many', - $parent->MyManyMany()->sort('ManyManySort'), + $relationName, + 'Testing Many Many', + $parent->$relationName()->sort($sortName), $config ); - $originalOrder = $parent->MyManyMany()->sort('ManyManySort')->column('ID'); + $originalOrder = $parent->$relationName()->sort($sortName)->column('ID'); $desiredOrder = []; // Make order non-contiguous, and 1-based @@ -68,7 +89,7 @@ class GridFieldOrderableRowsTest extends SapphireTest $reflection->invoke($orderable, $grid, $desiredOrder); - $newOrder = $parent->MyManyMany()->sort('ManyManySort')->map('ManyManySort', 'ID')->toArray(); + $newOrder = $parent->$relationName()->sort($sortName)->map($sortName, 'ID')->toArray(); $this->assertEquals($desiredOrder, $newOrder); } @@ -157,7 +178,7 @@ class GridFieldOrderableRowsTest extends SapphireTest foreach ($parent->MyHasManySubclassOrderedVersioned() as $item) { /** @var StubSubclassOrderedVersioned|Versioned $item */ if ($item->stagesDiffer()) { - $this->fail('Unexpected diference found on stages'); + $this->fail('Unexpected difference found on stages'); } } diff --git a/tests/OrderableRowsThroughTest.yml b/tests/OrderableRowsThroughTest.yml new file mode 100644 index 0000000..a0e11ea --- /dev/null +++ b/tests/OrderableRowsThroughTest.yml @@ -0,0 +1,30 @@ +Symbiote\GridFieldExtensions\Tests\Stub\ThroughDefiner: + DefinerOne: + DefinerTwo: + +Symbiote\GridFieldExtensions\Tests\Stub\ThroughBelongs: + BelongsOne: + BelongsTwo: + BelongsThree: + +Symbiote\GridFieldExtensions\Tests\Stub\ThroughIntermediary: + One: + Defining: =>Symbiote\GridFieldExtensions\Tests\Stub\ThroughDefiner.DefinerOne + Belonging: =>Symbiote\GridFieldExtensions\Tests\Stub\ThroughBelongs.BelongsOne + Sort: 3 + Two: + Defining: =>Symbiote\GridFieldExtensions\Tests\Stub\ThroughDefiner.DefinerOne + Belonging: =>Symbiote\GridFieldExtensions\Tests\Stub\ThroughBelongs.BelongsTwo + Sort: 2 + Three: + Defining: =>Symbiote\GridFieldExtensions\Tests\Stub\ThroughDefiner.DefinerOne + Belonging: =>Symbiote\GridFieldExtensions\Tests\Stub\ThroughBelongs.BelongsThree + Sort: 1 + Four: + Defining: =>Symbiote\GridFieldExtensions\Tests\Stub\ThroughDefiner.DefinerTwo + Belonging: =>Symbiote\GridFieldExtensions\Tests\Stub\ThroughBelongs.BelongsTwo + Sort: 1 + Five: + Defining: =>Symbiote\GridFieldExtensions\Tests\Stub\ThroughDefiner.DefinerTwo + Belonging: =>Symbiote\GridFieldExtensions\Tests\Stub\ThroughBelongs.BelongsThree + Sort: 2 diff --git a/tests/OrderableRowsThroughVersionedTest.php b/tests/OrderableRowsThroughVersionedTest.php new file mode 100644 index 0000000..694da01 --- /dev/null +++ b/tests/OrderableRowsThroughVersionedTest.php @@ -0,0 +1,126 @@ + [Versioned::class], + ThroughIntermediary::class => [Versioned::class], + ThroughBelongs::class => [Versioned::class], + ]; + + protected $originalReadingMode; + + protected function setUp() + { + parent::setUp(); + $this->orignalReadingMode = Versioned::get_reading_mode(); + } + + protected function tearDown() + { + Versioned::set_reading_mode($this->originalReadingMode); + unset($this->originalReadingMode); + parent::tearDown(); + } + + /** + * Basically the same as GridFieldOrderableRowsTest::testReorderItems + * but with some Versioned calls & checks mixed in. + */ + public function testReorderingSavesAndPublishes() + { + $parent = $this->objFromFixture(ThroughDefiner::class, 'DefinerOne'); + $relationName = 'Belongings'; + $sortName = 'Sort'; + + $orderable = new GridFieldOrderableRows($sortName); + $reflection = new ReflectionMethod($orderable, 'executeReorder'); + $reflection->setAccessible(true); + + $config = new GridFieldConfig_RelationEditor(); + $config->addComponent($orderable); + + // This test data is versioned - ensure we're all published + $parent->publishRecursive(); + // there should be no difference between stages at this point + foreach ($parent->$relationName() as $item) { + $this->assertFalse( + $item->getJoin()->stagesDiffer(), + 'No records should be different from their published versions' + ); + } + + $grid = new GridField( + 'Belongings', + 'Testing Many Many', + $parent->$relationName()->sort($sortName), + $config + ); + + $originalOrder = $parent->$relationName()->sort($sortName)->column('ID'); + // Ring (un)shift by one, e.g. 3,2,1 becomes 1,3,2. + // then string key our new order starting at 1 + $desiredOrder = array_values($originalOrder); + array_unshift($desiredOrder, array_pop($desiredOrder)); + $desiredOrder = array_combine( + range('1', count($desiredOrder)), + $desiredOrder + ); + $this->assertNotEquals($originalOrder, $desiredOrder); + + // Perform the reorder + $reflection->invoke($orderable, $grid, $desiredOrder); + + // Verify draft stage has reordered + Versioned::set_stage(Versioned::DRAFT); + $newOrder = $parent->$relationName()->sort($sortName)->map($sortName, 'ID')->toArray(); + $this->assertEquals($desiredOrder, $newOrder); + + // reorder should have been handled as versioned - there should be a difference between stages now + // by using a ring style shift every item should have a new sort (thus a new version). + $differenceFound = false; + foreach ($parent->$relationName() as $item) { + if ($item->getJoin()->stagesDiffer()) { + $differenceFound = true; + } + } + $this->assertTrue($differenceFound, 'All records should have changes in draft'); + + // Verify live stage has NOT reordered + Versioned::set_stage(Versioned::LIVE); + $sameOrder = $parent->$relationName()->sort($sortName)->column('ID'); + $this->assertEquals($originalOrder, $sameOrder); + + $parent->publishRecursive(); + + foreach ($parent->$relationName() as $item) { + $this->assertFalse( + $item->getJoin()->stagesDiffer(), + 'No records should be different from their published versions anymore' + ); + } + + $newLiveOrder = $parent->$relationName()->sort($sortName)->map($sortName, 'ID')->toArray(); + $this->assertEquals($desiredOrder, $newLiveOrder); + } +} diff --git a/tests/Stub/StubParent.php b/tests/Stub/StubParent.php index 4193c1c..8904318 100644 --- a/tests/Stub/StubParent.php +++ b/tests/Stub/StubParent.php @@ -7,19 +7,19 @@ use SilverStripe\ORM\DataObject; class StubParent extends DataObject implements TestOnly { - private static $has_many = array( + private static $has_many = [ 'MyHasMany' => StubOrdered::class, 'MyHasManySubclass' => StubSubclass::class, 'MyHasManySubclassOrderedVersioned' => StubSubclassOrderedVersioned::class, - ); + ]; - private static $many_many = array( - 'MyManyMany' => StubOrdered::class - ); + private static $many_many = [ + 'MyManyMany' => StubOrdered::class, + ]; - private static $many_many_extraFields = array( - 'MyManyMany' => array('ManyManySort' => 'Int') - ); + private static $many_many_extraFields = [ + 'MyManyMany' => ['ManyManySort' => 'Int'], + ]; private static $table_name = 'StubParent'; } diff --git a/tests/Stub/ThroughBelongs.php b/tests/Stub/ThroughBelongs.php new file mode 100644 index 0000000..3c6cb07 --- /dev/null +++ b/tests/Stub/ThroughBelongs.php @@ -0,0 +1,15 @@ + ThroughDefiner::class, + ]; +} diff --git a/tests/Stub/ThroughDefiner.php b/tests/Stub/ThroughDefiner.php new file mode 100644 index 0000000..62ab202 --- /dev/null +++ b/tests/Stub/ThroughDefiner.php @@ -0,0 +1,23 @@ + [ + 'through' => ThroughIntermediary::class, + 'from' => 'Defining', + 'to' => 'Belonging', + ] + ]; + + private static $owns = [ + 'Belongings' + ]; +} diff --git a/tests/Stub/ThroughIntermediary.php b/tests/Stub/ThroughIntermediary.php new file mode 100644 index 0000000..12a48e1 --- /dev/null +++ b/tests/Stub/ThroughIntermediary.php @@ -0,0 +1,21 @@ + DBInt::class, + ]; + + private static $has_one = [ + 'Defining' => ThroughDefiner::class, + 'Belonging' => ThroughBelongs::class, + ]; +} From a034bd5973996193fa02f27f467156d8ff60eeae Mon Sep 17 00:00:00 2001 From: Robbie Averill Date: Wed, 4 Jul 2018 09:30:03 +1200 Subject: [PATCH 14/23] Remove obsolete branch alias --- composer.json | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 80d7faa..849bb1a 100644 --- a/composer.json +++ b/composer.json @@ -25,7 +25,7 @@ "require-dev": { "phpunit/phpunit": "^5.7", "squizlabs/php_codesniffer": "^3.0", - "silverstripe/versioned": "^1@dev" + "silverstripe/versioned": "^1" }, "extra": { "screenshots": [ @@ -35,10 +35,7 @@ "expose": [ "css", "javascript" - ], - "branch-alias": { - "dev-master": "4.x-dev" - } + ] }, "replace": { "ajshort/silverstripe-gridfieldextensions": "self.version", From b221134ce19419d372399540f350a477751afe38 Mon Sep 17 00:00:00 2001 From: Robbie Averill Date: Fri, 28 Sep 2018 17:38:38 +0200 Subject: [PATCH 15/23] FIX Orderable rows now respects actual MMTL sort orders instead of incrementing from SiteTree --- src/GridFieldOrderableRows.php | 67 ++++++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 16 deletions(-) diff --git a/src/GridFieldOrderableRows.php b/src/GridFieldOrderableRows.php index 57bb4fb..d8c5e0c 100755 --- a/src/GridFieldOrderableRows.php +++ b/src/GridFieldOrderableRows.php @@ -4,6 +4,8 @@ namespace Symbiote\GridFieldExtensions; use Exception; use SilverStripe\Control\Controller; +use SilverStripe\Control\HTTPRequest; +use SilverStripe\Control\HTTPResponse_Exception; use SilverStripe\Control\RequestHandler; use SilverStripe\Core\ClassInfo; use SilverStripe\Forms\GridField\GridField; @@ -20,11 +22,11 @@ use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DataObjectInterface; use SilverStripe\ORM\DataObjectSchema; use SilverStripe\ORM\DB; +use SilverStripe\ORM\FieldType\DBDatetime; use SilverStripe\ORM\ManyManyList; use SilverStripe\ORM\ManyManyThroughList; use SilverStripe\ORM\ManyManyThroughQueryManipulator; use SilverStripe\ORM\SS_List; -use SilverStripe\ORM\FieldType\DBDatetime; use SilverStripe\Versioned\Versioned; use SilverStripe\View\ViewableData; @@ -295,7 +297,22 @@ class GridFieldOrderableRows extends RequestHandler implements $record->ID, $this->getSortField() ); - $sortField = new HiddenField($sortFieldName, false, $record->getField($this->getSortField())); + + // Default: Get the sort field directly from the current record + $currentSortValue = $record->getField($this->getSortField()); + + $list = $grid->getList(); + if ($list instanceof ManyManyThroughList) { + // In a many many through list we should get the current sort order from the relationship + // if it exists, not directly from the record + $throughListSorts = $this->getSortValuesFromManyManyThroughList($list, $this->getSortField()); + + if (array_key_exists($record->ID, $throughListSorts)) { + $currentSortValue = $throughListSorts[$record->ID]; + } + } + + $sortField = HiddenField::create($sortFieldName, false, $currentSortValue); $sortField->addExtraClass('ss-orderable-hidden-sort'); $sortField->setForm($grid->getForm()); @@ -345,17 +362,18 @@ class GridFieldOrderableRows extends RequestHandler implements $sortterm .= '"'.$this->getSortTable($list).'"."'.$this->getSortField().'"'; } return $list->sort($sortterm); - } else { - return $list; } + + return $list; } /** * Handles requests to reorder a set of IDs in a specific order. * * @param GridField $grid - * @param SS_HTTPRequest $request - * @return SS_HTTPResponse + * @param HTTPRequest $request + * @return string + * @throws HTTPResponse_Exception */ public function handleReorder($grid, $request) { @@ -536,16 +554,7 @@ class GridFieldOrderableRows extends RequestHandler implements } } } elseif ($items instanceof ManyManyThroughList) { - $manipulator = $this->getManyManyInspector($list); - $joinClass = $manipulator->getJoinClass(); - $fromRelationName = $manipulator->getForeignKey(); - $toRelationName = $manipulator->getLocalKey(); - $sortlist = DataList::create($joinClass)->filter([ - $toRelationName => $items->column('ID'), - // first() is safe as there are earlier checks to ensure our list to sort is valid - $fromRelationName => $items->first()->getJoin()->$fromRelationName, - ]); - $current = $sortlist->map($toRelationName, $sortField)->toArray(); + $current = $this->getSortValuesFromManyManyThroughList($list, $sortField); } else { $current = $items->map('ID', $sortField)->toArray(); } @@ -759,4 +768,30 @@ class GridFieldOrderableRows extends RequestHandler implements } return $inspector; } + + /** + * Used to get sort orders from a many many through list relationship record, rather than the current + * record itself. + * + * @param ManyManyList|ManyManyThroughList $list + * @return int[] Sort orders for the + */ + protected function getSortValuesFromManyManyThroughList($list, $sortField) + { + $manipulator = $this->getManyManyInspector($list); + + // Find the foreign key name, ID and class to look up + $joinClass = $manipulator->getJoinClass(); + $fromRelationName = $manipulator->getForeignKey(); + $toRelationName = $manipulator->getLocalKey(); + + // Create a list of the MMTL relations + $sortlist = DataList::create($joinClass)->filter([ + $toRelationName => $list->column('ID'), + // first() is safe as there are earlier checks to ensure our list to sort is valid + $fromRelationName => $list->first()->getJoin()->$fromRelationName, + ]); + + return $sortlist->map($toRelationName, $sortField)->toArray(); + } } From 78c63c6725cca07882b720a5c9581da45b04d769 Mon Sep 17 00:00:00 2001 From: Robbie Averill Date: Fri, 28 Sep 2018 17:59:44 +0200 Subject: [PATCH 16/23] Add test for getting the correct Sort order from MMTL items, fix incorrect test class name --- tests/GridFieldOrderableRowsTest.php | 34 +++++++++++++++++++++ tests/OrderableRowsThroughVersionedTest.php | 4 +-- tests/Stub/ThroughIntermediary.php | 7 ++--- 3 files changed, 39 insertions(+), 6 deletions(-) diff --git a/tests/GridFieldOrderableRowsTest.php b/tests/GridFieldOrderableRowsTest.php index 2ca98a9..dd71b07 100644 --- a/tests/GridFieldOrderableRowsTest.php +++ b/tests/GridFieldOrderableRowsTest.php @@ -88,6 +88,40 @@ class GridFieldOrderableRowsTest extends SapphireTest $this->assertEquals($desiredOrder, $newOrder); } + public function testManyManyThroughListSortOrdersAreUsedForInitialRender() + { + /** @var ThroughDefiner $record */ + $record = $this->objFromFixture(ThroughDefiner::class, 'DefinerOne'); + + $orderable = new GridFieldOrderableRows('Sort'); + $config = new GridFieldConfig_RelationEditor(); + $config->addComponent($orderable); + + $grid = new GridField( + 'Belongings', + 'Testing Many Many', + $record->Belongings()->sort('Sort'), + $config + ); + + // Get the first record, which would be the first one to have column contents generated + /** @var ThroughIntermediary $expected */ + $intermediary = $this->objFromFixture(ThroughIntermediary::class, 'One'); + + $result = $orderable->getColumnContent($grid, $record, 'irrelevant'); + + $this->assertContains( + 'Belongings[GridFieldEditableColumns][' . $record->ID . '][Sort]', + $result, + 'The field name is indexed under the record\'s ID' + ); + $this->assertContains( + 'value="' . $intermediary->Sort . '"', + $result, + 'The value comes from the MMTL intermediary Sort value' + ); + } + public function testSortableChildClass() { $orderable = new GridFieldOrderableRows('Sort'); diff --git a/tests/OrderableRowsThroughVersionedTest.php b/tests/OrderableRowsThroughVersionedTest.php index 694da01..e7e3cb1 100644 --- a/tests/OrderableRowsThroughVersionedTest.php +++ b/tests/OrderableRowsThroughVersionedTest.php @@ -12,7 +12,7 @@ use Symbiote\GridFieldExtensions\Tests\Stub\ThroughIntermediary; use Symbiote\GridFieldExtensions\Tests\Stub\ThroughBelongs; use Symbiote\GridFieldExtensions\GridFieldOrderableRows; -class OrderableRowsThroughTest extends SapphireTest +class OrderableRowsThroughVersionedTest extends SapphireTest { protected static $fixture_file = 'OrderableRowsThroughTest.yml'; @@ -105,7 +105,7 @@ class OrderableRowsThroughTest extends SapphireTest } } $this->assertTrue($differenceFound, 'All records should have changes in draft'); - + // Verify live stage has NOT reordered Versioned::set_stage(Versioned::LIVE); $sameOrder = $parent->$relationName()->sort($sortName)->column('ID'); diff --git a/tests/Stub/ThroughIntermediary.php b/tests/Stub/ThroughIntermediary.php index 12a48e1..9477d61 100644 --- a/tests/Stub/ThroughIntermediary.php +++ b/tests/Stub/ThroughIntermediary.php @@ -2,18 +2,17 @@ namespace Symbiote\GridFieldExtensions\Tests\Stub; -use SilverStripe\ORM\DataObject; -use SilverStripe\ORM\FieldType\DBInt; use SilverStripe\Dev\TestOnly; +use SilverStripe\ORM\DataObject; class ThroughIntermediary extends DataObject implements TestOnly { private static $table_name = 'IntermediaryThrough'; private static $db = [ - 'Sort' => DBInt::class, + 'Sort' => 'Int', ]; - + private static $has_one = [ 'Defining' => ThroughDefiner::class, 'Belonging' => ThroughBelongs::class, From 968b80791834cfbac0163e77f4362161ef2b51e8 Mon Sep 17 00:00:00 2001 From: Robbie Averill Date: Fri, 28 Sep 2018 18:24:50 +0200 Subject: [PATCH 17/23] Add SS 4.3.x to Travis matrix and move Postgres to 2.1.x-dev with SS 4.2 or newer --- .travis.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index 74cc970..f0fbdb3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,9 +9,11 @@ matrix: - php: 5.6 env: DB=MYSQL RECIPE_VERSION=1.0.x-dev PHPCS_TEST=1 PHPUNIT_TEST=1 - php: 7.0 - env: DB=PGSQL RECIPE_VERSION=1.1.x-dev PHPUNIT_TEST=1 + env: DB=MYSQL RECIPE_VERSION=1.1.x-dev PHPUNIT_TEST=1 - php: 7.1 - env: DB=MYSQL RECIPE_VERSION=4.2.x-dev PHPUNIT_COVERAGE_TEST=1 + env: DB=PGSQL RECIPE_VERSION=4.2.x-dev PHPUNIT_COVERAGE_TEST=1 + - php: 7.2 + env: DB=MYSQL RECIPE_VERSION=4.3.x-dev PHPUNIT_TEST=1 - php: 7.2 env: DB=MYSQL RECIPE_VERSION=4.x-dev PHPUNIT_TEST=1 @@ -21,7 +23,7 @@ before_script: - composer validate - composer require silverstripe/recipe-core "$RECIPE_VERSION" --no-update - - if [[ $DB == PGSQL ]]; then composer require silverstripe/postgresql:2.0.x-dev --no-update; fi + - if [[ $DB == PGSQL ]]; then composer require silverstripe/postgresql:2.1.x-dev --no-update; fi - composer install --prefer-dist --no-interaction --no-progress --no-suggest --optimize-autoloader --verbose --profile script: From 4263c9d9700ef74745362f9cf1afa0dd506f210f Mon Sep 17 00:00:00 2001 From: Robbie Averill Date: Wed, 17 Oct 2018 11:36:45 +0200 Subject: [PATCH 18/23] FIX Disable change tracking on configurable paginator inputs See https://github.com/silverstripe/silverstripe-framework/pull/8486 --- .../GridFieldExtensions/GridFieldConfigurablePaginator.ss | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/templates/Symbiote/GridFieldExtensions/GridFieldConfigurablePaginator.ss b/templates/Symbiote/GridFieldExtensions/GridFieldConfigurablePaginator.ss index 488c40f..17e3de0 100644 --- a/templates/Symbiote/GridFieldExtensions/GridFieldConfigurablePaginator.ss +++ b/templates/Symbiote/GridFieldExtensions/GridFieldConfigurablePaginator.ss @@ -2,7 +2,7 @@ <%t Symbiote\\GridFieldExtensions\\GridFieldConfigurablePaginator.SHOW 'Show' is 'Verb. Example: Show 1 of 2' %> - <% loop $PageSizes %> <% end_loop %> @@ -14,7 +14,7 @@ $FirstPage $PreviousPage <%t SilverStripe\\Forms\\GridField\\GridFieldPaginator.Page 'Page' %> - + <%t SilverStripe\\Forms\\GridField\\GridFieldPaginator.OF 'of' is 'Example: View 1 of 2' %> $NumPages From 3876527913a6f63750e9cf3beecc3349c60cee33 Mon Sep 17 00:00:00 2001 From: Robbie Averill Date: Fri, 19 Oct 2018 21:38:19 +0200 Subject: [PATCH 19/23] FIX Position inline editable checkbox fields relative rather than absolute --- css/GridFieldExtensions.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/css/GridFieldExtensions.css b/css/GridFieldExtensions.css index 498d478..f56db7e 100644 --- a/css/GridFieldExtensions.css +++ b/css/GridFieldExtensions.css @@ -42,6 +42,11 @@ background: #DFD; } +.grid-field__table .form-check-input.editable-column-field { + margin-top: .9rem; + position: relative; +} + /** * GridFieldAddNewMultiClass */ From 76541b27f238da3a069778514ea0c939b2c31019 Mon Sep 17 00:00:00 2001 From: Robbie Averill Date: Fri, 1 Mar 2019 15:38:49 +1300 Subject: [PATCH 20/23] Add PHP 7.3 to Travis builds and use recipe-cms instead of recipe-core Fixes the version incompatibility with silverstripe/versioned and its new interfaces --- .travis.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index f0fbdb3..7140b49 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,5 @@ language: php -env: - global: - - COMPOSER_ROOT_VERSION="3.1.x-dev" - matrix: include: - php: 5.6 @@ -14,7 +10,7 @@ matrix: env: DB=PGSQL RECIPE_VERSION=4.2.x-dev PHPUNIT_COVERAGE_TEST=1 - php: 7.2 env: DB=MYSQL RECIPE_VERSION=4.3.x-dev PHPUNIT_TEST=1 - - php: 7.2 + - php: 7.3 env: DB=MYSQL RECIPE_VERSION=4.x-dev PHPUNIT_TEST=1 before_script: @@ -22,7 +18,7 @@ before_script: - phpenv config-rm xdebug.ini - composer validate - - composer require silverstripe/recipe-core "$RECIPE_VERSION" --no-update + - composer require silverstripe/recipe-cms:"$RECIPE_VERSION" --no-update - if [[ $DB == PGSQL ]]; then composer require silverstripe/postgresql:2.1.x-dev --no-update; fi - composer install --prefer-dist --no-interaction --no-progress --no-suggest --optimize-autoloader --verbose --profile From 088e7f16a81fb07d789f665958dcfec1336398dd Mon Sep 17 00:00:00 2001 From: Ivo Bathke Date: Tue, 26 Feb 2019 08:37:12 +0100 Subject: [PATCH 21/23] FIX Fixed unload modal in GridFieldConfigurablePaginator --- src/GridFieldConfigurablePaginator.php | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/GridFieldConfigurablePaginator.php b/src/GridFieldConfigurablePaginator.php index 3655ae9..54e5c7d 100644 --- a/src/GridFieldConfigurablePaginator.php +++ b/src/GridFieldConfigurablePaginator.php @@ -290,33 +290,34 @@ class GridFieldConfigurablePaginator extends GridFieldPaginator 'title' => 'First', 'args' => array('first-shown' => 1), 'extra-class' => 'btn btn-secondary btn--hide-text btn-sm font-icon-angle-double-left ' - . 'ss-gridfield-firstpage', + . 'ss-gridfield-pagination-action ss-gridfield-firstpage', 'disable-previous' => ($this->getCurrentPage() == 1) ), 'prev' => array( 'title' => 'Previous', 'args' => array('first-shown' => $arguments['first-shown'] - $this->getItemsPerPage()), 'extra-class' => 'btn btn-secondary btn--hide-text btn-sm font-icon-angle-left ' - . 'ss-gridfield-previouspage', + . 'ss-gridfield-pagination-action ss-gridfield-previouspage', 'disable-previous' => ($this->getCurrentPage() == 1) ), 'next' => array( 'title' => 'Next', 'args' => array('first-shown' => $arguments['first-shown'] + $this->getItemsPerPage()), - 'extra-class' => 'btn btn-secondary btn--hide-text btn-sm font-icon-angle-right ss-gridfield-nextpage', + 'extra-class' => 'btn btn-secondary btn--hide-text btn-sm font-icon-angle-right ' + .'ss-gridfield-pagination-action ss-gridfield-nextpage', 'disable-next' => ($this->getCurrentPage() == $arguments['total-pages']) ), 'last' => array( 'title' => 'Last', 'args' => array('first-shown' => ($this->getTotalPages() - 1) * $this->getItemsPerPage() + 1), 'extra-class' => 'btn btn-secondary btn--hide-text btn-sm font-icon-angle-double-right ' - . 'ss-gridfield-lastpage', + . 'ss-gridfield-pagination-action ss-gridfield-lastpage', 'disable-next' => ($this->getCurrentPage() == $arguments['total-pages']) ), 'pagesize' => array( 'title' => 'Page Size', 'args' => array('first-shown' => $arguments['first-shown']), - 'extra-class' => 'ss-gridfield-pagesize-submit' + 'extra-class' => 'ss-gridfield-pagination-action ss-gridfield-pagesize-submit' ), ); From 600a39e428ae1d64f72b86538009608438459eb9 Mon Sep 17 00:00:00 2001 From: Loz Calver Date: Tue, 22 Jan 2019 14:15:00 +0000 Subject: [PATCH 22/23] FIX: Remove unnecessary "version mismatch" restriction (fixes #282) --- src/GridFieldOrderableRows.php | 16 +--------------- 1 file changed, 1 insertion(+), 15 deletions(-) diff --git a/src/GridFieldOrderableRows.php b/src/GridFieldOrderableRows.php index d8c5e0c..24029d9 100755 --- a/src/GridFieldOrderableRows.php +++ b/src/GridFieldOrderableRows.php @@ -588,25 +588,11 @@ class GridFieldOrderableRows extends RequestHandler implements // Model has sort column and is versioned - handle as versioned // Model has sort column and is NOT versioned - handle as NOT versioned // Model doesn't have sort column because sort column is on ManyManyList - handle as NOT versioned - // Model doesn't have sort column because sort column is on ManyManyThroughList... - // - Related item is not versioned: - // - Through object is versioned: THROW an error. - // - Through object is NOT versioned: handle as NOT versioned - // - Related item is versioned... - // - Through object is versioned: handle as versioned - // - Through object is NOT versioned: THROW an error. + // Model doesn't have sort column because sort column is on ManyManyThroughList - inspect through object if ($list instanceof ManyManyThroughList) { - $listClassVersioned = $class::create()->hasExtension(Versioned::class); // We'll be updating the join class, not the relation class. $class = $this->getManyManyInspector($list)->getJoinClass(); $isVersioned = $class::create()->hasExtension(Versioned::class); - - // If one side of the relationship is versioned and the other is not, throw an error. - if ($listClassVersioned xor $isVersioned) { - throw new Exception( - 'ManyManyThrough cannot mismatch Versioning between join class and related class' - ); - } } elseif (!$this->isManyMany($list)) { $isVersioned = $class::create()->hasExtension(Versioned::class); } From f22531bf6a31043bc69d48ae4d7b2746af619c7a Mon Sep 17 00:00:00 2001 From: Loz Calver Date: Wed, 10 Apr 2019 17:09:13 +0100 Subject: [PATCH 23/23] FIX: Unable to delete inline-added rows before saving them --- javascript/GridFieldExtensions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/GridFieldExtensions.js b/javascript/GridFieldExtensions.js index e776941..33e3115 100644 --- a/javascript/GridFieldExtensions.js +++ b/javascript/GridFieldExtensions.js @@ -208,7 +208,7 @@ } }); - $(".ss-gridfield-delete-inline").entwine({ + $(".grid-field .action.ss-gridfield-delete-inline").entwine({ onclick: function() { var msg = ss.i18n._t("GridFieldExtensions.CONFIRMDEL", "Are you sure you want to delete this?");