From a6a17439976710b2311558d363b5467fa429dcca Mon Sep 17 00:00:00 2001 From: bergice Date: Tue, 16 Oct 2018 18:49:51 +1300 Subject: [PATCH 01/13] BUG: Fix `ENTER` not triggering form save button as `GridField`s used `submit` type buttons --- src/Forms/GridField/GridField_FormAction.php | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Forms/GridField/GridField_FormAction.php b/src/Forms/GridField/GridField_FormAction.php index 627ef8b87..10dc772bc 100644 --- a/src/Forms/GridField/GridField_FormAction.php +++ b/src/Forms/GridField/GridField_FormAction.php @@ -101,6 +101,7 @@ class GridField_FormAction extends FormAction // will strip it from the requests 'name' => 'action_gridFieldAlterAction' . '?' . http_build_query($actionData), 'data-url' => $this->gridField->Link(), + 'type' => "button", ) ); } From af46381dca9ef11a407dbb7016a6ac1621a4b376 Mon Sep 17 00:00:00 2001 From: Maxime Rainville Date: Tue, 23 Oct 2018 11:52:25 +1300 Subject: [PATCH 02/13] MINOR Correct implementation of single lookup field when valueToLabel returns null --- src/Forms/SingleLookupField.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Forms/SingleLookupField.php b/src/Forms/SingleLookupField.php index a8dfc47bb..99b38f4c3 100644 --- a/src/Forms/SingleLookupField.php +++ b/src/Forms/SingleLookupField.php @@ -94,7 +94,7 @@ class SingleLookupField extends SingleSelectField return $label; } - return $value; + return parent::Value(); } /** From 4e62698391035e330815c83293a50bcc09f8413f Mon Sep 17 00:00:00 2001 From: Maxime Rainville Date: Wed, 24 Oct 2018 15:27:15 +1300 Subject: [PATCH 03/13] Add entry to change log about CMSPageHistoryController deprecation. (#8508) * Add entry to change log about CMSPageHistoryController deprecation. * Correct typo #8508 * Tweaking CMS History controller changelog entry #8508 --- docs/en/04_Changelogs/4.3.0.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/docs/en/04_Changelogs/4.3.0.md b/docs/en/04_Changelogs/4.3.0.md index 58f9a5b9d..9dba198d9 100644 --- a/docs/en/04_Changelogs/4.3.0.md +++ b/docs/en/04_Changelogs/4.3.0.md @@ -7,6 +7,7 @@ - Take care with `stageChildren()` overrides. `Hierarchy::numChildren() ` results will only make use of `stageChildren()` customisations that are applied to the base class and don't include record-specific behaviour. - New React-based search UI for the CMS, Asset-Admin, GridFields and ModelAdmins. - A new `GridFieldLazyLoader` component can be added to `GridField`. This will delay the fetching of data until the user access the container Tab of the GridField. + - `SilverStripe\VersionedAdmin\Controllers\CMSPageHistoryViewerController` is now the default CMS history controller and `SilverStripe\CMS\Controllers\CMSPageHistoryController` has been deprecated. ## Upgrading {#upgrading} @@ -41,3 +42,23 @@ public function getCMSFields() } ``` + +### Keep using the legacy `CMSPageHistoryController` + +To keep using the old CMS history controller for every page type, add the following entry to your YML config. + +```yml +SilverStripe\Core\Injector\Injector: + SilverStripe\CMS\Controllers\CMSPageHistoryController: + class: SilverStripe\CMS\Controllers\CMSPageHistoryController +``` + +If you want to use both CMS history controllers in different contexts, you can implement your own _Factory_ class. +```yml +SilverStripe\Core\Injector\Injector: + SilverStripe\CMS\Controllers\CMSPageHistoryController: + factory: + App\MySite\MyCustomControllerFactory +``` + +[Implementing a _Factory_ with the Injector](/developer_guides/extending/injector/#factories) From a3a4f48cb7385820b0a5ce97d717ffaadb614cea Mon Sep 17 00:00:00 2001 From: Aaron Carlino Date: Wed, 24 Oct 2018 16:47:16 +1300 Subject: [PATCH 04/13] MINOR Update doc to explain how to use and build the pattern lib --- .../02_CMS_Architecture.md | 28 ++++++++++++++++--- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/docs/en/02_Developer_Guides/15_Customising_the_Admin_Interface/02_CMS_Architecture.md b/docs/en/02_Developer_Guides/15_Customising_the_Admin_Interface/02_CMS_Architecture.md index c98c7fdfe..f90a7fa4b 100644 --- a/docs/en/02_Developer_Guides/15_Customising_the_Admin_Interface/02_CMS_Architecture.md +++ b/docs/en/02_Developer_Guides/15_Customising_the_Admin_Interface/02_CMS_Architecture.md @@ -55,15 +55,35 @@ coding conventions. A pattern library is a collection of user interface design elements, this helps developers and designers collaborate and to provide a quick preview of elements as they were intended without the need to build an entire interface to see it. Components built in React and used by the CMS are actively being added to the pattern library. +The pattern library can be used to preview React components without including them in the SilverStripe CMS. -To access the pattern library, starting from your project root: +### Viewing the latest pattern library -``` -cd vendor/silverstripe/admin && yarn pattern-lib +The easiest way to access the pattern library is to view it online. The pattern library for the latest SilverStripe 4 development branch is automatically built and deployed. Note that this may include new components that are not yet available in a stable release. + +[Browse the SilverStripe pattern library online](https://silverstripe.github.io/silverstripe-admin). + +### Running the pattern library + +If you're developing a new React component, running the pattern library locally is a good way to interact with it. + +The pattern library is built from the `silverstripe/admin` module, but it also requires `silverstripe/asset-admin`, `silversrtipe/cms` and `silverstripe/campaign-admin`. + +To run the pattern library locally, you'll need a SilverStripe project based on `silverstripe/recipe-cms` and `yarn` installed locally. The pattern library requires the JS source files so you'll need to use the `--prefer-source` flag when installing your dependencies with Composer. + +```bash +composer install --prefer-source +(cd vendor/silverstripe/asset-admin && yarn install) +(cd vendor/silverstripe/campaign-admin && yarn install) +(cd vendor/silverstripe/cms && yarn install) +cd vendor/silverstripe/admin && yarn install && yarn pattern-lib ``` -Then browse to `http://localhost:6006/` +The pattern library will be available at [http://localhost:6006](http://localhost:6006). The JS source files will be watched, so every time you make a change to a JavaScript file, the pattern library will automatically update itself. +If you want to build a static version of the pattern library, you can replace `yarn pattern-lib` with `yarn build-storybook`. This will output the pattern library files to a `storybook-static` folder. + +The SilverStripe pattern library is built using the [StoryBook JS library](https://storybook.js.org/). You can read the StoryBook documentation to learn about more advanced features and customisation options. ## The Admin URL From e72fc9e3d0f35a1d43f55f83f9919f67d72fb7cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sam=20Minn=C3=A9e?= Date: Thu, 25 Oct 2018 11:42:45 +1300 Subject: [PATCH 05/13] FIX DataObject singleton creation (#8516) Ensure DataObject instances are aware they are singletons so functions like populateDefaults() can be skipped. (fixes #4878) Correctly applies https://github.com/silverstripe/silverstripe-framework/pull/7850 to the 4.x line This has already been fixed in 3.x --- src/Core/Injector/Injector.php | 9 +++++++ tests/php/ORM/DataObjectTest.php | 45 ++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/src/Core/Injector/Injector.php b/src/Core/Injector/Injector.php index 6b0885132..6cf980cbf 100644 --- a/src/Core/Injector/Injector.php +++ b/src/Core/Injector/Injector.php @@ -13,6 +13,7 @@ use SilverStripe\Core\ClassInfo; use SilverStripe\Core\Config\Config; use SilverStripe\Core\Environment; use SilverStripe\Dev\Deprecation; +use SilverStripe\ORM\DataObject; /** * A simple injection manager that manages creating objects and injecting @@ -581,6 +582,14 @@ class Injector implements ContainerInterface $constructorParams = $spec['constructor']; } + // If we're dealing with a DataObject singleton without specific constructor params, pass through Singleton + // flag as second argument + if ((!$type || $type !== self::PROTOTYPE) + && empty($constructorParams) + && is_subclass_of($class, DataObject::class)) { + $constructorParams = array(null, true); + } + $factory = isset($spec['factory']) ? $this->get($spec['factory']) : $this->getObjectCreator(); $object = $factory->create($class, $constructorParams); diff --git a/tests/php/ORM/DataObjectTest.php b/tests/php/ORM/DataObjectTest.php index 55c2ea659..3a2158c4c 100644 --- a/tests/php/ORM/DataObjectTest.php +++ b/tests/php/ORM/DataObjectTest.php @@ -66,6 +66,51 @@ class DataObjectTest extends SapphireTest ); } + /** + * @dataProvider provideSingletons + */ + public function testSingleton($inst, $defaultValue, $altDefaultValue) + { + $inst = $inst(); + // Test that populateDefaults() isn't called on singletons + // which can lead to SQL errors during build, and endless loops + if ($defaultValue) { + $this->assertEquals($defaultValue, $inst->MyFieldWithDefault); + } else { + $this->assertEmpty($inst->MyFieldWithDefault); + } + + if ($altDefaultValue) { + $this->assertEquals($altDefaultValue, $inst->MyFieldWithAltDefault); + } else { + $this->assertEmpty($inst->MyFieldWithAltDefault); + } + } + + public function provideSingletons() + { + // because PHPUnit evalutes test providers *before* setUp methods + // any extensions added in the setUp methods won't be available + // we must return closures to generate the arguments at run time + return array( + 'create() static method' => array(function () { + return DataObjectTest\Fixture::create(); + }, 'Default Value', 'Default Value'), + 'New object creation' => array(function () { + return new DataObjectTest\Fixture(); + }, 'Default Value', 'Default Value'), + 'singleton() function' => array(function () { + return singleton(DataObjectTest\Fixture::class); + }, null, null), + 'singleton() static method' => array(function () { + return DataObjectTest\Fixture::singleton(); + }, null, null), + 'Manual constructor args' => array(function () { + return new DataObjectTest\Fixture(null, true); + }, null, null), + ); + } + public function testDb() { $schema = DataObject::getSchema(); From d879148bffe31506345257b731d75756570b971a Mon Sep 17 00:00:00 2001 From: Luke Edwards Date: Fri, 26 Oct 2018 14:28:15 +1300 Subject: [PATCH 06/13] Add config to force legacy filter header globally --- docs/en/04_Changelogs/4.3.0.md | 6 ++++++ src/Forms/GridField/GridFieldFilterHeader.php | 15 +++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/docs/en/04_Changelogs/4.3.0.md b/docs/en/04_Changelogs/4.3.0.md index 9dba198d9..3965ad378 100644 --- a/docs/en/04_Changelogs/4.3.0.md +++ b/docs/en/04_Changelogs/4.3.0.md @@ -26,6 +26,12 @@ To enable the legacy search API on a `GridFieldFilterHeader`, you can either: * set the `useLegacyFilterHeader` property to `true`, * or pass `true` to the first argument of its constructor. +To force the legacy search API on all instances of `GridFieldFilterHeader`, you can set it in your [configuration file](../../configuration): +```yml +SilverStripe\Forms\GridField\GridFieldFilterHeader: + force_legacy: true +``` + ```php public function getCMSFields() { diff --git a/src/Forms/GridField/GridFieldFilterHeader.php b/src/Forms/GridField/GridFieldFilterHeader.php index fd3dd8ea9..13ad12354 100755 --- a/src/Forms/GridField/GridFieldFilterHeader.php +++ b/src/Forms/GridField/GridFieldFilterHeader.php @@ -6,6 +6,7 @@ use LogicException; use SilverStripe\Admin\LeftAndMain; use SilverStripe\Control\Controller; use SilverStripe\Control\HTTPResponse; +use SilverStripe\Core\Config\Config; use SilverStripe\Core\Convert; use SilverStripe\Dev\Deprecation; use SilverStripe\Forms\FieldGroup; @@ -43,6 +44,16 @@ class GridFieldFilterHeader implements GridField_URLHandler, GridField_HTMLProvi */ public $useLegacyFilterHeader = false; + /** + * Forces all filter components to revert to displaying the legacy + * table header style rather than the react driven search box + * + * @deprecated 4.3.0:5.0.0 Will be removed in 5.0 + * @config + * @var bool + */ + private static $force_legacy = false; + /** * @var \SilverStripe\ORM\Search\SearchContext */ @@ -76,7 +87,7 @@ class GridFieldFilterHeader implements GridField_URLHandler, GridField_HTMLProvi } /** - * @param bool $useLegacy + * @param bool $useLegacy This will be removed in 5.0 * @param callable|null $updateSearchContext This will be removed in 5.0 * @param callable|null $updateSearchForm This will be removed in 5.0 */ @@ -85,7 +96,7 @@ class GridFieldFilterHeader implements GridField_URLHandler, GridField_HTMLProvi callable $updateSearchContext = null, callable $updateSearchForm = null ) { - $this->useLegacyFilterHeader = $useLegacy; + $this->useLegacyFilterHeader = Config::inst()->get(self::class, 'force_legacy') || $useLegacy; $this->updateSearchContextCallback = $updateSearchContext; $this->updateSearchFormCallback = $updateSearchForm; } From 3284bf48d6e3da8b2b1a7831e2d7fe4b401e2fd6 Mon Sep 17 00:00:00 2001 From: Luke Edwards Date: Fri, 26 Oct 2018 14:43:56 +1300 Subject: [PATCH 07/13] Fix search filtering relations and clear filters (#8477) --- src/Forms/GridField/GridFieldFilterHeader.php | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/Forms/GridField/GridFieldFilterHeader.php b/src/Forms/GridField/GridFieldFilterHeader.php index fd3dd8ea9..ab0dd58e0 100755 --- a/src/Forms/GridField/GridFieldFilterHeader.php +++ b/src/Forms/GridField/GridFieldFilterHeader.php @@ -154,7 +154,7 @@ class GridFieldFilterHeader implements GridField_URLHandler, GridField_HTMLProvi * If the GridField has a filterable datalist, return an array of actions * * @param GridField $gridField - * @return array + * @return void */ public function handleAction(GridField $gridField, $actionName, $arguments, $data) { @@ -163,14 +163,13 @@ class GridFieldFilterHeader implements GridField_URLHandler, GridField_HTMLProvi } $state = $gridField->State->GridFieldFilterHeader; + $state->Columns = null; if ($actionName === 'filter') { if (isset($data['filter'][$gridField->getName()])) { foreach ($data['filter'][$gridField->getName()] as $key => $filter) { $state->Columns->$key = $filter; } } - } elseif ($actionName === 'reset') { - $state->Columns = null; } } @@ -193,12 +192,10 @@ class GridFieldFilterHeader implements GridField_URLHandler, GridField_HTMLProvi $filterArguments = $columns->toArray(); $dataListClone = clone($dataList); - foreach ($filterArguments as $columnName => $value) { - if ($dataList->canFilterBy($columnName) && $value) { - $dataListClone = $dataListClone->filter($columnName . ':PartialMatch', $value); - } - } - return $dataListClone; + $results = $this->getSearchContext($gridField) + ->getQuery($filterArguments, false, false, $dataListClone); + + return $results; } /** @@ -337,9 +334,11 @@ class GridFieldFilterHeader implements GridField_URLHandler, GridField_HTMLProvi $field->addExtraClass('stacked'); } + $name = $gridField->Title ?: singleton($gridField->getModelClass())->i18n_plural_name(); + $this->searchForm = $form = new Form( $gridField, - "SearchForm", + $name . "SearchForm", $searchFields, new FieldList() ); From c7b8b80e8b33a12667dfa9e5147bfbc583ccfc56 Mon Sep 17 00:00:00 2001 From: Luke Edwards Date: Thu, 1 Nov 2018 11:12:52 +1300 Subject: [PATCH 08/13] Persist GridField readonly state, add view button (#8535) * Persist GridField readonly state, add view button * Minor clarity fixes --- src/Forms/GridField/GridField.php | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/Forms/GridField/GridField.php b/src/Forms/GridField/GridField.php index 0585e3703..a180fa5dc 100644 --- a/src/Forms/GridField/GridField.php +++ b/src/Forms/GridField/GridField.php @@ -113,11 +113,13 @@ class GridField extends FormField protected $readonlyComponents = [ GridField_ActionMenu::class, GridFieldConfig_RecordViewer::class, + GridFieldButtonRow::class, GridFieldDataColumns::class, GridFieldDetailForm::class, GridFieldLazyLoader::class, GridFieldPageCount::class, GridFieldPaginator::class, + GridFieldFilterHeader::class, GridFieldSortableHeader::class, GridFieldToolbarHeader::class, GridFieldViewButton::class, @@ -241,16 +243,22 @@ class GridField extends FormField { $copy = clone $this; $copy->setReadonly(true); + $copyConfig = $copy->getConfig(); // get the whitelist for allowable readonly components $allowedComponents = $this->getReadonlyComponents(); foreach ($this->getConfig()->getComponents() as $component) { // if a component doesn't exist, remove it from the readonly version. if (!in_array(get_class($component), $allowedComponents)) { - $copy->getConfig()->removeComponent($component); + $copyConfig->removeComponent($component); } } + // As the edit button may have been removed, add a view button if it doesn't have one + if (!$copyConfig->getComponentByType(GridFieldViewButton::class)) { + $copyConfig->addComponent(new GridFieldViewButton); + } + return $copy; } @@ -290,6 +298,18 @@ class GridField extends FormField return $this; } + /** + * @param bool $readonly + * + * @return $this + */ + public function setReadonly($readonly) + { + parent::setReadonly($readonly); + $this->getState()->Readonly = $readonly; + return $this; + } + /** * @return ArrayList */ @@ -1009,6 +1029,9 @@ class GridField extends FormField } if ($request->getHeader('X-Pjax') === 'CurrentField') { + if ($this->getState()->Readonly) { + $this->performDisabledTransformation(); + } return $this->FieldHolder(); } From 55f95b7bc8f91384df459bd70c87cacf92225f68 Mon Sep 17 00:00:00 2001 From: Michael Strong Date: Thu, 1 Nov 2018 13:42:27 +1300 Subject: [PATCH 09/13] BUGFIX many many through not sorting by join table (#8534) * BUGFIX many many through not sorting by join table * #8534 added docs to support many many sorting fix * #8534 added test cases for many_many default sorting --- .../00_Model/02_Relations.md | 15 +++++++ src/ORM/DataObject.php | 6 +++ src/ORM/ManyManyThroughQueryManipulator.php | 7 ---- tests/php/ORM/ManyManyListTest.php | 41 +++++++++++++++++++ tests/php/ORM/ManyManyThroughListTest.php | 33 +++++++++++++++ tests/php/ORM/ManyManyThroughListTest.yml | 25 +++++++++++ .../FallbackLocale.php | 22 ++++++++++ .../ORM/ManyManyThroughListTest/Locale.php | 36 ++++++++++++++++ 8 files changed, 178 insertions(+), 7 deletions(-) create mode 100644 tests/php/ORM/ManyManyThroughListTest/FallbackLocale.php create mode 100644 tests/php/ORM/ManyManyThroughListTest/Locale.php diff --git a/docs/en/02_Developer_Guides/00_Model/02_Relations.md b/docs/en/02_Developer_Guides/00_Model/02_Relations.md index bc0876f4f..938de1a9d 100644 --- a/docs/en/02_Developer_Guides/00_Model/02_Relations.md +++ b/docs/en/02_Developer_Guides/00_Model/02_Relations.md @@ -295,6 +295,15 @@ class Supporter extends DataObject } ``` +To ensure this `many_many` is sorted by "Ranking" by default you can add this to your config: + +```yaml +Team_Supporters: + default_sort: '"Team_Supporter"."Ranking" ASC' +``` + +`Team_Supporters` is the table name automatically generated for the many_many relation in this case. + ### many_many through relationship joined on a separate DataObject If necessary, a third DataObject class can instead be specified as the joining table, @@ -312,6 +321,9 @@ This is declared via array syntax, with the following keys on the many_many: - `from` Name of the has_one relationship pointing back at the object declaring many_many - `to` Name of the has_one relationship pointing to the object declaring belongs_many_many. +Just like a any normal DataObject, you can apply a default sort which will be applied when +accessing many many through relations. + Note: The `through` class must not also be the name of any field or relation on the parent or child record. @@ -348,6 +360,8 @@ class TeamSupporter extends DataObject 'Team' => Team::class, 'Supporter' => Supporter::class, ]; + + private static $default_sort = '"TeamSupporter"."Ranking" ASC' } ``` @@ -468,6 +482,7 @@ the best way to think about it is that the object where the relationship will be Product => Categories, the `Product` should contain the `many_many`, because it is much more likely that the user will select Categories for a Product than vice-versa. + ## Cascading deletions Relationships between objects can cause cascading deletions, if necessary, through configuration of the diff --git a/src/ORM/DataObject.php b/src/ORM/DataObject.php index 1092afc32..a47034381 100644 --- a/src/ORM/DataObject.php +++ b/src/ORM/DataObject.php @@ -2051,6 +2051,12 @@ class DataObject extends ViewableData implements DataObjectInterface, i18nEntity $query->setQueryParam('Component.ExtraFields', $extraFields); }); + // If we have a default sort set for our "join" then we should overwrite any default already set. + $joinSort = Config::inst()->get($manyManyComponent['join'], 'default_sort'); + if (!empty($joinSort)) { + $result = $result->sort($joinSort); + } + $this->extend('updateManyManyComponents', $result); // If this is called on a singleton, then we return an 'orphaned relation' that can have the diff --git a/src/ORM/ManyManyThroughQueryManipulator.php b/src/ORM/ManyManyThroughQueryManipulator.php index 2024a65aa..6fffbbe60 100644 --- a/src/ORM/ManyManyThroughQueryManipulator.php +++ b/src/ORM/ManyManyThroughQueryManipulator.php @@ -254,13 +254,6 @@ class ManyManyThroughQueryManipulator implements DataQueryManipulator ); } - // Set a default sort from the join model if available and nothing is already set - if (empty($sqlSelect->getOrderBy()) - && $sort = Config::inst()->get($this->getJoinClass(), 'default_sort') - ) { - $sqlSelect->setOrderBy($sort); - } - // Apply join and record sql for later insertion (at end of replacements) // By using a string placeholder $$_SUBQUERY_$$ we protect field/table rewrites from interfering twice // on the already-finalised inner list diff --git a/tests/php/ORM/ManyManyListTest.php b/tests/php/ORM/ManyManyListTest.php index aa6218474..564864f35 100644 --- a/tests/php/ORM/ManyManyListTest.php +++ b/tests/php/ORM/ManyManyListTest.php @@ -2,6 +2,7 @@ namespace SilverStripe\ORM\Tests; +use SilverStripe\Core\Config\Config; use SilverStripe\ORM\FieldType\DBMoney; use SilverStripe\ORM\ManyManyList; use SilverStripe\Core\Convert; @@ -369,6 +370,46 @@ class ManyManyListTest extends SapphireTest $this->assertSQLEquals($expected, $list->sql($parameters)); } + /** + * This tests that we can set a default sort on a join table, even though the class doesn't exist. + * + * @return void + */ + public function testSortByExtraFieldsDefaultSort() + { + $obj = new ManyManyListTest\ExtraFieldsObject(); + $obj->write(); + + $obj2 = new ManyManyListTest\ExtraFieldsObject(); + $obj2->write(); + + $money = new DBMoney(); + $money->setAmount(100); + $money->setCurrency('USD'); + + // Add two objects as relations (first is linking back to itself) + $obj->Clients()->add($obj, ['Worth' => $money, 'Reference' => 'A']); + $obj->Clients()->add($obj2, ['Worth' => $money, 'Reference' => 'B']); + + // Set the default sort for this relation + Config::inst()->update('ManyManyListTest_ExtraFields_Clients', 'default_sort', 'Reference ASC'); + $clients = $obj->Clients(); + $this->assertCount(2, $clients); + + list($first, $second) = $obj->Clients(); + $this->assertEquals('A', $first->Reference); + $this->assertEquals('B', $second->Reference); + + // Now we ensure the default sort is being respected by reversing its order + Config::inst()->update('ManyManyListTest_ExtraFields_Clients', 'default_sort', 'Reference DESC'); + $reverseClients = $obj->Clients(); + $this->assertCount(2, $reverseClients); + + list($reverseFirst, $reverseSecond) = $obj->Clients(); + $this->assertEquals('B', $reverseFirst->Reference); + $this->assertEquals('A', $reverseSecond->Reference); + } + public function testFilteringOnPreviouslyJoinedTable() { /** @var ManyManyListTest\Category $category */ diff --git a/tests/php/ORM/ManyManyThroughListTest.php b/tests/php/ORM/ManyManyThroughListTest.php index b85c6317b..0b18cbec5 100644 --- a/tests/php/ORM/ManyManyThroughListTest.php +++ b/tests/php/ORM/ManyManyThroughListTest.php @@ -2,11 +2,14 @@ namespace SilverStripe\ORM\Tests; +use SilverStripe\Core\Config\Config; use SilverStripe\Dev\SapphireTest; use SilverStripe\ORM\DataObject; use SilverStripe\ORM\ManyManyThroughList; use SilverStripe\ORM\Tests\ManyManyThroughListTest\PolyItem; use SilverStripe\ORM\Tests\ManyManyThroughListTest\PolyJoinObject; +use SilverStripe\ORM\Tests\ManyManyThroughListTest\Locale; +use SilverStripe\ORM\Tests\ManyManyThroughListTest\FallbackLocale; class ManyManyThroughListTest extends SapphireTest { @@ -20,6 +23,8 @@ class ManyManyThroughListTest extends SapphireTest ManyManyThroughListTest\PolyJoinObject::class, ManyManyThroughListTest\PolyObjectA::class, ManyManyThroughListTest\PolyObjectB::class, + ManyManyThroughListTest\Locale::class, + ManyManyThroughListTest\FallbackLocale::class, ]; protected function setUp() @@ -320,4 +325,32 @@ class ManyManyThroughListTest extends SapphireTest $this->assertEquals($joinTable, $objB1->Items()->getJoinTable()); $this->assertEquals($joinTable, $objB2->Items()->getJoinTable()); } + + /** + * This tests that default sort works when the join table has a default sort set, and the main + * dataobject has a default sort set. + * + * @return void + */ + public function testDefaultSortOnJoinAndMain() + { + // We have spanish mexico with two fall back locales; argentina and international sorted in that order. + $mexico = $this->objFromFixture(Locale::class, 'mexico'); + + $fallbacks = $mexico->Fallbacks(); + $this->assertCount(2, $fallbacks); + + // Ensure the default sort is is correct + list($first, $second) = $fallbacks; + $this->assertSame('Argentina', $first->Title); + $this->assertSame('International', $second->Title); + + // Ensure that we're respecting the default sort by reversing it + Config::inst()->update(FallbackLocale::class, 'default_sort', '"ManyManyThroughTest_FallbackLocale"."Sort" DESC'); + + $reverse = $mexico->Fallbacks(); + list($firstReverse, $secondReverse) = $reverse; + $this->assertSame('International', $firstReverse->Title); + $this->assertSame('Argentina', $secondReverse->Title); + } } diff --git a/tests/php/ORM/ManyManyThroughListTest.yml b/tests/php/ORM/ManyManyThroughListTest.yml index 88750a29d..c7c3c3c2a 100644 --- a/tests/php/ORM/ManyManyThroughListTest.yml +++ b/tests/php/ORM/ManyManyThroughListTest.yml @@ -51,3 +51,28 @@ SilverStripe\ORM\Tests\ManyManyThroughListTest\PolyJoinObject: Sort: 2 Parent: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\PolyObjectB.objb2 Child: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\PolyItem.child2 +SilverStripe\ORM\Tests\ManyManyThroughListTest\Locale: + international: + Title: 'International' + Locale: 'en_NZ' + URLSegment: 'international' + IsGlobalDefault: 1 + mexico: + Title: 'Mexico' + Locale: 'es_MX' + URLSegment: 'mexico' + IsGlobalDefault: 0 + argentina: + Title: 'Argentina' + Locale: 'es_AR' + URLSegment: 'argentina' + IsGlobalDefault: 0 +SilverStripe\ORM\Tests\ManyManyThroughListTest\FallbackLocale: + mexico_international: + Sort: 2 + Parent: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\Locale.mexico + Locale: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\Locale.international + mexico_argentina: + Sort: 1 + Parent: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\Locale.mexico + Locale: =>SilverStripe\ORM\Tests\ManyManyThroughListTest\Locale.argentina \ No newline at end of file diff --git a/tests/php/ORM/ManyManyThroughListTest/FallbackLocale.php b/tests/php/ORM/ManyManyThroughListTest/FallbackLocale.php new file mode 100644 index 000000000..cb865028f --- /dev/null +++ b/tests/php/ORM/ManyManyThroughListTest/FallbackLocale.php @@ -0,0 +1,22 @@ + 'Int', + ]; + + private static $has_one = [ + 'Parent' => Locale::class, + 'Locale' => Locale::class, + ]; + + private static $table_name = 'ManyManyThroughTest_FallbackLocale'; + + private static $default_sort = 'Sort'; +} diff --git a/tests/php/ORM/ManyManyThroughListTest/Locale.php b/tests/php/ORM/ManyManyThroughListTest/Locale.php new file mode 100644 index 000000000..37bff23d9 --- /dev/null +++ b/tests/php/ORM/ManyManyThroughListTest/Locale.php @@ -0,0 +1,36 @@ + 'Varchar(100)', + 'Locale' => 'Varchar(10)', + 'URLSegment' => 'Varchar(100)', + 'IsGlobalDefault' => 'Boolean', + ]; + + private static $has_many = [ + 'FallbackLocales' => FallbackLocale::class . '.Parent', + ]; + + private static $many_many = [ + 'Fallbacks' => [ + 'through' => FallbackLocale::class, + 'from' => 'Parent', + 'to' => 'Locale', + ], + ]; + + private static $default_sort = '"ManyManyThroughTest_Locale"."Locale" ASC'; +} From 46e4c19070688df74757bd9a44dc2f0b2fdcd6a9 Mon Sep 17 00:00:00 2001 From: Maxime Rainville Date: Thu, 1 Nov 2018 22:38:26 +1300 Subject: [PATCH 10/13] MINOR Add a reference to SS_Object in .upgrade.yml to allow upgrade from SS3.7 --- .upgrade.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.upgrade.yml b/.upgrade.yml index e4e2c392a..33d42e81e 100644 --- a/.upgrade.yml +++ b/.upgrade.yml @@ -958,6 +958,9 @@ warnings: 'Object': message: 'Replaced with traits' url: 'https://docs.silverstripe.org/en/4/changelogs/4.0.0#object-replace' + 'SS_Object': + message: 'Replaced with traits' + url: 'https://docs.silverstripe.org/en/4/changelogs/4.0.0#object-replace' 'SS_Log': message: 'Replaced with a PSR-3 logger' url: 'https://docs.silverstripe.org/en/4/changelogs/4.0.0#psr3-logging' From 8866e7674a1a9c2be48c8e9532cfcaa667cdf7b5 Mon Sep 17 00:00:00 2001 From: Luke Edwards Date: Fri, 2 Nov 2018 12:26:53 +1300 Subject: [PATCH 11/13] BUG: Fix duplicate plugins on HTML editor fields (#8559) * BUG: Fix duplicate plugins on HTML editor fields * Add new test --- src/Forms/HTMLEditor/HTMLEditorConfig.php | 2 ++ src/Forms/HTMLEditor/TinyMCECombinedGenerator.php | 2 ++ tests/php/Forms/HTMLEditor/HTMLEditorConfigTest.php | 2 +- tests/php/Forms/HTMLEditor/TinyMCEConfigTest.php | 7 +++++++ 4 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Forms/HTMLEditor/HTMLEditorConfig.php b/src/Forms/HTMLEditor/HTMLEditorConfig.php index 27a1527aa..6672971d8 100644 --- a/src/Forms/HTMLEditor/HTMLEditorConfig.php +++ b/src/Forms/HTMLEditor/HTMLEditorConfig.php @@ -83,6 +83,7 @@ abstract class HTMLEditorConfig // Create new instance if unconfigured if (!isset(self::$configs[$identifier])) { self::$configs[$identifier] = static::create(); + self::$configs[$identifier]->setOption('editorIdentifier', $identifier); } return self::$configs[$identifier]; } @@ -98,6 +99,7 @@ abstract class HTMLEditorConfig { if ($config) { self::$configs[$identifier] = $config; + self::$configs[$identifier]->setOption('editorIdentifier', $identifier); } else { unset(self::$configs[$identifier]); } diff --git a/src/Forms/HTMLEditor/TinyMCECombinedGenerator.php b/src/Forms/HTMLEditor/TinyMCECombinedGenerator.php index f2f3a3b09..a037cb355 100644 --- a/src/Forms/HTMLEditor/TinyMCECombinedGenerator.php +++ b/src/Forms/HTMLEditor/TinyMCECombinedGenerator.php @@ -130,11 +130,13 @@ class TinyMCECombinedGenerator implements TinyMCEScriptGenerator, Flushable // Register vars for config $baseDirJS = Convert::raw2js(Director::absoluteBaseURL()); + $name = Convert::raw2js($this->checkName($config)); $buffer = []; $buffer[] = <<