From e9cf57677f4bed2ef2a9d74919c6addb66168d69 Mon Sep 17 00:00:00 2001 From: Sabina Talipova Date: Fri, 22 Apr 2022 09:44:21 +1200 Subject: [PATCH] CMSEditLink method modified to support DataObject links and Example of using ElemenatalArea with DataObject --- .../examples/elementalarea_with_dataobject.md | 111 +++++++++++++ src/Models/BaseElement.php | 82 ++++++--- tests/BaseElementTest.php | 155 +++++++++++++++++- .../add-block-element-to-data-object.feature | 44 +++++ tests/ElementalAreaDataObjectTest.yml | 44 +++++ tests/Src/TestDataObject.php | 27 +++ tests/Src/TestDataObjectWithCMSEditLink.php | 45 +++++ tests/Src/TestElementDataObject.php | 22 +++ 8 files changed, 505 insertions(+), 25 deletions(-) create mode 100644 docs/en/examples/elementalarea_with_dataobject.md create mode 100644 tests/Behat/features/add-block-element-to-data-object.feature create mode 100644 tests/ElementalAreaDataObjectTest.yml create mode 100644 tests/Src/TestDataObject.php create mode 100644 tests/Src/TestDataObjectWithCMSEditLink.php create mode 100644 tests/Src/TestElementDataObject.php diff --git a/docs/en/examples/elementalarea_with_dataobject.md b/docs/en/examples/elementalarea_with_dataobject.md new file mode 100644 index 000000000..3ceab5686 --- /dev/null +++ b/docs/en/examples/elementalarea_with_dataobject.md @@ -0,0 +1,111 @@ +# Using ElementalArea with DataObject + +## Creating Model and adding ElementalArea + +In this example the class `BlogPost` will be created, it will extend the `DataObject` class. Also a new `BlogPost` Admin section will be created in the Admin panel where CMS users can manage the `BlogPost` objects. + +Let's look at an example: + +**app/src/Admins/BlogPostsAdmin.php** + +```php + ElementalArea::class, + ]; + + private static $owns = ['ElementalArea']; + + // ... +} + ``` + +If you are using `ElementalArea` together with `DataObject`, it is important to define the `CMSEditLink()` method in the class.\ +`BaseElement::CMSEditLink()` relies on a valid CMS link being available in the parent `DataObject` - in this case, `BlogPost`. It is used to navigate directly to the editing section of the particular element.\ +***Note:*** For nested `GridField`s this method can get more complicated. Similarly, if your class is used in multiple admins, you will have to choose one to be the canonical admin section for the purposes of this method. This example only shows the simplest case, where the `BlogPost` class is used directly in `BlogPostsAdmin`. + +```php +// ... + +class BlogPost extends DataObject +{ + // ... + + public function CMSEditLink() + { + // In this example we use BlogPostsAdmin class as Controller + $admin = BlogPostsAdmin::singleton(); + + // Makes link more readable. Instead App\Models\ BlogPost, we get App-Models-BlogPost + $sanitisedClassname = str_replace('\\', '-', $this->ClassName); + + // Returns link to editing section with elements + return Controller::join_links( + $admin->Link($sanitisedClassname), + 'EditForm/field/', + $sanitisedClassname, + 'item', + $this->ID, + ); + } + + // ... +} + +``` + +And finally, add `ElementalAreasExtension` to the `DataObject` + +**app/_config/elemental.yml** + +```yml +App\Models\BlogPost: + extensions: + - DNADesign\Elemental\Extensions\ElementalAreasExtension +``` + +## Related Documentation + +* [Preview](https://docs.silverstripe.org/en/4/developer_guides/customising_the_admin_interface/preview/) +(note that this requires at least `silverstripe/admin` 4.11.0 and `dnadesign/silverstripe-elemental` 4.9.0) \ No newline at end of file diff --git a/src/Models/BaseElement.php b/src/Models/BaseElement.php index 3cb20a215..70d81f744 100644 --- a/src/Models/BaseElement.php +++ b/src/Models/BaseElement.php @@ -243,13 +243,18 @@ public function canEdit($member = null) public function canDelete($member = null) { $extended = $this->extendedCan(__FUNCTION__, $member); + if ($extended !== null) { return $extended; } if ($this->hasMethod('getPage')) { if ($page = $this->getPage()) { - return $page->canArchive($member); + if ($page->hasExtension(Versioned::class)) { + return $page->canArchive($member); + } else { + return $page->canDelete($member); + } } } @@ -568,7 +573,8 @@ public function getSimpleClassName() } /** - * @return null|SiteTree + * Despite the name of the method, getPage can return any type of DataObject + * @return null|DataObject * @throws \Psr\Container\NotFoundExceptionInterface * @throws \SilverStripe\ORM\ValidationException */ @@ -579,7 +585,6 @@ public function getPage() return $this->cacheData['page']; } - $class = DataObject::getSchema()->hasOneComponent($this, 'Parent'); $area = ($this->ParentID) ? DataObject::get_by_id($class, $this->ParentID) : null; if ($area instanceof ElementalArea && $area->exists()) { @@ -638,9 +643,10 @@ public function getAnchor() */ public function AbsoluteLink($action = null) { - if ($page = $this->getPage()) { + $page = $this->getPage(); + + if ($page && ClassInfo::hasMethod($page, 'AbsoluteLink')) { $link = $page->AbsoluteLink($action) . '#' . $this->getAnchor(); - $this->extend('updateAbsoluteLink', $link); return $link; @@ -657,9 +663,10 @@ public function AbsoluteLink($action = null) */ public function Link($action = null) { - if ($page = $this->getPage()) { - $link = $page->Link($action) . '#' . $this->getAnchor(); + $page = $this->getPage(); + if ($page && ClassInfo::hasMethod($page, 'Link')) { + $link = $page->Link($action) . '#' . $this->getAnchor(); $this->extend('updateLink', $link); return $link; @@ -712,35 +719,62 @@ public function CMSEditLink($directLink = false) return $this->cacheData['cms_edit_link']; } + $link = $this->getElementCMSLink($directLink); + $this->extend('updateCMSEditLink', $link); + + if ($link) { + $this->cacheData['cms_edit_link'] = $link; + } + + return $link; + } + + /** + * @param bool $directLink + * @return null|string + */ + private function getElementCMSLink(bool $directLink) + { $relationName = $this->getAreaRelationName(); $page = $this->getPage(); + $link = null; + if (!$page) { - $link = null; - $this->extend('updateCMSEditLink', $link); return $link; } - if (!$page instanceof SiteTree && method_exists($page, 'CMSEditLink')) { - $link = Controller::join_links($page->CMSEditLink(), 'ItemEditForm'); - } else { + if ($page instanceof SiteTree) { $link = $page->CMSEditLink(); + } elseif (ClassInfo::hasMethod($page, 'CMSEditLink')) { + $link = Controller::join_links($page->CMSEditLink(), 'ItemEditForm'); } - - // In-line editable blocks should just take you to the page. Editable ones should add the suffix for detail form + // In-line editable blocks should just take you to the page. + // Editable ones should add the suffix for detail form. if (!$this->inlineEditable() || $directLink) { - $link = Controller::join_links( - singleton(CMSPageEditController::class)->Link('EditForm'), - $page->ID, - 'field/' . $relationName . '/item/', - $this->ID, - 'edit' - ); + if ($page instanceof SiteTree) { + return Controller::join_links( + singleton(CMSPageEditController::class)->Link('EditForm'), + $page->ID, + 'field', + $relationName, + 'item', + $this->ID, + 'edit' + ); + } else { + // If $page is not a Page, then generate $link base on $page->CMSEditLink() + return Controller::join_links( + $link, + 'field', + $relationName, + 'item', + $this->ID, + 'edit' + ); + } } - $this->extend('updateCMSEditLink', $link); - - $this->cacheData['cms_edit_link'] = $link; return $link; } diff --git a/tests/BaseElementTest.php b/tests/BaseElementTest.php index dee091143..21a49ed63 100644 --- a/tests/BaseElementTest.php +++ b/tests/BaseElementTest.php @@ -7,9 +7,13 @@ use DNADesign\Elemental\Models\BaseElement; use DNADesign\Elemental\Models\ElementalArea; use DNADesign\Elemental\Models\ElementContent; +use DNADesign\Elemental\Tests\Src\TestDataObject; use DNADesign\Elemental\Tests\Src\TestElement; +use DNADesign\Elemental\Tests\Src\TestElementDataObject; +use DNADesign\Elemental\Tests\Src\TestDataObjectWithCMSEditLink; use DNADesign\Elemental\Tests\Src\TestPage; use Page; +use ReflectionClass; use SilverStripe\Control\Director; use SilverStripe\Core\Config\Config; use SilverStripe\Dev\FunctionalTest; @@ -18,17 +22,29 @@ class BaseElementTest extends FunctionalTest { - protected static $fixture_file = 'ElementalPageExtensionTest.yml'; + protected static $fixture_file = [ + 'ElementalPageExtensionTest.yml', + 'ElementalAreaDataObjectTest.yml' + ]; protected static $required_extensions = [ Page::class => [ ElementalPageExtension::class, ], + TestDataObject::class => [ + ElementalPageExtension::class, + ], + TestDataObjectWithCMSEditLink::class => [ + ElementalPageExtension::class, + ], ]; protected static $extra_dataobjects = [ TestPage::class, TestElement::class, + TestDataObject::class, + TestDataObjectWithCMSEditLink::class, + TestElementDataObject::class, ]; public function testSimpleClassName() @@ -236,4 +252,141 @@ public function testOnBeforeWriteNoParent() $this->assertEquals(0, (int) $element1->Sort); } + + public function getElementCMSLinkDataProvider() + { + return [ + // Element in DataObject with $directLink === true + 'element1' => [ + TestElement::class, + 'elementDataObject1', + 'http://localhost/admin/1/ItemEditForm/field/ElementalArea/item/', + true + ], + // Element in DataObject with $inline_editable = false + 'element2' => [ + TestElementDataObject::class, + 'testElementDataObject1', + 'http://localhost/admin/1/ItemEditForm/field/ElementalArea/item/', + ], + // Element in DataObject with $inline_editable = true + 'element3' => [ + ElementContent::class, + 'contentDataObject1', + 'http://localhost/admin/1/ItemEditForm', + ], + // Element in Page with $inline_editable = true + 'element4' => [ + ElementContent::class, + 'content1', + 'http://localhost/admin/pages/edit/show/1', + ], + // Element in DataObject with $directLink === true + 'element5' => [ + ElementContent::class, + 'content1', + 'admin/pages/edit/EditForm/1/field/ElementalArea/item/1/edit', + true + ], + // Element without any Page or DataObject + 'element6' => [ + ElementContent::class, + 'contentDataObject2', + null + ], + // DataObject without CMSEditLink method + 'element7' => [ + TestElement::class, + 'elementDataObject2', + null + ], + ]; + } + + /** + * @dataProvider getElementCMSLinkDataProvider + */ + public function testCMSEditLink(string $class, string $element, ?string $link, bool $directLink = false) + { + $object = $this->objFromFixture($class, $element); + $editLink = $object->CMSEditLink($directLink); + + if ($link) { + $this->assertStringContainsString($link, $editLink); + } else { + $this->assertNull($link); + } + } + + public function canDeleteProvider() + { + return [ + // Element on Page + 'element1' => [ + ElementContent::class, + 'content1', + 'ADMIN', + ], + // Element in DataObject + 'element2' => [ + TestElement::class, + 'elementDataObject1', + 'CMS_ACCESS_CMSMain', + ], + ]; + } + + /** + * @dataProvider canDeleteProvider + */ + public function testCanDelete( + string $class, + string $element, + string $permission + ) { + $this->logOut(); + $this->logInWithPermission($permission); + $object = $this->objFromFixture($class, $element); + $canDelete = $object->canDelete(); + $this->assertNotNull($canDelete); + $this->assertTrue($canDelete); + } + + public function linksProvider() + { + return [ + // Element on Page + 'element1' => [ + ElementContent::class, + 'content1', + '/test-elemental/#e1', + ], + // Element in DataObject + 'element2' => [ + TestElement::class, + 'elementDataObject1', + null, + ], + ]; + } + + /** + * @dataProvider linksProvider + */ + public function testLinkWithDataObject(string $class, string $element, ?string $link) + { + $object = $this->objFromFixture($class, $element); + $this->assertEquals($link, $object->Link()); + } + + /** + * @dataProvider linksProvider + */ + public function testAbsoluteLink(string $class, string $element, ?string $link) + { + $link = $link ? Director::absoluteURL($link) : $link; + $object = $this->objFromFixture($class, $element); + $absoluteLink = $object->AbsoluteLink(); + $this->assertEquals($link, $absoluteLink); + } } diff --git a/tests/Behat/features/add-block-element-to-data-object.feature b/tests/Behat/features/add-block-element-to-data-object.feature new file mode 100644 index 000000000..b2cf5239b --- /dev/null +++ b/tests/Behat/features/add-block-element-to-data-object.feature @@ -0,0 +1,44 @@ +@javascript +Feature: Add elements in the CMS DataObject + As a CMS user + I want to add elements in the CMS DataObject + So that I can use multiple elements in a Dataobject + + Background: + Given I am logged in with "ADMIN" permissions + + Scenario: I can add inline-editable elements to the DataObject + When I go to "/admin/elemental-behat-test-admin/" + Then I press the "Add Elemental Behat Test Object" button + Then I press the "Create" button + Then I wait 1 second + Then I press the "Add block" button + Then I press the "Content" button in the add block popover + Then I should see "Untitled Content block" as the title for block 1 + When I click on block 1 + And I should see "Title" + And I should see "Content" + And I fill in "Elemental Behat Test Title" for "Title" for block 1 + Then I press the "Save" button + Then I should see "Elemental Behat Test Title" as the title for block 1 + + Scenario: I can add non-inline-editable elements to the DataObject + Given content blocks are not in-line editable + When I go to "/admin/elemental-behat-test-admin/" + Then I press the "Add Elemental Behat Test Object" button + Then I press the "Create" button + Then I wait 1 second + Then I press the "Add block" button + Then I press the "Content" button in the add block popover + When I click on block 1 + And I should see "Title" + And I should see "Content" + + Given I fill in "New Elemental Behat Test Title" for "Title" + And I fill in "

