Skip to content

Commit

Permalink
ENH Improved validation
Browse files Browse the repository at this point in the history
  • Loading branch information
emteknetnz committed Sep 28, 2023
1 parent 7a16b8b commit f78ecb4
Show file tree
Hide file tree
Showing 12 changed files with 261 additions and 4 deletions.
1 change: 0 additions & 1 deletion _config.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,4 @@

// Avoid creating global variables
call_user_func(function () {

});
Empty file added lang/_manifest_exclude
Empty file.
7 changes: 7 additions & 0 deletions lang/en.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
en:
SilverStripe\LinkField\Form\ExternalLinkField:
INVALID: 'Please enter a valid url'
SilverStripe\LinkField\Form\PhoneField:
INVALID: 'Please enter a valid phone number'
SilverStripe\LinkField\Validators\HasOneCanViewValidator:
CANNOTBEVIEWED: '{name} cannot be viewed'
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;
}
}
10 changes: 9 additions & 1 deletion 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 All @@ -28,12 +30,18 @@ public function getCMSFields(): FieldList
$this->beforeUpdateCMSFields(static function (FieldList $fields) {
$fields->replaceField('Email', EmailField::create('Email'));
});

return parent::getCMSFields();
}

public function getURL(): string
{
return $this->Email ? sprintf('mailto:%s', $this->Email) : '';
}

public function getCMSCompositeValidator(): CompositeValidator
{
$validator = parent::getCMSCompositeValidator();
$validator->addValidator(RequiredFields::create(['Email']));
return $validator;
}
}
20 changes: 20 additions & 0 deletions src/Models/ExternalLink.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

namespace SilverStripe\LinkField\Models;

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 @@ -15,6 +20,14 @@ class ExternalLink extends Link
'ExternalUrl' => 'Varchar',
];

public function getCMSFields(): FieldList
{
$this->beforeUpdateCMSFields(function (FieldList $fields) {
$fields->replaceField('ExternalUrl', ExternalLinkField::create('ExternalUrl'));
});
return parent::getCMSFields();
}

public function generateLinkDescription(array $data): string
{
return isset($data['ExternalUrl']) ? $data['ExternalUrl'] : '';
Expand All @@ -24,4 +37,11 @@ public function getURL(): string
{
return $this->ExternalUrl ?? '';
}

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 @@ -3,6 +3,8 @@
namespace SilverStripe\LinkField\Models;

use SilverStripe\Assets\File;
use SilverStripe\Forms\CompositeValidator;
use SilverStripe\Forms\RequiredFields;

/**
* A link to a File track in asset-admin
Expand Down Expand Up @@ -42,4 +44,11 @@ public function getURL(): string

return $file->exists() ? (string) $file->getURL() : '';
}

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 @@ -16,6 +16,9 @@
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\FieldType\DBHTMLText;
use SilverStripe\View\Requirements;
use SilverStripe\Forms\Form;
use SilverStripe\ORM\ValidationResult;
use SilverStripe\Forms\TreeDropdownField;

/**
* A Link Data Object. This class should be a subclass, and you should never directly interact with a plain Link
Expand Down Expand Up @@ -110,6 +113,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
20 changes: 20 additions & 0 deletions src/Models/PhoneLink.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

namespace SilverStripe\LinkField\Models;

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 @@ -15,6 +20,14 @@ class PhoneLink extends Link
'Phone' => 'Varchar(255)',
];

public function getCMSFields(): FieldList
{
$this->beforeUpdateCMSFields(function (FieldList $fields) {
$fields->replaceField('Phone', PhoneField::create('Phone'));
});
return parent::getCMSFields();
}

public function generateLinkDescription(array $data): string
{
return isset($data['Phone']) ? $data['Phone'] : '';
Expand All @@ -24,4 +37,11 @@ public function getURL(): string
{
return $this->Phone ? sprintf('tel:%s', $this->Phone) : '';
}

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 @@ -63,7 +65,7 @@ public function getCMSFields(): FieldList

$fields->insertAfter(
'Title',
TreeDropdownField::create(
LinkFieldTreeDropdownField::create(
'PageID',
'Page',
SiteTree::class,
Expand Down Expand Up @@ -128,4 +130,11 @@ public function getTitle(): ?string
// Use page title as a default value in case CMS user didn't provide the title
return $page->Title;
}

public function getCMSCompositeValidator(): CompositeValidator
{
$validator = parent::getCMSCompositeValidator();
$validator->addValidator(RequiredFields::create(['PageID']));
return $validator;
}
}

0 comments on commit f78ecb4

Please sign in to comment.