diff --git a/client/dist/styles/bundle.css b/client/dist/styles/bundle.css
index b9106c1f..3b8902f0 100644
--- a/client/dist/styles/bundle.css
+++ b/client/dist/styles/bundle.css
@@ -1 +1 @@
-.link-field__container{position:relative}.link-field__loading{position:absolute;top:0;left:0;width:100%;height:100%}.link-field__loading .cms-content-loading-overlay{position:relative}.link-field__save-record-first{padding-top:7px}.link-picker__link,.link-picker{display:flex;height:auto;width:100%;min-height:54px;background:#fff;padding:0}.link-picker{align-items:stretch;cursor:pointer;box-shadow:none}.link-picker:not(:last-child){margin-bottom:10px}.link-picker.font-icon-link::before{margin:.76925rem}.link-picker__cannot-create{cursor:default;flex-grow:1;padding:16px 13px}.link-picker__menu{flex-grow:1}.link-picker__menu-toggle{width:100%;height:100%;text-align:left}.link-picker__menu-toggle::before{padding:.76925rem}.link-picker__menu-icon{vertical-align:middle;padding-right:.7rem}.link-picker__link{align-items:center;text-align:left;margin-right:0;justify-content:space-between;position:relative;border-top:0;border-top-left-radius:0;border-top-right-radius:0;border-bottom:0;border-bottom-left-radius:0;border-bottom-right-radius:0}.link-picker__link:hover,.link-picker__link:focus{text-decoration:none;color:inherit}.link-picker__link::before{top:29px;left:32px;content:" ";position:absolute;border:1px solid #cf3f00;border-radius:100%;bottom:6px;box-shadow:0 0 1px .5px #fff;display:block;height:8px;width:8px;z-index:1}.link-picker__link--draft::before{background-color:#ff7f22}.link-picker__link--modified::before{background-color:#fff7f0}.link-picker__link--unsaved::before,.link-picker__link--unversioned::before,.link-picker__link--published::before{display:none}.link-picker__link--is-first,.link-picker__link--is-sorting{border-top:1px solid #ced5e1;border-top-left-radius:.23rem;border-top-right-radius:.23rem}.link-picker__link--is-last,.link-picker__link--is-sorting{border-bottom:1px solid #ced5e1;border-bottom-left-radius:.23rem;border-bottom-right-radius:.23rem}.link-picker__button{display:flex;align-items:center;flex-grow:1;height:100%;min-width:0;text-align:left;border:none;margin-right:0}.link-picker__button[class*=font-icon-]::before{position:absolute;font-size:1.231rem;padding:.76925rem;margin-right:6px;flex-grow:0}.link-picker__drag-handle{display:none;left:5px;position:absolute;z-index:100}.link-picker__drag-handle:hover{cursor:grab}.link-picker__link:hover .link-picker__drag-handle{display:block}.link-picker__links--dragging *{cursor:grabbing !important}.link-picker__link-detail{flex-grow:1;width:100%;padding-left:3.5rem}.link-picker__delete{flex-grow:0}.link-picker__url{color:#0071c4}.link-picker__type,.link-picker__title-text{display:inline-block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.link-picker__title{display:flex;align-items:center;width:100%}.link-picker__title .badge{color:#cf3f00;background-color:#fff2ea;padding:2px 3px 2px 4px}.link-picker__title-text{min-width:0;margin-right:5px}.link-picker__type{width:100%}
+.link-field__container{position:relative}.link-field__loading{position:absolute;top:0;left:0;width:100%;height:100%}.link-field__loading .cms-content-loading-overlay{position:relative}.link-field__save-record-first{padding-top:7px}.link-picker__link,.link-picker{display:flex;height:auto;width:100%;min-height:54px;background:#fff;padding:0}.link-picker{align-items:stretch;cursor:pointer;box-shadow:none}.link-picker:not(:last-child){margin-bottom:10px}.link-picker.font-icon-link::before{margin:.76925rem}.link-picker__cannot-create{cursor:default;flex-grow:1;padding:16px 13px}.link-picker__menu{flex-grow:1}.link-picker__menu-toggle{width:100%;height:100%;text-align:left}.link-picker__menu-toggle::before{padding:.76925rem}.link-picker__menu-icon{vertical-align:middle;padding-right:.7rem}.link-picker__link{align-items:center;text-align:left;margin-right:0;justify-content:space-between;position:relative;border-top:0;border-top-left-radius:0;border-top-right-radius:0;border-bottom:0;border-bottom-left-radius:0;border-bottom-right-radius:0}.link-picker__link:hover,.link-picker__link:focus{text-decoration:none;color:inherit}.link-picker__link::before{top:29px;left:32px;content:" ";position:absolute;border:1px solid #cf3f00;border-radius:100%;bottom:6px;box-shadow:0 0 1px .5px #fff;display:block;height:8px;width:8px;z-index:1}.link-picker__link--draft::before{background-color:#ff7f22}.link-picker__link--modified::before{background-color:#fff7f0}.link-picker__link--unsaved::before,.link-picker__link--unversioned::before,.link-picker__link--published::before{display:none}.link-picker__link--readonly{background-color:#eef0f4}.link-picker__link--is-first,.link-picker__link--is-sorting{border-top:1px solid #ced5e1;border-top-left-radius:.23rem;border-top-right-radius:.23rem}.link-picker__link--is-last,.link-picker__link--is-sorting{border-bottom:1px solid #ced5e1;border-bottom-left-radius:.23rem;border-bottom-right-radius:.23rem}.link-picker__button{display:flex;align-items:center;flex-grow:1;height:100%;min-width:0;text-align:left;border:none;margin-right:0}.link-picker__button[class*=font-icon-]::before{position:absolute;font-size:1.231rem;padding:.76925rem;margin-right:6px;flex-grow:0}.link-picker__drag-handle{display:none;left:5px;position:absolute;z-index:100}.link-picker__drag-handle:hover{cursor:grab}.link-picker__link:hover .link-picker__drag-handle{display:block}.link-picker__links--dragging *{cursor:grabbing !important}.link-picker__link-detail{flex-grow:1;width:100%;padding-left:3.5rem}.link-picker__delete{flex-grow:0}.link-picker__url{color:#0071c4}.link-picker__type,.link-picker__title-text{display:inline-block;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.link-picker__title{display:flex;align-items:center;width:100%}.link-picker__title .badge{color:#cf3f00;background-color:#fff2ea;padding:2px 3px 2px 4px}.link-picker__title-text{min-width:0;margin-right:5px}.link-picker__type{width:100%}
diff --git a/client/src/components/LinkField/LinkField.js b/client/src/components/LinkField/LinkField.js
index 1a10a09c..8496b6e0 100644
--- a/client/src/components/LinkField/LinkField.js
+++ b/client/src/components/LinkField/LinkField.js
@@ -44,6 +44,7 @@ const LinkField = ({
actions,
isMulti = false,
canCreate,
+ readonly,
ownerID,
ownerClass,
ownerRelation,
@@ -192,6 +193,7 @@ const LinkField = ({
isLast={i === linkIDs.length - 1}
isSorting={isSorting}
canCreate={canCreate}
+ readonly={readonly}
/>);
}
return links;
@@ -255,6 +257,7 @@ const LinkField = ({
onModalClosed={onModalClosed}
types={types}
canCreate={canCreate}
+ readonly={readonly}
/> }
{ isMulti &&
{
+const LinkPicker = ({ types, onModalSuccess, onModalClosed, canCreate, readonly }) => {
const [typeKey, setTypeKey] = useState('');
/**
@@ -43,7 +43,7 @@ const LinkPicker = ({ types, onModalSuccess, onModalClosed, canCreate }) => {
const allowedTypes = typeArray.filter(type => type.allowed);
const message = i18n._t('LinkField.CANNOT_CREATE_LINK', 'Cannot create link');
- if (!canCreate || allowedTypes.length === 0) {
+ if (!canCreate || allowedTypes.length === 0 || readonly) {
return (
@@ -72,7 +72,8 @@ LinkPicker.propTypes = {
types: PropTypes.object.isRequired,
onModalSuccess: PropTypes.func.isRequired,
onModalClosed: PropTypes.func,
- canCreate: PropTypes.bool.isRequired
+ canCreate: PropTypes.bool.isRequired,
+ readonly: PropTypes.bool.isRequired,
};
export {LinkPicker as Component};
diff --git a/client/src/components/LinkPicker/LinkPicker.scss b/client/src/components/LinkPicker/LinkPicker.scss
index dd50ab09..ca660150 100644
--- a/client/src/components/LinkPicker/LinkPicker.scss
+++ b/client/src/components/LinkPicker/LinkPicker.scss
@@ -99,7 +99,7 @@
display: none;
}
- &.readonly {
+ &--readonly {
background-color: $gray-100;
}
}
diff --git a/client/src/components/LinkPicker/LinkPickerTitle.js b/client/src/components/LinkPicker/LinkPickerTitle.js
index ae7d145d..bed145a6 100644
--- a/client/src/components/LinkPicker/LinkPickerTitle.js
+++ b/client/src/components/LinkPicker/LinkPickerTitle.js
@@ -47,6 +47,7 @@ const LinkPickerTitle = ({
isLast,
isSorting,
canCreate,
+ readonly,
}) => {
const { loading } = useContext(LinkFieldContext);
const {
@@ -66,7 +67,7 @@ const LinkPickerTitle = ({
'link-picker__link--is-last': isLast,
'link-picker__link--is-sorting': isSorting,
'form-control': true,
- 'readonly': !canCreate,
+ 'link-picker__link--readonly': readonly || !canCreate,
};
if (versionState) {
classes[`link-picker__link--${versionState}`] = true;
@@ -95,7 +96,7 @@ const LinkPickerTitle = ({
- {(canDelete && canCreate) &&
+ {(canDelete && !readonly) &&
}
@@ -116,6 +117,7 @@ LinkPickerTitle.propTypes = {
isLast: PropTypes.bool.isRequired,
isSorting: PropTypes.bool.isRequired,
canCreate: PropTypes.bool.isRequired,
+ readonly: PropTypes.bool.isRequired,
};
export default LinkPickerTitle;
diff --git a/client/src/components/LinkPicker/tests/LinkPicker-test.js b/client/src/components/LinkPicker/tests/LinkPicker-test.js
index 1e99394a..19b62da2 100644
--- a/client/src/components/LinkPicker/tests/LinkPicker-test.js
+++ b/client/src/components/LinkPicker/tests/LinkPicker-test.js
@@ -8,6 +8,7 @@ function makeProps(obj = {}) {
return {
types: { phone: { key: 'phone', title: 'Phone', icon: 'font-icon-phone', allowed: true } },
canCreate: true,
+ readonly: false,
onModalSuccess: () => {},
onModalClosed: () => {},
...obj
diff --git a/client/src/components/LinkPicker/tests/LinkPickerTitle-test.js b/client/src/components/LinkPicker/tests/LinkPickerTitle-test.js
index 2e6c4b43..849b51c6 100644
--- a/client/src/components/LinkPicker/tests/LinkPickerTitle-test.js
+++ b/client/src/components/LinkPicker/tests/LinkPickerTitle-test.js
@@ -15,6 +15,7 @@ function makeProps(obj = {}) {
typeIcon: 'font-icon-phone',
canDelete: true,
canCreate: true,
+ readonly: false,
onDelete: () => {},
onClick: () => {},
isMulti: false,
@@ -90,3 +91,17 @@ test('LinkPickerTitle main button should not fire the onClick callback while loa
fireEvent.click(container.querySelector('button.link-picker__button'));
expect(mockOnClick).toHaveBeenCalledTimes(0);
});
+
+test('LinkPickerTitle render() should have readonly class if cannot edit', () => {
+ const { container } = render(
+
+ );
+ expect(container.querySelectorAll('.link-picker__link--readonly')).toHaveLength(1);
+});
+
+test('LinkPickerTitle render() should not have readonly class if can edit', () => {
+ const { container } = render(
+
+ );
+ expect(container.querySelectorAll('.link-picker__link--readonly')).toHaveLength(0);
+});
diff --git a/client/src/entwine/LinkField.js b/client/src/entwine/LinkField.js
index 6c59ec28..2c8f23e3 100644
--- a/client/src/entwine/LinkField.js
+++ b/client/src/entwine/LinkField.js
@@ -55,6 +55,7 @@ jQuery.entwine('ss', ($) => {
isMulti: this.data('is-multi') ?? false,
types: this.data('types') ?? {},
canCreate: inputField.data('can-create') ? true : false,
+ readonly: inputField.data('readonly') ? true : false,
};
},
diff --git a/src/Controllers/LinkFieldController.php b/src/Controllers/LinkFieldController.php
index ed82f7ce..eaac088e 100644
--- a/src/Controllers/LinkFieldController.php
+++ b/src/Controllers/LinkFieldController.php
@@ -379,7 +379,7 @@ private function createLinkForm(Link $link, string $operation): Form
// Make readonly if fail can check
if ($operation === 'create' && !$link->canCreate()
|| $operation === 'edit' && !$link->canEdit()
- || $this->isReadOnlyField() && !$link->canEdit()
+ || $this->isReadOnlyField()
) {
$form->makeReadonly();
}
@@ -395,12 +395,10 @@ private function createLinkForm(Link $link, string $operation): Form
*/
private function isReadOnlyField(): bool
{
- $request = $this->getRequest();
- $ownerClass = $request->getVar('ownerClass') ?: $request->postVar('OwnerClass');
+ $ownerClass = $this->getOwnerClassFromRequest();
$ownerRelation = $this->ownerRelationFromRequest();
- $isReadOnly = Injector::inst()->get($ownerClass)->getCMSFields()->dataFieldByName($ownerRelation)?->isReadonly();
- return $isReadOnly ?? false;
+ return (bool) Injector::inst()->get($ownerClass)->getCMSFields()->dataFieldByName($ownerRelation)?->isReadonly();
}
/**
@@ -485,20 +483,41 @@ private function typeKeyFromRequest(): string
}
/**
- * Get the owner based on the query string params ownerID, ownerClass, ownerRelation
- * OR the POST vars OwnerID, OwnerClass, OwnerRelation
+ * Get the owner class based on the query string param OwnerClass
*/
- private function ownerFromRequest(): DataObject
+ private function getOwnerClassFromRequest(): string
{
$request = $this->getRequest();
- $ownerID = (int) ($request->getVar('ownerID') ?: $request->postVar('OwnerID'));
- if ($ownerID === 0) {
- $this->jsonError(404, _t(__CLASS__ . '.INVALID_OWNER_ID', 'Invalid ownerID'));
- }
$ownerClass = $request->getVar('ownerClass') ?: $request->postVar('OwnerClass');
if (!is_a($ownerClass, DataObject::class, true)) {
$this->jsonError(404, _t(__CLASS__ . '.INVALID_OWNER_CLASS', 'Invalid ownerClass'));
}
+
+ return $ownerClass;
+ }
+
+ /**
+ * Get the owner ID based on the query string param OwnerID
+ */
+ private function getOwnerIDFromRequest(): int
+ {
+ $request = $this->getRequest();
+ $ownerID = (int) ($request->getVar('ownerID') ?: $request->postVar('OwnerID'));
+ if ($ownerID === 0) {
+ $this->jsonError(404, _t(__CLASS__ . '.INVALID_OWNER_ID', 'Invalid ownerID'));
+ }
+
+ return $ownerID;
+ }
+
+ /**
+ * Get the owner based on the query string params ownerID, ownerClass, ownerRelation
+ * OR the POST vars OwnerID, OwnerClass, OwnerRelation
+ */
+ private function ownerFromRequest(): DataObject
+ {
+ $ownerID = $this->getOwnerIDFromRequest();
+ $ownerClass = $this->getOwnerClassFromRequest();
$ownerRelation = $this->ownerRelationFromRequest();
/** @var DataObject $obj */
$obj = Injector::inst()->get($ownerClass);
@@ -536,6 +555,7 @@ private function ownerRelationFromRequest(): string
if (!$ownerRelation) {
$this->jsonError(404, _t(__CLASS__ . '.INVALID_OWNER_RELATION', 'Invalid ownerRelation'));
}
+
return $ownerRelation;
}
}
diff --git a/src/Form/LinkField.php b/src/Form/LinkField.php
index f9b2591d..823ef6ce 100644
--- a/src/Form/LinkField.php
+++ b/src/Form/LinkField.php
@@ -2,13 +2,10 @@
namespace SilverStripe\LinkField\Form;
-use LogicException;
use SilverStripe\Forms\FormField;
use SilverStripe\LinkField\Models\Link;
use SilverStripe\LinkField\Form\Traits\AllowedLinkClassesTrait;
use SilverStripe\LinkField\Form\Traits\LinkFieldGetOwnerTrait;
-use SilverStripe\ORM\DataObject;
-use SilverStripe\ORM\DataObjectInterface;
/**
* Allows CMS users to edit a Link object.
@@ -36,7 +33,8 @@ public function setValue($value, $data = null)
public function getSchemaStateDefaults()
{
$data = parent::getSchemaStateDefaults();
- $data['canCreate'] = $this->getOwner()->canEdit() && !$this->isReadonly();
+ $data['canCreate'] = $this->getOwner()->canEdit();
+ $data['readonly'] = $this->isReadonly();
return $data;
}
@@ -44,7 +42,8 @@ protected function getDefaultAttributes(): array
{
$attributes = parent::getDefaultAttributes();
$attributes['data-value'] = $this->Value();
- $attributes['data-can-create'] = $this->getOwner()->canEdit() && !$this->isReadonly();
+ $attributes['data-can-create'] = $this->getOwner()->canEdit();
+ $attributes['data-readonly'] = $this->isReadonly();
$ownerFields = $this->getOwnerFields();
$attributes['data-owner-id'] = $ownerFields['ID'];
$attributes['data-owner-class'] = $ownerFields['Class'];
@@ -63,13 +62,14 @@ public function getSchemaDataDefaults()
return $data;
}
- /**
+ /**
* Changes this field to the readonly field.
*/
public function performReadonlyTransformation()
{
- $copy = $this->castedCopy(LinkField_Readonly::class);
+ $clone = $this->castedCopy($this);
+ $clone->setReadonly(true);
- return $copy;
+ return $clone;
}
}
diff --git a/src/Form/LinkField_Readonly.php b/src/Form/LinkField_Readonly.php
deleted file mode 100644
index e41b2b91..00000000
--- a/src/Form/LinkField_Readonly.php
+++ /dev/null
@@ -1,27 +0,0 @@
-name, $this->title);
- $field->setValue($this->Value());
- $field->setForm($this->form);
-
- // Store values to hidden field
- $valueField = new HiddenField($this->name);
- $valueField->setValue($this->Value());
- $valueField->setForm($this->form);
-
- return $field->Field() . $valueField->Field();
- }
-}
diff --git a/src/Form/MultiLinkField.php b/src/Form/MultiLinkField.php
index a454fd14..a8540a81 100644
--- a/src/Form/MultiLinkField.php
+++ b/src/Form/MultiLinkField.php
@@ -53,7 +53,8 @@ public function getSchemaStateDefaults()
{
$data = parent::getSchemaStateDefaults();
$data['value'] = $this->getValueArray();
- $data['canCreate'] = $this->getOwner()->canEdit() && !$this->isReadonly();
+ $data['canCreate'] = $this->getOwner()->canEdit();
+ $data['readonly'] = $this->isReadonly();
return $data;
}
@@ -61,7 +62,8 @@ protected function getDefaultAttributes(): array
{
$attributes = parent::getDefaultAttributes();
$attributes['data-value'] = $this->getValueArray();
- $attributes['data-can-create'] = $this->getOwner()->canEdit() && !$this->isReadonly();
+ $attributes['data-can-create'] = $this->getOwner()->canEdit();
+ $attributes['data-readonly'] = $this->isReadonly();
$ownerFields = $this->getOwnerFields();
$attributes['data-owner-id'] = $ownerFields['ID'];
$attributes['data-owner-class'] = $ownerFields['Class'];
@@ -72,7 +74,7 @@ protected function getDefaultAttributes(): array
/**
* Extracts the value of this field, normalised as a non-associative array.
*/
- protected function getValueArray(): array
+ private function getValueArray(): array
{
return $this->convertValueToArray($this->Value());
}
@@ -156,13 +158,14 @@ private function loadFrom(DataObject $record): void
parent::setValue($value);
}
- /**
+ /**
* Changes this field to the readonly field.
*/
public function performReadonlyTransformation()
{
- $copy = $this->castedCopy(MultiLinkField_Readonly::class);
+ $clone = $this->castedCopy($this);
+ $clone->setReadonly(true);
- return $copy;
+ return $clone;
}
}
diff --git a/src/Form/MultiLinkField_Readonly.php b/src/Form/MultiLinkField_Readonly.php
deleted file mode 100644
index 4cc4768c..00000000
--- a/src/Form/MultiLinkField_Readonly.php
+++ /dev/null
@@ -1,27 +0,0 @@
-name, $this->title);
- $field->setValue($this->getValueArray());
- $field->setForm($this->form);
-
- // Store values to hidden field
- $valueField = new HiddenField($this->name);
- $valueField->setValue($this->getValueArray());
- $valueField->setForm($this->form);
-
- return $field->Field() . $valueField->Field();
- }
-}