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 0c0cdaa
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 69 deletions.
32 changes: 27 additions & 5 deletions README.md
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 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]),
],
);
```
Expand Down
5 changes: 4 additions & 1 deletion src/Form/AbstractLinkField.php
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 @@ -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();
Expand Down
28 changes: 7 additions & 21 deletions src/Models/Link.php
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 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
*/
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
76 changes: 76 additions & 0 deletions tests/php/Form/AbstractLinkFieldTest.php
@@ -0,0 +1,76 @@
<?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);
}
}
// 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;
}
}
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
@@ -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 0c0cdaa

Please sign in to comment.