Skip to content

Commit

Permalink
NEW Allowed link types
Browse files Browse the repository at this point in the history
  • Loading branch information
Sabina Talipova committed Dec 18, 2023
1 parent 55208f8 commit 84824c7
Show file tree
Hide file tree
Showing 26 changed files with 505 additions and 415 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,22 @@ 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.

```php
$fields->addFieldsToTab(
'Root.Main',
[
MultiLinkField::create('PageLinkList')
->setAllowedTypes([ SiteTreeLink::class ]),
Link::create('EmailLink')
->setAllowedTypes([ EmailLink::class ]),
],
);
```

## Unversioned links

The `Link` model has the `Versioned` extension applied to it by default. If you wish for links to not be versioned, then remove the extension from the `Link` model in the projects `app/_config.php` file.
Expand Down
8 changes: 0 additions & 8 deletions _config/graphql.yml

This file was deleted.

20 changes: 0 additions & 20 deletions _config/types.yml

This file was deleted.

3 changes: 0 additions & 3 deletions _graphql/queries.yml

This file was deleted.

5 changes: 0 additions & 5 deletions _graphql/types.yml

This file was deleted.

2 changes: 1 addition & 1 deletion client/dist/js/bundle.js

Large diffs are not rendered by default.

2 changes: 0 additions & 2 deletions client/src/boot/index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
/* global document */
/* eslint-disable */
import registerComponents from './registerComponents';
import registerQueries from './registerQueries';

document.addEventListener('DOMContentLoaded', () => {
registerComponents();
registerQueries();
});
8 changes: 0 additions & 8 deletions client/src/boot/registerQueries.js

This file was deleted.

9 changes: 4 additions & 5 deletions client/src/components/LinkField/LinkField.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ import i18n from 'i18n';
const section = 'SilverStripe\\LinkField\\Controllers\\LinkFieldController';

