diff --git a/lang/_manifest_exclude b/lang/_manifest_exclude
new file mode 100644
index 00000000..e69de29b
diff --git a/lang/en.yml b/lang/en.yml
index 37f9f86e..892faf86 100644
--- a/lang/en.yml
+++ b/lang/en.yml
@@ -3,6 +3,10 @@ en:
CREATE_LINK: 'Create link'
MENUTITLE: 'Link fields'
UPDATE_LINK: 'Update link'
+ SilverStripe\LinkField\Form\ExternalLinkField:
+ INVALID: 'Please enter a valid url'
+ SilverStripe\LinkField\Form\PhoneField:
+ INVALID: 'Please enter a valid phone number'
SilverStripe\LinkField\Form\Traits\AllowedLinkClassesTrait:
INVALID_TYPECLASS: '"{class}": {typeclass} is not a valid Link Type'
INVALID_TYPECLASS_EMPTY: '"{class}": Allowed types cannot be empty'
@@ -81,3 +85,5 @@ en:
has_one_Page: Page
SilverStripe\LinkField\Tasks\LinkableMigrationTask:
VERSIONED_STATUS_MISMATCH: 'Linkable and LinkField do not have matching Versioned applications. Make sure that both are either un-Versioned or Versioned'
+ SilverStripe\LinkField\Validators\HasOneCanViewValidator:
+ CANNOTBEVIEWED: '{name} cannot be viewed'
diff --git a/src/Form/ExternalLinkField.php b/src/Form/ExternalLinkField.php
new file mode 100644
index 00000000..44545d75
--- /dev/null
+++ b/src/Form/ExternalLinkField.php
@@ -0,0 +1,62 @@
+ element type="text" attribute
+ * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/tel
+ */
+ protected $inputType = 'url';
+
+ /**
+ * This is added as a classname to the element
+ */
+ public function Type()
+ {
+ return 'url text';
+ }
+
+ /**
+ * @param Validator $validator
+ *
+ * @return string
+ */
+ public function validate($validator)
+ {
+ $result = true;
+ $this->value = trim($this->value ?? '');
+ if ($this->value && !preg_match('#' . self::RX . '#', $this->value)) {
+ $validator->validationError(
+ $this->name,
+ _t(__CLASS__ . '.INVALID', 'Please enter a valid url'),
+ 'validation'
+ );
+ $result = false;
+ }
+ return $this->extendValidationResult($result, $validator);
+ }
+
+ /**
+ * This is passed to the frontent via FormField::getSchemaValidation()
+ * and used in Validator.js
+ */
+ public function getSchemaValidation()
+ {
+ $rules = parent::getSchemaValidation();
+ $rules['regex'] = ['pattern' => self::RX];
+ return $rules;
+ }
+}
diff --git a/src/Form/FormFactory.php b/src/Form/FormFactory.php
new file mode 100644
index 00000000..e462be02
--- /dev/null
+++ b/src/Form/FormFactory.php
@@ -0,0 +1,46 @@
+scaffoldLinkFields($linkData);
+ $fields->push(HiddenField::create('typeKey')->setValue($context['LinkTypeKey']));
+ $this->extend('updateFormFields', $fields, $controller, $name, $context);
+
+ return $fields;
+ }
+
+ protected function getValidator($controller, $name, $context)
+ {
+ if (!array_key_exists('LinkType', $context)) {
+ return null;
+ }
+ /** @var DataObject|Type $type */
+ $type = $context['LinkType'];
+ return Injector::inst()->get($type)->getCMSCompositeValidator();
+ }
+}
diff --git a/src/Form/LinkFieldTreeDropdownField.php b/src/Form/LinkFieldTreeDropdownField.php
new file mode 100644
index 00000000..881edb57
--- /dev/null
+++ b/src/Form/LinkFieldTreeDropdownField.php
@@ -0,0 +1,27 @@
+ '[^0]'];
+ return $rules;
+ }
+}
diff --git a/src/Form/PhoneField.php b/src/Form/PhoneField.php
new file mode 100644
index 00000000..089e09df
--- /dev/null
+++ b/src/Form/PhoneField.php
@@ -0,0 +1,62 @@
+ element type="tel" attribute
+ * https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/tel
+ */
+ protected $inputType = 'tel';
+
+ /**
+ * This is added as a classname to the element
+ */
+ public function Type()
+ {
+ return 'phone text';
+ }
+
+ /**
+ * @param Validator $validator
+ *
+ * @return string
+ */
+ public function validate($validator)
+ {
+ $result = true;
+ $this->value = trim($this->value ?? '');
+ if ($this->value && !preg_match('#' . self::RX . '#', $this->value)) {
+ $validator->validationError(
+ $this->name,
+ _t(__CLASS__ . '.INVALID', 'Please enter a valid phone number'),
+ 'validation'
+ );
+ $result = false;
+ }
+ return $this->extendValidationResult($result, $validator);
+ }
+
+ /**
+ * This is passed to the frontent via FormField::getSchemaValidation()
+ * and used in Validator.js
+ */
+ public function getSchemaValidation()
+ {
+ $rules = parent::getSchemaValidation();
+ $rules['regex'] = ['pattern' => self::RX];
+ return $rules;
+ }
+}
diff --git a/src/Models/EmailLink.php b/src/Models/EmailLink.php
index 2b074161..dde0ea0e 100644
--- a/src/Models/EmailLink.php
+++ b/src/Models/EmailLink.php
@@ -4,6 +4,8 @@
use SilverStripe\Forms\EmailField;
use SilverStripe\Forms\FieldList;
+use SilverStripe\Forms\CompositeValidator;
+use SilverStripe\Forms\RequiredFields;
/**
* A link to an Email address.
@@ -55,4 +57,11 @@ public function getMenuTitle(): string
{
return _t(__CLASS__ . '.LINKLABEL', 'Link to email address');
}
+
+ public function getCMSCompositeValidator(): CompositeValidator
+ {
+ $validator = parent::getCMSCompositeValidator();
+ $validator->addValidator(RequiredFields::create(['Email']));
+ return $validator;
+ }
}
diff --git a/src/Models/ExternalLink.php b/src/Models/ExternalLink.php
index c724d906..6d2a893b 100644
--- a/src/Models/ExternalLink.php
+++ b/src/Models/ExternalLink.php
@@ -3,6 +3,10 @@
namespace SilverStripe\LinkField\Models;
use SilverStripe\Forms\FieldList;
+use SilverStripe\Forms\CompositeValidator;
+use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\FieldList;
+use SilverStripe\LinkField\Form\ExternalLinkField;
/**
* A link to an external URL.
@@ -25,6 +29,14 @@ class ExternalLink extends Link
private static $icon = 'font-icon-external-link';
public function getCMSFields(): FieldList
+ {
+ $this->beforeUpdateCMSFields(function (FieldList $fields) {
+ $fields->replaceField('ExternalUrl', ExternalLinkField::create('ExternalUrl'));
+ });
+ return parent::getCMSFields();
+ }
+
+ public function generateLinkDescription(array $data): string
{
$this->beforeUpdateCMSFields(function (FieldList $fields) {
$linkField = $fields->dataFieldByName('ExternalUrl');
@@ -51,4 +63,11 @@ public function getMenuTitle(): string
{
return _t(__CLASS__ . '.LINKLABEL', 'Link to external URL');
}
+
+ public function getCMSCompositeValidator(): CompositeValidator
+ {
+ $validator = parent::getCMSCompositeValidator();
+ $validator->addValidator(RequiredFields::create(['ExternalUrl']));
+ return $validator;
+ }
}
diff --git a/src/Models/FileLink.php b/src/Models/FileLink.php
index 452d7684..c0c8afbb 100644
--- a/src/Models/FileLink.php
+++ b/src/Models/FileLink.php
@@ -4,6 +4,8 @@
use SilverStripe\Assets\File;
use SilverStripe\Forms\FieldList;
+use SilverStripe\Forms\CompositeValidator;
+use SilverStripe\Forms\RequiredFields;
/**
* A link to a File in the CMS
@@ -71,4 +73,11 @@ public function getMenuTitle(): string
{
return _t(__CLASS__ . '.LINKLABEL', 'Link to a file');
}
+
+ public function getCMSCompositeValidator(): CompositeValidator
+ {
+ $validator = parent::getCMSCompositeValidator();
+ $validator->addValidator(RequiredFields::create(['File']));
+ return $validator;
+ }
}
diff --git a/src/Models/Link.php b/src/Models/Link.php
index 4a0cfed6..382b5b0e 100644
--- a/src/Models/Link.php
+++ b/src/Models/Link.php
@@ -15,6 +15,9 @@
use SilverStripe\ORM\DataObjectSchema;
use SilverStripe\ORM\FieldType\DBHTMLText;
use SilverStripe\Versioned\Versioned;
+use SilverStripe\Forms\Form;
+use SilverStripe\ORM\ValidationResult;
+use SilverStripe\Forms\TreeDropdownField;
/**
* A Link Data Object. This class should be treated as abstract. You should never directly interact with a plain Link
@@ -153,6 +156,37 @@ public function getCMSCompositeValidator(): CompositeValidator
return $validator;
}
+ /**
+ * This method is used to validate the dataobject before saving as part of DataObject::write()
+ * Linkfield works a bit differently from normal forms, because where the modal data is turned
+ * into JSON and saved into a single JsonField and in saveInto() the JSON is loaded into the
+ * dataobject using DataObject::setData($data)
+ * Because of this alternate method of saving, the getCMSCompositeValidator() isn't called
+ * so we need to call it manually here by loading it into a temporary Form
+ *
+ * @return ValidationResult
+ */
+ public function validate()
+ {
+ $parentResult = parent::validate();
+ $validator = $this->getCMSCompositeValidator();
+ $form = Form::create(null, Form::DEFAULT_NAME, $this->getCMSFields());
+ $form->setValidator($validator);
+ $form->loadDataFrom($this);
+ // Workaround an issue where RequiredFields does not treat RelationID's of 0 as missing
+ // by changing the value of any TreeDropdowns on the field with a value of 0 to empty string
+ /** @var FormField $field */
+ foreach ($form->Fields()->flattenFields() as $field) {
+ if (is_a($field, TreeDropdownField::class) && $field->Value() === 0) {
+ $field->setValue('');
+ }
+ }
+ $formResult = $form->validationResult();
+ $combinedResult = $parentResult->combineAnd($formResult);
+ $this->extend('updateValidate', $combinedResult);
+ return $combinedResult;
+ }
+
/**
* Form hook defined in @see Form::saveInto()
* We use this to work with an in-memory only field
diff --git a/src/Models/PhoneLink.php b/src/Models/PhoneLink.php
index 0a72031b..6328dd38 100644
--- a/src/Models/PhoneLink.php
+++ b/src/Models/PhoneLink.php
@@ -3,6 +3,10 @@
namespace SilverStripe\LinkField\Models;
use SilverStripe\Forms\FieldList;
+use SilverStripe\Forms\CompositeValidator;
+use SilverStripe\Forms\RequiredFields;
+use SilverStripe\Forms\FieldList;
+use SilverStripe\LinkField\Form\PhoneField;
/**
* A link to a phone number
@@ -23,6 +27,14 @@ class PhoneLink extends Link
private static $icon = 'font-icon-mobile';
public function getCMSFields(): FieldList
+ {
+ $this->beforeUpdateCMSFields(function (FieldList $fields) {
+ $fields->replaceField('Phone', PhoneField::create('Phone'));
+ });
+ return parent::getCMSFields();
+ }
+
+ public function generateLinkDescription(array $data): string
{
$this->beforeUpdateCMSFields(function (FieldList $fields) {
$linkField = $fields->dataFieldByName('Phone');
@@ -50,4 +62,11 @@ public function getMenuTitle(): string
{
return _t(__CLASS__ . '.LINKLABEL', 'Phone number');
}
+
+ public function getCMSCompositeValidator(): CompositeValidator
+ {
+ $validator = parent::getCMSCompositeValidator();
+ $validator->addValidator(RequiredFields::create(['Phone']));
+ return $validator;
+ }
}
diff --git a/src/Models/SiteTreeLink.php b/src/Models/SiteTreeLink.php
index 023ec7c9..d492b8e2 100644
--- a/src/Models/SiteTreeLink.php
+++ b/src/Models/SiteTreeLink.php
@@ -7,7 +7,9 @@
use SilverStripe\Control\Controller;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\TextField;
-use SilverStripe\Forms\TreeDropdownField;
+use SilverStripe\Forms\CompositeValidator;
+use SilverStripe\Forms\RequiredFields;
+use SilverStripe\LinkField\Form\LinkFieldTreeDropdownField;
/**
* A link to a Page in the CMS
@@ -69,7 +71,7 @@ public function getCMSFields(): FieldList
$fields->insertAfter(
'Title',
- TreeDropdownField::create(
+ LinkFieldTreeDropdownField::create(
'PageID',
_t(__CLASS__ . '.PAGE_FIELD_TITLE', 'Page'),
SiteTree::class,
@@ -147,4 +149,11 @@ public function getMenuTitle(): string
{
return _t(__CLASS__ . '.LINKLABEL', 'Page on this site');
}
+
+ public function getCMSCompositeValidator(): CompositeValidator
+ {
+ $validator = parent::getCMSCompositeValidator();
+ $validator->addValidator(RequiredFields::create(['PageID']));
+ return $validator;
+ }
}