New sample content

" for the "Content" HTML field + And I press the "Publish" button + Then I should see a "Published content block" message + Then I should see "New Elemental Behat Test Title" + And the "Content" field should contain "

New sample content

" + Then I press the "Navigate up a folder" button + Then I should see "New Elemental Behat Test Title" as the title for block 1 diff --git a/tests/ElementalAreaDataObjectTest.yml b/tests/ElementalAreaDataObjectTest.yml new file mode 100644 index 000000000..3763e40cd --- /dev/null +++ b/tests/ElementalAreaDataObjectTest.yml @@ -0,0 +1,44 @@ +DNADesign\Elemental\Models\ElementalArea: + areaDataObject1: + Title: Area 1 + OwnerClassName: DNADesign\Elemental\Tests\Src\TestDataObjectWithCMSEditLink + areaDataObject2: + Title: Area 2 + OwnerClassName: DNADesign\Elemental\Tests\Src\TestDataObject + areaWithoutPage: + Title: Area without page + +DNADesign\Elemental\Tests\Src\TestDataObjectWithCMSEditLink: + dataObject1: + Title: DataObject with CMSEditLink method + ElementalAreaID: =>DNADesign\Elemental\Models\ElementalArea.areaDataObject1 + +DNADesign\Elemental\Tests\Src\TestDataObject: + dataObject2: + Title: DataObject without CMSEditLink method + ElementalAreaID: =>DNADesign\Elemental\Models\ElementalArea.areaDataObject2 + +DNADesign\Elemental\Tests\Src\TestElement: + elementDataObject1: + Title: Element 1 + TestValue: 'Hello Test' + ParentID: =>DNADesign\Elemental\Models\ElementalArea.areaDataObject1 + elementDataObject2: + Title: Element 2 + TestValue: 'Hello Test' + ParentID: =>DNADesign\Elemental\Models\ElementalArea.areaDataObject2 + +DNADesign\Elemental\Tests\Src\TestElementDataObject: + testElementDataObject1: + Title: Test Element inline-editable + TestValue: 'Hello Test' + ParentID: =>DNADesign\Elemental\Models\ElementalArea.areaDataObject1 + +DNADesign\Elemental\Models\ElementContent: + contentDataObject1: + HTML: Some content + ParentID: =>DNADesign\Elemental\Models\ElementalArea.areaDataObject1 + contentDataObject2: + HTML: Some content + ParentID: =>DNADesign\Elemental\Models\ElementalArea.areaWithoutPage + \ No newline at end of file diff --git a/tests/Src/TestDataObject.php b/tests/Src/TestDataObject.php new file mode 100644 index 000000000..caaa56574 --- /dev/null +++ b/tests/Src/TestDataObject.php @@ -0,0 +1,27 @@ + 'Varchar(255)', + 'Content' => 'HTMLText', + ]; + + private static $has_one = [ + 'ElementalArea' => ElementalArea::class, + ]; + + private static $owns = [ + 'ElementalArea', + ]; +} diff --git a/tests/Src/TestDataObjectWithCMSEditLink.php b/tests/Src/TestDataObjectWithCMSEditLink.php new file mode 100644 index 000000000..ab9586389 --- /dev/null +++ b/tests/Src/TestDataObjectWithCMSEditLink.php @@ -0,0 +1,45 @@ + 'Varchar(255)', + 'Content' => 'HTMLText', + ]; + + private static $has_one = [ + 'ElementalArea' => ElementalArea::class, + ]; + + private static $owns = [ + 'ElementalArea', + ]; + + public function CMSEditLink() + { + $link = Controller::join_links( + 'admin/', + $this->ID, + ); + return Director::absoluteURL($link); + } + + public function canDelete($member = null) + { + $member = $member ? $member : Security::getCurrentUser(); + $codes = ['CMS_ACCESS_CMSMain']; + return Permission::checkMember($member, $codes); + } +} diff --git a/tests/Src/TestElementDataObject.php b/tests/Src/TestElementDataObject.php new file mode 100644 index 000000000..c3a527485 --- /dev/null +++ b/tests/Src/TestElementDataObject.php @@ -0,0 +1,22 @@ + 'Text', + ]; + + private static bool $inline_editable = false; + + public function getType() + { + return 'A test element in DataObject'; + } +}