/**
* value - ID of the Link passed from JsonField
* onChange - callback function passed from JsonField - used to update the underlying <input> form field
* types - injected by the GraphQL query
* value - ID of the Link passed from LinkField entwine
* onChange - callback function passed from LinkField entwine - used to update the underlying <input> form field
* types - types of the Link passed from LinkField entwine
* actions - object of redux actions
* isMulti - whether this field handles multiple links or not
*/
const LinkField = ({ value = null, onChange, types, actions, isMulti = false }) => {
const LinkField = ({ value = null, onChange, types = [], actions, isMulti = false }) => {
const [data, setData] = useState({});
const [editingID, setEditingID] = useState(0);

Expand Down Expand Up @@ -184,7 +184,6 @@ const mapDispatchToProps = (dispatch) => ({
});

export default compose(
injectGraphql('readLinkTypes'),
fieldHolder,
connect(null, mapDispatchToProps)
)(LinkField);
1 change: 1 addition & 0 deletions client/src/entwine/LinkField.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ jQuery.entwine('ss', ($) => {
value,
onChange: this.handleChange.bind(this),
isMulti: this.data('is-multi') ?? false,
types: this.data('types') ?? [],
};
},

Expand Down
48 changes: 0 additions & 48 deletions client/src/state/linkTypes/readLinkTypes.js

This file was deleted.

3 changes: 3 additions & 0 deletions lang/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,6 @@ en:
MISSING_DEFAULT_TITLE: 'Page missing'
SilverStripe\LinkField\Models\FileLink:
MISSING_DEFAULT_TITLE: 'File missing'
SilverStripe\LinkField\Form\Traits\AllowedLinkClassesTrait:
INVALID_TYPECLASS: '"{class}": {typeclass} is not a valid Link Type'
INVALID_TYPECLASS_EMPTY: '"{class}": Allowed types cannot be empty'
9 changes: 5 additions & 4 deletions src/Controllers/LinkFieldController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
use SilverStripe\Forms\DefaultFormFactory;
use SilverStripe\Forms\Form;
use SilverStripe\LinkField\Models\Link;
use SilverStripe\LinkField\Type\Registry;
use SilverStripe\Security\SecurityToken;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\FormAction;
Expand All @@ -19,6 +18,8 @@
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\Forms\HiddenField;
use SilverStripe\LinkField\Form\LinkField;
use SilverStripe\LinkField\Services\LinkTypeService;
use SilverStripe\ORM\DataList;

class LinkFieldController extends LeftAndMain
Expand Down Expand Up @@ -74,7 +75,7 @@ public function linkForm(): Form
}
} else {
$typeKey = $this->typeKeyFromRequest();
$link = Registry::create()->byKey($typeKey);
$link = LinkTypeService::create()->byKey($typeKey);
if (!$link) {
$this->jsonError(404, _t('LinkField.INVALID_TYPEKEY', 'Invalid typeKey'));
}
Expand Down Expand Up @@ -175,7 +176,7 @@ public function save(array $data, Form $form): HTTPResponse
// Creating a new Link
$operation = 'create';
$typeKey = $this->typeKeyFromRequest();
$className = Registry::create()->list()[$typeKey] ?? '';
$className = LinkTypeService::create()->byKey($typeKey) ?? '';
if (!$className) {
$this->jsonError(404, _t('LinkField.INVALID_TYPEKEY', 'Invalid typeKey'));
}
Expand Down Expand Up @@ -240,7 +241,7 @@ private function createLinkForm(Link $link, string $operation): Form
$form = $formFactory->getForm($this, $name, ['Record' => $link]);

// Set where the form is submitted to
$typeKey = Registry::create()->keyByClassName($link->ClassName);
$typeKey = LinkTypeService::create()->keyByClassName($link->ClassName);
$form->setFormAction($this->Link("linkForm/$id?typeKey=$typeKey"));

// Add save action button
Expand Down
10 changes: 10 additions & 0 deletions src/Form/LinkField.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DataObjectInterface;
use SilverStripe\LinkField\Models\Link;
use SilverStripe\LinkField\Form\Traits\AllowedLinkClassesTrait;

/**
* Allows CMS users to edit a Link object.
*/
class LinkField extends FormField
{
use AllowedLinkClassesTrait;

protected $schemaComponent = 'LinkField';

protected $schemaDataType = FormField::SCHEMA_DATA_TYPE_CUSTOM;
Expand Down Expand Up @@ -63,4 +66,11 @@ protected function getDefaultAttributes(): array
$attributes['data-value'] = $this->Value();
return $attributes;
}

public function getSchemaDataDefaults()
{
$data = parent::getSchemaDataDefaults();
$data['types'] = json_decode($this->getTypesProps());
return $data;
}
}
4 changes: 4 additions & 0 deletions src/Form/MultiLinkField.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use LogicException;
use SilverStripe\Forms\FormField;
use SilverStripe\LinkField\Form\Traits\AllowedLinkClassesTrait;
use SilverStripe\ORM\DataObjectInterface;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\Relation;
Expand All @@ -16,6 +17,8 @@
*/
class MultiLinkField extends FormField
{
use AllowedLinkClassesTrait;

protected $schemaComponent = 'LinkField';

protected $schemaDataType = FormField::SCHEMA_DATA_TYPE_CUSTOM;
Expand Down Expand Up @@ -61,6 +64,7 @@ public function getSchemaDataDefaults()
{
$data = parent::getSchemaDataDefaults();
$data['isMulti'] = true;
$data['types'] = json_decode($this->getTypesProps());
return $data;
}

Expand Down
124 changes: 124 additions & 0 deletions src/Form/Traits/AllowedLinkClassesTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<?php

namespace SilverStripe\LinkField\Form\Traits;

use InvalidArgumentException;
use SilverStripe\Core\Injector\Injector;
use SilverStripe\LinkField\Models\Link;
use SilverStripe\LinkField\Services\LinkTypeService;

/**
* Trait to manage which Link type can be added to LinkField form field.
* This trait is used in LinkField and MultiLinkField classes.
*/
trait AllowedLinkClassesTrait
{
private array $allowed_types = [];

/**
* Set allowed types for LinkField
* @param string[] $types
*/
public function setAllowedTypes(array $types): static
{
if ($this->validateTypes($types)) {
$this->allowed_types = $types;
}

return $this;
}

/**
* Get allowed types for LinkField
*/
public function getAllowedTypes(): array
{
return $this->allowed_types;
}

/**
* Validate types that they are subclasses of Link
* @param string[] $types
* @throws InvalidArgumentException
*/
private function validateTypes(array $types): bool
{
if (empty($types)) {
throw new InvalidArgumentException(
_t(
__CLASS__ . '.INVALID_TYPECLASS_EMPTY',
'"{class}": Allowed types cannot be empty',
['class' => static::class],
),
);
}

$validClasses = [];
foreach ($types as $type) {
if (is_subclass_of($type, Link::class)) {
$validClasses[] = $type;
} else {
throw new InvalidArgumentException(
_t(
__CLASS__ . '.INVALID_TYPECLASS',
'"{class}": {typeclass} is not a valid Link Type',
['class' => static::class, 'typeclass' => $type],
sprintf(
'"%s": %s is not a valid Link Type',
static::class,
$type,
),
),
);
}
}

return count($validClasses) > 0;
}

/**
* The method returns an associational array converted to a JSON string,
* of available link types with additional parameters necessary
* for full-fledged work on the client side.
* @throws InvalidArgumentException
*/
public function getTypesProps(): string
{
$typesList = [];
$typeDefinitions = $this->genarateAllowedTypes();
foreach ($typeDefinitions as $key => $class) {
$type = Injector::inst()->get($class);
$typesList[$key] = [
'key' => $key,
'title' => $type->i18n_singular_name(),
'handlerName' => $type->LinkTypeHandlerName(),
];
}

return json_encode($typesList);
}

/**
* Generate allowed types with key => value pair
* Example: ['cms' => SiteTreeLink::class]
* @param string[] $types
*/
private function genarateAllowedTypes(): array
{
$typeDefinitions = $this->getAllowedTypes() ?? [];

if (empty($typeDefinitions)) {
return LinkTypeService::create()->generateAllLinkTypes();
}

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

return $result;
}
}
Loading

0 comments on commit 84824c7

Please sign in to comment.