Skip to content

Commit

Permalink
ENH Improved validation
Browse files Browse the repository at this point in the history
  • Loading branch information
emteknetnz committed Jan 30, 2024
1 parent a46c745 commit 0f43c91
Show file tree
Hide file tree
Showing 10 changed files with 256 additions and 2 deletions.
4 changes: 4 additions & 0 deletions lang/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
62 changes: 62 additions & 0 deletions src/Form/ExternalLinkField.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

namespace SilverStripe\LinkField\Form;

use SilverStripe\Forms\TextField;
use SilverStripe\Forms\Validator;

/**
* Text input field with validation for a url
*/
class ExternalLinkField extends TextField
{
/**
* This needs to be not surronded by regex delimiters so that it works on the frontend
*/
private const RX = '^[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$';

/**
* This is used for the <input> 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 <input> 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;
}
}
27 changes: 27 additions & 0 deletions src/Form/LinkFieldTreeDropdownField.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

namespace SilverStripe\LinkField\Form;

use SilverStripe\Forms\TreeDropdownField;

/**
* This class exists as workaround to an issue where RequiredFields is not respected
* when selecting an empty value using TreeDropdownField because RequiredFields views
* a value of 0 (ie no relation selected) as as non-emtpy value, as RequiredFields only counts
* an emtpy string as a missing value
*
* This class in only intended to be used in SiteTreeLink which also has a RequiredFields of ['PageID']
*/
class LinkFieldTreeDropdownField extends TreeDropdownField
{
/**
* This is passed to the frontent via FormField::getSchemaValidation()
* and used in Validator.js
*/
public function getSchemaValidation()
{
$rules = parent::getSchemaValidation();
$rules['regex'] = ['pattern' => '[^0]'];
return $rules;
}
}
62 changes: 62 additions & 0 deletions src/Form/PhoneField.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
<?php

namespace SilverStripe\LinkField\Form;

use SilverStripe\Forms\TextField;
use SilverStripe\Forms\Validator;

/**
* Text input field with validation for a phone number
*/
class PhoneField extends TextField
{
/**
* This needs to be not surronded by regex delimiters so that it works on the frontend
*/
private const RX = '^[()\- 0-9]+$';

/**
* This is used for the <input> 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 <input> 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;
}
}
9 changes: 9 additions & 0 deletions src/Models/EmailLink.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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;
}
}
19 changes: 19 additions & 0 deletions src/Models/ExternalLink.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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');
Expand All @@ -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;
}
}
9 changes: 9 additions & 0 deletions src/Models/FileLink.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
}
}
34 changes: 34 additions & 0 deletions src/Models/Link.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions src/Models/PhoneLink.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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');
Expand Down Expand Up @@ -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;
}
}
13 changes: 11 additions & 2 deletions src/Models/SiteTreeLink.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -69,7 +71,7 @@ public function getCMSFields(): FieldList

$fields->insertAfter(
'Title',
TreeDropdownField::create(
LinkFieldTreeDropdownField::create(
'PageID',
_t(__CLASS__ . '.PAGE_FIELD_TITLE', 'Page'),
SiteTree::class,
Expand Down Expand Up @@ -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;
}
}

0 comments on commit 0f43c91

Please sign in to comment.