diff --git a/src/Models/EmailLink.php b/src/Models/EmailLink.php index d5fd21a8..e0228f32 100644 --- a/src/Models/EmailLink.php +++ b/src/Models/EmailLink.php @@ -3,6 +3,7 @@ namespace SilverStripe\LinkField\Models; use SilverStripe\Forms\EmailField; +use SilverStripe\Forms\FieldList; /** * A link to an Email address. @@ -24,7 +25,7 @@ public function generateLinkDescription(array $data): string return isset($data['Email']) ? $data['Email'] : ''; } - public function getCMSFields() + public function getCMSFields(): FieldList { $fields = parent::getCMSFields(); diff --git a/src/Models/Link.php b/src/Models/Link.php index 16c4020d..3e4b6e6a 100644 --- a/src/Models/Link.php +++ b/src/Models/Link.php @@ -3,8 +3,14 @@ namespace SilverStripe\LinkField\Models; use InvalidArgumentException; +use ReflectionException; +use SilverStripe\Core\ClassInfo; use SilverStripe\Core\Injector\Injector; +use SilverStripe\Forms\CompositeValidator; +use SilverStripe\Forms\DropdownField; use SilverStripe\Forms\FieldList; +use SilverStripe\Forms\Form; +use SilverStripe\Forms\RequiredFields; use SilverStripe\LinkField\JsonData; use SilverStripe\LinkField\Type\Registry; use SilverStripe\LinkField\Type\Type; @@ -28,6 +34,15 @@ class Link extends DataObject implements JsonData, Type 'OpenInNew' => 'Boolean' ]; + /** + * In-memory only property used to change link type + * This case is relevant for CMS edit form which doesn't use React driven UI + * This is a workaround as changing the ClassName directly is not fully supported in the GridField admin + * + * @var string + */ + private $linkType; + public function defineLinkTypeRequirements() { @@ -51,11 +66,80 @@ public function LinkTypeTile(): string return $this->i18n_singular_name(); } + /** + * @param array $data + * @return FieldList + * @throws ReflectionException + */ public function scaffoldLinkFields(array $data): FieldList { return $this->getCMSFields(); } + /** + * @return FieldList + * @throws ReflectionException + */ + public function getCMSFields(): FieldList + { + $fields = parent::getCMSFields(); + $linkTypes = $this->getLinkTypes(); + + if (static::class === self::class) { + // Add a link type selection field for generic links + $fields->addFieldsToTab( + 'Root.Main', + [ + $linkTypeField = DropdownField::create('LinkType', 'Link Type', $linkTypes), + ], + 'Title' + ); + + $linkTypeField->setEmptyString('-- select type --'); + } + + return $fields; + } + + /** + * @return CompositeValidator + */ + public function getCMSCompositeValidator(): CompositeValidator + { + $validator = parent::getCMSCompositeValidator(); + + if (static::class === self::class) { + // Make Link type mandatory for generic links + $validator->addValidator(RequiredFields::create([ + 'LinkType', + ])); + } + + return $validator; + } + + /** + * Form hook defined in @see Form::saveInto() + * We use this to work with an in-memory only field + * + * @param $value + */ + public function saveLinkType($value) + { + $this->linkType = $value; + } + + public function onBeforeWrite(): void + { + // Detect link type change and update the class accordingly + if ($this->linkType && DataObject::singleton($this->linkType) instanceof Link) { + $this->setClassName($this->linkType); + $this->populateDefaults(); + $this->forceChange(); + } + + parent::onBeforeWrite(); + } function setData($data): JsonData { @@ -142,4 +226,26 @@ public function forTemplate() { return $this->renderWith([self::class]); } + + /** + * Get all link types except the generic one + * + * @return array + * @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; + } } diff --git a/src/Models/SiteTreeLink.php b/src/Models/SiteTreeLink.php index 3aa278e5..e4e59660 100644 --- a/src/Models/SiteTreeLink.php +++ b/src/Models/SiteTreeLink.php @@ -4,6 +4,7 @@ use SilverStripe\CMS\Forms\AnchorSelectorField; use SilverStripe\CMS\Model\SiteTree; +use SilverStripe\Forms\FieldList; use SilverStripe\Forms\TreeDropdownField; /** @@ -38,7 +39,7 @@ public function generateLinkDescription(array $data): string return $page ? $page->URLSegment : ''; } - public function getCMSFields() + public function getCMSFields(): FieldList { $fields = parent::getCMSFields(); diff --git a/tests/php/Models/LinkTest.php b/tests/php/Models/LinkTest.php index 90aaabac..e0887792 100644 --- a/tests/php/Models/LinkTest.php +++ b/tests/php/Models/LinkTest.php @@ -2,10 +2,15 @@ namespace SilverStripe\LinkField\Tests\Models; +use ReflectionException; use SilverStripe\CMS\Model\SiteTree; use SilverStripe\Dev\SapphireTest; +use SilverStripe\LinkField\Models\EmailLink; +use SilverStripe\LinkField\Models\ExternalLink; +use SilverStripe\LinkField\Models\FileLink; use SilverStripe\LinkField\Models\Link; use SilverStripe\LinkField\Models\SiteTreeLink; +use SilverStripe\ORM\DataObject; use SilverStripe\ORM\ValidationException; class LinkTest extends SapphireTest @@ -47,4 +52,32 @@ public function testSiteTreeLinkTitleFallback(): void $this->assertEquals($customTitle, $model->Title, 'We expect to get the custom title not page title'); } + + /** + * @param string $class + * @param bool $expected + * @throws ReflectionException + * @dataProvider linkTypeProvider + */ + public function testLinkType(string $class, bool $expected): void + { + /** @var Link $model */ + $model = DataObject::singleton($class); + $fields = $model->getCMSFields(); + $linkTypeField = $fields->fieldByName('Root.Main.LinkType'); + $expected + ? $this->assertNotNull($linkTypeField, 'We expect to a find link type field') + : $this->assertNull($linkTypeField, 'We do not expect to a find link type field'); + } + + public function linkTypeProvider(): array + { + return [ + [EmailLink::class, false], + [ExternalLink::class, false], + [FileLink::class, false], + [SiteTreeLink::class, false], + [Link::class, true], + ]; + } }