-
Notifications
You must be signed in to change notification settings - Fork 96
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
NEW Add an extension to dynamically generate edit URLs
- Loading branch information
1 parent
fd91da3
commit 052ed99
Showing
10 changed files
with
417 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
<?php | ||
|
||
namespace SilverStripe\Admin; | ||
|
||
use LogicException; | ||
use SilverStripe\CMS\Controllers\CMSMain; | ||
use SilverStripe\Control\Controller; | ||
use SilverStripe\Control\Director; | ||
use SilverStripe\Core\Extension; | ||
use SilverStripe\Forms\FieldList; | ||
use SilverStripe\Forms\GridField\GridField; | ||
use SilverStripe\Forms\GridField\GridFieldDetailForm; | ||
use SilverStripe\ORM\DataObject; | ||
|
||
/** | ||
* An extension that automatically generates a CMS edit link for DataObjects even if | ||
* they are canonically edited in some nested {@link GridField}. | ||
* Designed to be used in conjunction with the {@link CMSPreviewable} interface. | ||
* | ||
* For nested relations (e.g. a DataObject managed in a GridField of another DataObject) | ||
* you can apply this extension to both the parent and the child object and the links | ||
* will chain down the nested `GridField`s to the root cms edit owner. | ||
* | ||
* You must set a cms_edit_owner config variable which defines the cms edit | ||
* owner for this class. | ||
* e.g. set this to a {@link LeftAndMain} class: | ||
* private static string cms_edit_owner = MyModelAdmin::class; | ||
* or to a has_one relation: | ||
* private static string cms_edit_owner = 'Parent'; | ||
* | ||
* Note that the cms edit owner must implement a getCMSEditLinkForManagedDataObject() method. | ||
* | ||
* If the cms edit owner is a has_one relation, the class on the other end | ||
* of the relation must have a CMSEditLink() method. | ||
*/ | ||
class CMSEditLinkExtension extends Extension | ||
{ | ||
private static string $cms_edit_owner = ''; | ||
|
||
/** | ||
* Get the admin or DataObject which owns this object for CMS editing purposes. | ||
* | ||
* @return LeftAndMain|DataObject|null | ||
*/ | ||
public function getCMSEditOwner() | ||
{ | ||
$ownerType = $this->owner->config()->get('cms_edit_owner'); | ||
if (is_subclass_of($ownerType, LeftAndMain::class)) { | ||
return $ownerType::singleton(); | ||
} | ||
return $this->owner->getComponent($ownerType); | ||
} | ||
|
||
/** | ||
* Get the link for editing an object from the CMS edit form of this object. | ||
* @throws LogicException if a link cannot be established | ||
* e.g. if the object is not in a has_many relation or not edited inside a GridField. | ||
*/ | ||
public function getCMSEditLinkForManagedDataObject(DataObject $obj, string $reciprocalRelation): string | ||
{ | ||
$fields = $this->owner->getCMSFields(); | ||
$link = $this->getCMSEditLinkForRelation($this->owner->hasMany(false), $obj, $reciprocalRelation, $fields); | ||
if (!$link) { | ||
throw new LogicException('Could not produce an edit link for the passed object.'); | ||
} | ||
return $link; | ||
} | ||
|
||
/** | ||
* Get a link to edit this DataObject in the CMS. | ||
*/ | ||
public function CMSEditLink(): string | ||
{ | ||
$owner = $this->owner->getCMSEditOwner(); | ||
if (!$owner || !$owner->exists()) { | ||
return ''; | ||
} | ||
|
||
if (!$owner->hasMethod('getCMSEditLinkForManagedDataObject')) { | ||
throw new LogicException('The cms edit owner must implement getCMSEditLinkForManagedDataObject()'); | ||
} | ||
|
||
if ($owner instanceof DataObject) { | ||
$relativeLink = $owner->getCMSEditLinkForManagedDataObject($this->owner, $this->owner->config()->get('cms_edit_owner')); | ||
} else { | ||
$relativeLink = $owner->getCMSEditLinkForManagedDataObject($this->owner); | ||
} | ||
return Director::absoluteURL($relativeLink); | ||
} | ||
|
||
private function getCMSEditLinkForRelation(array $componentConfig, DataObject $obj, string $reciprocalRelation, FieldList $fields): string | ||
{ | ||
$candidate = null; | ||
foreach ($componentConfig as $relation => $class) { | ||
// Check for dot notation being used to explicitly mark the reciprocal relation. | ||
$remoteField = null; | ||
if (strpos($class ?? '', '.') !== false) { | ||
list($class, $remoteField) = explode('.', $class ?? ''); | ||
} | ||
|
||
// We're only interested in relations to the $obj class. | ||
if (!is_a($obj, $class)) { | ||
continue; | ||
} | ||
|
||
if ($remoteField) { | ||
if ($remoteField === $reciprocalRelation) { | ||
// We've found a direct reciprocal relation, so this is definitely correct. | ||
if ($this->relationIsEditable($relation, $fields)) { | ||
return $this->constructLink($relation, $obj->ID); | ||
} | ||
// If the relation isn't in a gridfield, we have no link for it. | ||
return ''; | ||
} | ||
// We're not interested in unrelated relations. | ||
continue; | ||
} | ||
|
||
// Check for relations that have gridfields we can build a link from. | ||
if ($this->relationIsEditable($relation, $fields)) { | ||
$candidate = $relation; | ||
} | ||
} | ||
|
||
// Only do this if we didn't find a direct reciprocal relation. | ||
return $candidate ? $this->constructLink($candidate, $obj->ID) : ''; | ||
} | ||
|
||
private function relationIsEditable(string $relation, FieldList $fields): bool | ||
{ | ||
$field = $fields->dataFieldByName($relation); | ||
return $field | ||
&& $field instanceof GridField | ||
&& $field->getConfig()->getComponentByType(GridFieldDetailForm::class); | ||
} | ||
|
||
private function constructLink(string $relation, int $id): string | ||
{ | ||
$ownerType = $this->owner->config()->get('cms_edit_owner'); | ||
$prefix = is_a($ownerType, CMSMain::class, true) ? 'field' : 'ItemEditForm/field'; | ||
return Controller::join_links( | ||
$this->owner->CMSEditLink(), | ||
$prefix, | ||
$relation, | ||
'item', | ||
$id | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
<?php | ||
|
||
namespace SilverStripe\Admin\Tests; | ||
|
||
use LogicException; | ||
use SilverStripe\Admin\Tests\CMSEditLinkExtensionTest\BasicNestedObject; | ||
use SilverStripe\Admin\Tests\CMSEditLinkExtensionTest\BelongsToModelAdmin; | ||
use SilverStripe\Admin\Tests\CMSEditLinkExtensionTest\CMSEditModelAdmin; | ||
use SilverStripe\Admin\Tests\CMSEditLinkExtensionTest\NestedObject; | ||
use SilverStripe\Admin\Tests\CMSEditLinkExtensionTest\PolymorphicNestedObject; | ||
use SilverStripe\Dev\SapphireTest; | ||
|
||
class CMSEditLinkExtensionTest extends SapphireTest | ||
{ | ||
protected static $fixture_file = 'CMSEditLinkExtensionTest.yml'; | ||
|
||
protected $usesDatabase = true; | ||
|
||
protected static $extra_dataobjects = [ | ||
BelongsToModelAdmin::class, | ||
BasicNestedObject::class, | ||
NestedObject::class, | ||
PolymorphicNestedObject::class, | ||
]; | ||
|
||
protected static $extra_controllers = [ | ||
CMSEditModelAdmin::class, | ||
]; | ||
|
||
public function testGetCMSEditOwner() | ||
{ | ||
$adminSingleton = CMSEditModelAdmin::singleton(); | ||
$root = $this->objFromFixture(BelongsToModelAdmin::class, 'root'); | ||
$basicNested = $this->objFromFixture(BasicNestedObject::class, 'one'); | ||
$nested = $this->objFromFixture(NestedObject::class, 'one'); | ||
$polymorphic = $this->objFromFixture(PolymorphicNestedObject::class, 'one'); | ||
|
||
$this->assertSame($adminSingleton, $root->getCMSEditOwner()); | ||
$this->assertSame($root->ID, $basicNested->getCMSEditOwner()->ID); | ||
$this->assertSame($root->ID, $nested->getCMSEditOwner()->ID); | ||
$this->assertSame($root->ID, $polymorphic->getCMSEditOwner()->ID); | ||
} | ||
|
||
public function testGetEditLinkForDataObject() | ||
{ | ||
$root = $this->objFromFixture(BelongsToModelAdmin::class, 'root'); | ||
$basicNested = $this->objFromFixture(BasicNestedObject::class, 'one'); | ||
$nested = $this->objFromFixture(NestedObject::class, 'one'); | ||
$polymorphic = $this->objFromFixture(PolymorphicNestedObject::class, 'one'); | ||
|
||
$rootUrl = "http://localhost/admin/cms-edit-test/belongsHere/EditForm/field/belongsHere/item/$root->ID"; | ||
$this->assertSame( | ||
"$rootUrl/ItemEditForm/field/BasicNested/item/$basicNested->ID", | ||
$root->getCMSEditLinkForManagedDataObject($basicNested, 'Parent') | ||
); | ||
$this->assertSame( | ||
"$rootUrl/ItemEditForm/field/Nested/item/$nested->ID", | ||
$root->getCMSEditLinkForManagedDataObject($nested, 'Parent') | ||
); | ||
$this->assertSame( | ||
"$rootUrl/ItemEditForm/field/PolyMorphic/item/$polymorphic->ID", | ||
$root->getCMSEditLinkForManagedDataObject($polymorphic, 'Parent') | ||
); | ||
} | ||
|
||
public function testGetEditLinkForDataObjectException() | ||
{ | ||
$root = $this->objFromFixture(BelongsToModelAdmin::class, 'root'); | ||
$nested = $this->objFromFixture(NestedObject::class, 'redHerringOne'); | ||
|
||
$this->expectException(LogicException::class); | ||
$this->assertNull($root->getCMSEditLinkForManagedDataObject($nested, 'AnotherOfTheSameClass')); | ||
} | ||
|
||
public function testCMSEditLink() | ||
{ | ||
$root = $this->objFromFixture(BelongsToModelAdmin::class, 'root'); | ||
$basicNested = $this->objFromFixture(BasicNestedObject::class, 'one'); | ||
$nested = $this->objFromFixture(NestedObject::class, 'one'); | ||
$polymorphic = $this->objFromFixture(PolymorphicNestedObject::class, 'one'); | ||
|
||
$rootUrl = "http://localhost/admin/cms-edit-test/belongsHere/EditForm/field/belongsHere/item/$root->ID"; | ||
$this->assertSame($rootUrl, $root->CMSEditLink()); | ||
$this->assertSame( | ||
"$rootUrl/ItemEditForm/field/BasicNested/item/$basicNested->ID", | ||
$basicNested->CMSEditLink() | ||
); | ||
$this->assertSame( | ||
"$rootUrl/ItemEditForm/field/Nested/item/$nested->ID", | ||
$nested->CMSEditLink() | ||
); | ||
$this->assertSame( | ||
"$rootUrl/ItemEditForm/field/PolyMorphic/item/$polymorphic->ID", | ||
$polymorphic->CMSEditLink() | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
SilverStripe\Admin\Tests\CMSEditLinkExtensionTest\BasicNestedObject: | ||
one: | ||
Name: 'some name' | ||
|
||
SilverStripe\Admin\Tests\CMSEditLinkExtensionTest\PolymorphicNestedObject: | ||
one: | ||
Name: 'some name' | ||
|
||
SilverStripe\Admin\Tests\CMSEditLinkExtensionTest\NestedObject: | ||
one: | ||
Name: 'some name' | ||
redHerringOne: | ||
Name: 'This exists so there is a record in an edge-case relation' | ||
redHerringTwo: | ||
Name: 'This exists so there is a record in an edge-case relation' | ||
|
||
SilverStripe\Admin\Tests\CMSEditLinkExtensionTest\BelongsToModelAdmin: | ||
redHerringOne: | ||
Name: 'This exists so we know it doesnt just grab the first record' | ||
root: | ||
Name: 'this is the record we care about' | ||
Nested: | ||
- '=>SilverStripe\Admin\Tests\CMSEditLinkExtensionTest\NestedObject.one' | ||
ArbitraryRelation: | ||
- '=>SilverStripe\Admin\Tests\CMSEditLinkExtensionTest\NestedObject.redHerringOne' | ||
AnotherArbitraryRelation: | ||
- '=>SilverStripe\Admin\Tests\CMSEditLinkExtensionTest\NestedObject.redHerringTwo' | ||
BasicNested: | ||
- '=>SilverStripe\Admin\Tests\CMSEditLinkExtensionTest\BasicNestedObject.one' | ||
PolyMorphic: | ||
- '=>SilverStripe\Admin\Tests\CMSEditLinkExtensionTest\PolymorphicNestedObject.one' | ||
redHerringTwo: | ||
Name: 'This exists so we know it doesnt just grab the last record' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
<?php | ||
|
||
namespace SilverStripe\Admin\Tests\CMSEditLinkExtensionTest; | ||
|
||
use SilverStripe\Admin\CMSEditLinkExtension; | ||
use SilverStripe\Dev\TestOnly; | ||
use SilverStripe\ORM\DataObject; | ||
|
||
class BasicNestedObject extends DataObject implements TestOnly | ||
{ | ||
private static $table_name = 'CMSEditLinkTest_BasicNestedObject'; | ||
|
||
private static $cms_edit_owner = 'Parent'; | ||
|
||
private static $db = [ | ||
'Name' => 'Varchar(255)', | ||
]; | ||
|
||
private static $has_one = [ | ||
'Parent' => BelongsToModelAdmin::class, | ||
]; | ||
|
||
private static $extensions = [ | ||
CMSEditLinkExtension::class, | ||
]; | ||
} |
37 changes: 37 additions & 0 deletions
37
tests/php/CMSEditLinkExtensionTest/BelongsToModelAdmin.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
<?php | ||
|
||
namespace SilverStripe\Admin\Tests\CMSEditLinkExtensionTest; | ||
|
||
use SilverStripe\Admin\CMSEditLinkExtension; | ||
use SilverStripe\Dev\TestOnly; | ||
use SilverStripe\ORM\DataObject; | ||
|
||
class BelongsToModelAdmin extends DataObject implements TestOnly | ||
{ | ||
private static $table_name = 'CMSEditLinkTest_BelongsToModelAdmin'; | ||
|
||
private static $cms_edit_owner = CMSEditModelAdmin::class; | ||
|
||
private static $db = [ | ||
'Name' => 'Varchar(255)', | ||
]; | ||
|
||
private static $has_many = [ | ||
'ArbitraryRelation' => NestedObject::class, | ||
'Nested' => NestedObject::class . '.Parent', | ||
'AnotherArbitraryRelation' => NestedObject::class . '.AnotherOfTheSameClass', | ||
'BasicNested' => BasicNestedObject::class, | ||
'PolyMorphic' => PolymorphicNestedObject::class, | ||
]; | ||
|
||
public function getCMSFields() | ||
{ | ||
$fields = parent::getCMSFields(); | ||
$fields->removeByName(['ArbitraryRelation', 'AnotherArbitraryRelation']); | ||
return $fields; | ||
} | ||
|
||
private static $extensions = [ | ||
CMSEditLinkExtension::class, | ||
]; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
<?php | ||
|
||
namespace SilverStripe\Admin\Tests\CMSEditLinkExtensionTest; | ||
|
||
use SilverStripe\Admin\ModelAdmin; | ||
use SilverStripe\Dev\TestOnly; | ||
|
||
class CMSEditModelAdmin extends ModelAdmin implements TestOnly | ||
{ | ||
private static $url_segment = 'cms-edit-test'; | ||
|
||
private static $managed_models = [ | ||
'belongsHere' => BelongsToModelAdmin::class, | ||
]; | ||
} |
Oops, something went wrong.