From 0c0cdaaee4dccf1c9b34ac7515b1fd7f6f8cda2d Mon Sep 17 00:00:00 2001 From: Steve Boyd Date: Wed, 14 Feb 2024 12:20:45 +1300 Subject: [PATCH] NEW Globally disallow link types --- README.md | 32 ++++++-- src/Form/AbstractLinkField.php | 5 +- src/Models/Link.php | 28 ++----- tests/php/Form/AbstractLinkFieldTest.php | 76 +++++++++++++++++++ ...ieldTest.yml => AbstractLinkFieldTest.yml} | 2 +- .../TestBlock.php | 4 +- tests/php/Form/LinkFieldTest.php | 39 ---------- 7 files changed, 117 insertions(+), 69 deletions(-) create mode 100644 tests/php/Form/AbstractLinkFieldTest.php rename tests/php/Form/{LinkFieldTest.yml => AbstractLinkFieldTest.yml} (80%) rename tests/php/Form/{LinkFieldTest => AbstractLinkFieldTest}/TestBlock.php (71%) delete mode 100644 tests/php/Form/LinkFieldTest.php diff --git a/README.md b/README.md index f8942d6f..4b470a8a 100644 --- a/README.md +++ b/README.md @@ -104,17 +104,39 @@ class ExternalLinkExtension extends Extension ## Controlling what type of links can be created in a LinkField -By default, all `Link` subclasses can be created by a LinkField. This includes any custom `Link` subclasses defined in your projects or via third party module. -Developers can control the link types allowed for individual `LinkField`. The `setAllowedTypes` method only allow link types that have been provided as parameters. +By default, all `Link` subclasses can be created by a `LinkField`. This includes any custom `Link` subclasses defined in your project or via a third party module. + +If you wish to globally disable one of the default `Link` subclasses for all `LinkField` instances, then this can be done using the following YAML configuration with the FQCN (Fully-Qualified Class Name) of the relevant default `Link` subclass you wish to disable: + +```yml +SilverStripe\LinkField\Models\SiteTreeLink: + allowed_by_default: false +``` + +You can also apply this configuration to any of your own custom `Link` subclasses: + +```php +namespace App\Links; + +use SilverStripe\LinkField\Models\Link; + +class MyCustomLink extends Link +{ + // ... + private static bool $allowed_by_default = false; +} +``` + +Developers can control the link types allowed for individual `LinkField`. The `setAllowedTypes()` method only allow link types that have been provided as parameters. This method will override the `allowed_by_default` configuration. ```php $fields->addFieldsToTab( 'Root.Main', [ + LinkField::create('EmailLink') + ->setAllowedTypes([EmailLink::class]), MultiLinkField::create('PageLinkList') - ->setAllowedTypes([ SiteTreeLink::class ]), - Link::create('EmailLink') - ->setAllowedTypes([ EmailLink::class ]), + ->setAllowedTypes([SiteTreeLink::class, EmailLink::class]), ], ); ``` diff --git a/src/Form/AbstractLinkField.php b/src/Form/AbstractLinkField.php index f2547b2d..cbd24ae9 100644 --- a/src/Form/AbstractLinkField.php +++ b/src/Form/AbstractLinkField.php @@ -6,6 +6,7 @@ use DNADesign\Elemental\Models\BaseElement; use InvalidArgumentException; use LogicException; +use SilverStripe\Core\Config\Config; use SilverStripe\Core\Injector\Injector; use SilverStripe\Forms\Form; use SilverStripe\Forms\FormField; @@ -183,7 +184,9 @@ private function generateAllowedTypes(): array $typeDefinitions = $this->getAllowedTypes() ?? []; if (empty($typeDefinitions)) { - return LinkTypeService::create()->generateAllLinkTypes(); + $allLinkTypes = LinkTypeService::create()->generateAllLinkTypes(); + $fn = fn ($className) => Config::inst()->get($className, 'allowed_by_default'); + return array_filter($allLinkTypes, $fn); } $result = array(); diff --git a/src/Models/Link.php b/src/Models/Link.php index 4324f97d..cc70e22a 100644 --- a/src/Models/Link.php +++ b/src/Models/Link.php @@ -57,6 +57,13 @@ class Link extends DataObject */ private static int $menu_priority = 100; + /** + * Whether this link type is allowed by default + * If this is set to `false` then this type of Link can still be manually allowed + * on a per field basis with AbstractLinkField::setAllowedTypes(); + */ + private static bool $allowed_by_default = true; + /** * The css class for the icon to display for this link type */ @@ -373,27 +380,6 @@ private function canPerformAction(string $canMethod, $member, $context = []) return parent::$canMethod($member, $context); } - /** - * Get all link types except the generic one - * - * @throws ReflectionException - */ - private function getLinkTypes(): array - { - $classes = ClassInfo::subclassesFor(self::class); - $types = []; - - foreach ($classes as $class) { - if ($class === self::class) { - continue; - } - - $types[$class] = ClassInfo::shortName($class); - } - - return $types; - } - public function getTitle(): string { // If we have link text, we can just bail out without any changes diff --git a/tests/php/Form/AbstractLinkFieldTest.php b/tests/php/Form/AbstractLinkFieldTest.php new file mode 100644 index 00000000..4330e944 --- /dev/null +++ b/tests/php/Form/AbstractLinkFieldTest.php @@ -0,0 +1,76 @@ +setFields(new FieldList([$field])); + $block = $this->objFromFixture(TestBlock::class, 'TestBlock01'); + $form->loadDataFrom($block); + $reflector = new ReflectionObject($field); + $method = $reflector->getMethod('getOwnerFields'); + $method->setAccessible(true); + $res = $method->invoke($field); + $this->assertEquals([ + 'ID' => $block->ID, + 'Class' => TestBlock::class, + 'Relation' => 'MyLink', + ], $res); + } + + public function testAllowedLinks(): void + { + // Ensure only default link subclasses are included this test + foreach (ClassInfo::subclassesFor(Link::class) as $className) { + if (strpos($className, 'SilverStripe\\LinkField\\Models\\') !== 0) { + Config::modify()->set($className, 'allowed_by_default', false); + } + } + // Test default allowed types + $field = new LinkField('MyLink'); + $keys = $this->getKeysForAllowedTypes($field); + $this->assertSame(['email', 'external', 'file', 'phone', 'sitetree'], $keys); + // Test can disallow globally + Config::modify()->set(PhoneLink::class, 'allowed_by_default', false); + $keys = $this->getKeysForAllowedTypes($field); + $this->assertSame(['email', 'external', 'file', 'sitetree'], $keys); + // Test can override with setAllowedTypes() + $field->setAllowedTypes([EmailLink::class, PhoneLink::class]); + $keys = $this->getKeysForAllowedTypes($field); + $this->assertSame(['email', 'phone'], $keys); + } + + private function getKeysForAllowedTypes(LinkField $field): array + { + $rawJson = $field->getTypesProp(); + $types = json_decode($rawJson, true); + $allowedTypes = array_filter($types, fn($type) => $type['allowed']); + $keys = array_column($allowedTypes, 'key'); + sort($keys); + return $keys; + } +} diff --git a/tests/php/Form/LinkFieldTest.yml b/tests/php/Form/AbstractLinkFieldTest.yml similarity index 80% rename from tests/php/Form/LinkFieldTest.yml rename to tests/php/Form/AbstractLinkFieldTest.yml index 46ba0010..a25702b9 100644 --- a/tests/php/Form/LinkFieldTest.yml +++ b/tests/php/Form/AbstractLinkFieldTest.yml @@ -2,6 +2,6 @@ SilverStripe\LinkField\Tests\Controllers\LinkFieldControllerTest\TestPhoneLink: TestPhoneLink01: Title: My phone link 01 Phone: 0123456790 -SilverStripe\LinkField\Tests\Form\LinkFieldTest\TestBlock: +SilverStripe\LinkField\Tests\Form\AbstractLinkFieldTest\TestBlock: TestBlock01: MyLink: =>SilverStripe\LinkField\Tests\Controllers\LinkFieldControllerTest\TestPhoneLink.TestPhoneLink01 diff --git a/tests/php/Form/LinkFieldTest/TestBlock.php b/tests/php/Form/AbstractLinkFieldTest/TestBlock.php similarity index 71% rename from tests/php/Form/LinkFieldTest/TestBlock.php rename to tests/php/Form/AbstractLinkFieldTest/TestBlock.php index a40cc1ac..13f59faa 100644 --- a/tests/php/Form/LinkFieldTest/TestBlock.php +++ b/tests/php/Form/AbstractLinkFieldTest/TestBlock.php @@ -1,6 +1,6 @@ Link::class, diff --git a/tests/php/Form/LinkFieldTest.php b/tests/php/Form/LinkFieldTest.php deleted file mode 100644 index 28be216c..00000000 --- a/tests/php/Form/LinkFieldTest.php +++ /dev/null @@ -1,39 +0,0 @@ -setFields(new FieldList([$field])); - $block = $this->objFromFixture(TestBlock::class, 'TestBlock01'); - $form->loadDataFrom($block); - $reflector = new ReflectionObject($field); - $method = $reflector->getMethod('getOwnerFields'); - $method->setAccessible(true); - $res = $method->invoke($field); - $this->assertEquals([ - 'ID' => $block->ID, - 'Class' => TestBlock::class, - 'Relation' => 'MyLink', - ], $res); - } -}