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; + } }