Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NEW Allowed link types #137

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
maxime-rainville marked this conversation as resolved.
Show resolved Hide resolved
* @param string[] $types
*/
public function setAllowedTypes(array $types): static

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's our thinking when an empty array is passed to this method?

  • Do we want to throw an exception?
  • Do we want to unset any restriction and go back to allowing all Link types?

Copy link
Contributor Author

@sabina-talipova sabina-talipova Dec 18, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I put my comment here #137 (comment).
I think an empty array is invalid argument.

{
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
maxime-rainville marked this conversation as resolved.
Show resolved Hide resolved
* @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
maxime-rainville marked this conversation as resolved.
Show resolved Hide resolved
{
$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
Loading