Skip to content

Commit

Permalink
NEW Globally disallow link types
Browse files Browse the repository at this point in the history
  • Loading branch information
emteknetnz committed Feb 13, 2024
1 parent 2bf91ca commit a4fb268
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 89 deletions.
32 changes: 27 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 projects or via 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, using the FQCN 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]),
],
);
```
Expand Down
23 changes: 13 additions & 10 deletions src/Form/AbstractLinkField.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -70,11 +71,11 @@ public function getAllowedTypes(): array
public function getTypesProp(): string
{
$typesList = [];
$typeDefinitions = $this->generateAllowedTypes();
$allowedTypes = $this->generateAllowedTypes();
$allTypes = LinkTypeService::create()->generateAllLinkTypes();
foreach ($allTypes as $key => $class) {
$type = Injector::inst()->get($class);
$allowed = array_key_exists($key, $typeDefinitions) && $type->canCreate();
$allowed = array_key_exists($key, $allowedTypes) && $type->canCreate();
$typesList[$key] = [
'key' => $key,
'title' => $type->getMenuTitle(),
Expand Down Expand Up @@ -180,17 +181,19 @@ protected function getOwnerFields(): array
*/
private function generateAllowedTypes(): array
{
$typeDefinitions = $this->getAllowedTypes() ?? [];
$allowedTypes = $this->getAllowedTypes() ?? [];

if (empty($typeDefinitions)) {
return LinkTypeService::create()->generateAllLinkTypes();
if (empty($allowedTypes)) {
$allLinkTypes = LinkTypeService::create()->generateAllLinkTypes();
$fn = fn ($className) => Config::inst()->get($className, 'allowed_by_default');
return array_filter($allLinkTypes, $fn);
}

$result = array();
foreach ($typeDefinitions as $class) {
if (is_subclass_of($class, Link::class)) {
$type = Injector::inst()->get($class)->getShortCode();
$result[$type] = $class;
$result = [];
foreach ($allowedTypes as $className) {
if (is_subclass_of($className, Link::class)) {
$shortCode = Injector::inst()->get($className)->getShortCode();
$result[$shortCode] = $className;
}
}
return $result;
Expand Down
28 changes: 7 additions & 21 deletions src/Models/Link.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 it can manually allowed by a call to
* AbstractLinkField::setAllowedTypes([MyCustomLinkType::class]);
*/
private static bool $allowed_by_default = true;

/**
* The css class for the icon to display for this link type
*/
Expand Down Expand Up @@ -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
Expand Down
22 changes: 11 additions & 11 deletions src/Services/LinkTypeService.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,17 @@ class LinkTypeService
*/
public function generateAllLinkTypes(): array
{
$typeDefinitions = ClassInfo::subclassesFor(Link::class);
$classNames = ClassInfo::subclassesFor(Link::class);

$result = array();
foreach ($typeDefinitions as $class) {
if (is_subclass_of($class, Link::class)) {
$type = Injector::inst()->get($class)->getShortCode();
$result[$type] = $class;
$allLinkTypes = [];
foreach ($classNames as $className) {
if (is_subclass_of($className, Link::class)) {
$shortCode = Injector::inst()->get($className)->getShortCode();
$allLinkTypes[$shortCode] = $className;
}
}

return $result;
return $allLinkTypes;
}

/**
Expand All @@ -40,8 +40,8 @@ public function generateAllLinkTypes(): array
*/
public function byKey(string $key): ?Link
{
$typeDefinitions = $this->generateAllLinkTypes();
$className = $typeDefinitions[$key] ?? null;
$linkTypes = $this->generateAllLinkTypes();
$className = $linkTypes[$key] ?? null;

if (!$className) {
return null;
Expand All @@ -56,9 +56,9 @@ public function byKey(string $key): ?Link
*/
public function keyByClassName(string $classname): ?string
{
$typeDefinitions = $this->generateAllLinkTypes();
$linkTypes = $this->generateAllLinkTypes();

foreach ($typeDefinitions as $key => $class) {
foreach ($linkTypes as $key => $class) {
if ($class === $classname) {
return $key;
}
Expand Down
75 changes: 75 additions & 0 deletions tests/php/Form/AbstractLinkFieldTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
<?php

namespace SilverStripe\LinkField\Tests\Form;

use SilverStripe\Dev\SapphireTest;
use SilverStripe\Forms\FieldList;
use SilverStripe\LinkField\Form\LinkField;
use SilverStripe\LinkField\Tests\Form\AbstractLinkFieldTest\TestBlock;
use SilverStripe\LinkField\Tests\Controllers\LinkFieldControllerTest\TestPhoneLink;
use SilverStripe\Forms\Form;
use ReflectionObject;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Config\Config;
use SilverStripe\LinkField\Models\Link;
use SilverStripe\LinkField\Models\PhoneLink;
use SilverStripe\LinkField\Models\EmailLink;

class AbstractLinkFieldTest extends SapphireTest
{
protected static $fixture_file = 'AbstractLinkFieldTest.yml';

protected static $extra_dataobjects = [
TestBlock::class,
TestPhoneLink::class,
];

public function testElementalNamespaceRemoved(): void
{
$form = new Form();
$field = new LinkField('PageElements_1_MyLink');
$form->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);
}
}
$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);
// Can override with setAllowedTypes()
$field->setAllowedTypes([EmailLink::class, PhoneLink::class]);
$keys = $this->getKeysForAllowedTypes($field);
$this->assertSame(['email', 'phone'], $keys);
}

private function getKeysForAllowedTypes(LinkField $field)
{
$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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<?php

namespace SilverStripe\LinkField\Tests\Form\LinkFieldTest;
namespace SilverStripe\LinkField\Tests\Form\AbstractLinkFieldTest;

use SilverStripe\LinkField\Models\Link;
use DNADesign\Elemental\Models\BaseElement;
use SilverStripe\Dev\TestOnly;

class TestBlock extends BaseElement implements TestOnly
{
private static $table_name = 'LinkField_TestBlock';
private static $table_name = 'AbstractLinkFieldTest_TestBlock';

private static $has_one = [
'MyLink' => Link::class,
Expand Down
39 changes: 0 additions & 39 deletions tests/php/Form/LinkFieldTest.php

This file was deleted.

0 comments on commit a4fb268

Please sign in to comment.