Comments (7)
EVENT_DEFINE_RULES
gets called as a prerequisite to setting the entry type, because the element need to identify which POST values (like typeId
) are “safe” to set on the newly-created element, and it does that based on which attributes have validation rules associated with them.
If you need to add logic that determines whether a rule should be applied to an element, you should add the rule no matter what from EVENT_DEFINE_RULES
, and use the when
param to determine when the rule should actually apply:
use craft\base\Event;
use craft\base\Model;
use craft\elements\Entry;
use craft\events\DefineRulesEvent;
Event::on(Entry::class, Model::EVENT_DEFINE_RULES, function(DefineRulesEvent $event) {
/** @var */
$event->rules[] = [
'attribute',
'rule',
'when' => fn(Entry $entry) => $entry->getFieldLayout()->isFieldIncluded('fieldHandle'),
];
});
In addition to fixing this issue, it will also ensure the rule is always working properly, even if the entry’s type has changed since the time the rules were defined (which will happen on save-draft requests if the entry’s type was changed via the Edit Entry page UI).
from cms.
Ah right, the when
param makes much more sense here! Thanks for the detailed explanation @brandonkelly.
from cms.
@brandonkelly I tried attaching the rule with the when
param but am running into another issue now when creating a new nested element:
// URL decode
$event->rules[] = [
'field:legacyUrl',
'filter',
'filter' => 'urldecode',
'when' => fn(Element $element) => $element->getFieldLayout()->isFieldIncluded('legacyUrl'),
'skipOnEmpty' => true,
'skipOnArray' => true,
];
Exception:
craft\errors\InvalidFieldException: Invalid field handle: legacyUrl in /var/www/html/vendor/craftcms/cms/src/base/Element.php:5962
Stack trace:
#0 /var/www/html/vendor/craftcms/cms/src/base/Element.php(4677): craft\base\Element->normalizeFieldValue('legacyUrl')
#1 /var/www/html/vendor/craftcms/cms/src/base/Element.php(2313): craft\base\Element->getFieldValue('legacyUrl')
#2 /var/www/html/vendor/yiisoft/yii2/validators/Validator.php(257): craft\base\Element->__get('field:legacyUrl')
#3 /var/www/html/vendor/yiisoft/yii2/base/Model.php(368): yii\validators\Validator->validateAttributes(Object(craft\elements\Entry), Array)
#4 /var/www/html/vendor/craftcms/cms/src/base/Element.php(2649): yii\base\Model->validate(Array, true)
#5 /var/www/html/vendor/craftcms/cms/src/services/Elements.php(3506): craft\base\Element->validate(NULL)
#6 /var/www/html/vendor/craftcms/cms/src/services/Elements.php(1251): craft\services\Elements->_saveElementInternal(Object(craft\elements\Entry), true, false, NULL, Array, false, false, true)
#7 /var/www/html/vendor/craftcms/cms/src/services/Drafts.php(261): craft\services\Elements->saveElement(Object(craft\elements\Entry))
#8 /var/www/html/vendor/craftcms/cms/src/controllers/ElementsController.php(202): craft\services\Drafts->saveElementAsDraft(Object(craft\elements\Entry), 1, 'First draft', NULL, false)
#9 [internal function]: craft\controllers\ElementsController->actionCreate()
#10 /var/www/html/vendor/yiisoft/yii2/base/InlineAction.php(57): call_user_func_array(Array, Array)
#11 /var/www/html/vendor/yiisoft/yii2/base/Controller.php(178): yii\base\InlineAction->runWithParams(Array)
#12 /var/www/html/vendor/yiisoft/yii2/base/Module.php(552): yii\base\Controller->runAction('create', Array)
#13 /var/www/html/vendor/craftcms/cms/src/web/Application.php(349): yii\base\Module->runAction('elements/create', Array)
#14 /var/www/html/vendor/craftcms/cms/src/web/Application.php(650): craft\web\Application->runAction('elements/create', Array)
#15 /var/www/html/vendor/craftcms/cms/src/web/Application.php(311): craft\web\Application->_processActionRequest(Object(craft\web\Request))
#16 /var/www/html/vendor/yiisoft/yii2/base/Application.php(384): craft\web\Application->handleRequest(Object(craft\web\Request))
#17 /var/www/html/web/index.php(12): yii\base\Application->run()
#18 {main}
from cms.
Are you overwriting the field handle in the layout and using the original handle in your rule instead of the changed one?
from cms.
Sorry, I missed that the validation rule is for a custom field.
The only way to validate custom fields is via EVENT_AFTER_VALIDATE
, rather than going through normal validation rules, since the available custom fields on the element can change with its layout. Which is why all of the normal custom field validation logic happens via Element::afterValidate()
.
So you could do something like this:
use craft\base\Model;
use craft\elements\Entry;
use yii\base\Event;
use yii\validators\FilterValidator;
Event::on(Entry::class, Model::EVENT_AFTER_VALIDATE, function(Event $event) {
/** @var Entry $entry */
$entry = $event->sender;
if ($entry->getFieldLayout()->isFieldIncluded('legacyUrl')) {
$validator = new FilterValidator([
'filter' => 'urldecode',
'skipOnEmpty' => true,
'skipOnArray' => true,
]);
$validator->validateAttribute($entry, 'field:legacyUrl');
}
});
from cms.
@brandonkelly Understood. I tried applying the validation rules via EVENT_AFTER_VALIDATE
and am now observing the following behaviors:
- The
skipOnEmpty
parameter seems to be ignored byFilterValidator::validateAttribute()
(but can be managed by manually checking for a value before calling the validator method) - Unable to apply the validation rules when an entry is created for the first time
- Saving the entry for the second time applies the validation rules to the canonical element as well as, strangely, to the first revision of the entry. All subsequent saves seem to work as expected.
Is this because after the draft is merged with POST during the first save, the final validation is on the essentials scenario only which does not trigger EVENT_AFTER_VALIDATE
? I’m guessing that setting the on => 'essentials'
param on these rules would not have any impact since the validation is performed manually?
This is the code I’m using:
use craft\base\Model;
use craft\elements\Entry;
use craft\helpers\UrlHelper;
use yii\base\Event;
use yii\validators\FilterValidator;
Event::on(Entry::class, Model::EVENT_AFTER_VALIDATE, function(Event $event) {
/** @var Entry $entry */
$entry = $event->sender;
if (
!ElementHelper::isCanonical($entry) ||
(!$element->firstSave && ElementHelper::isDraftOrRevision($entry))
) { return; }
if (
$entry->getFieldLayout()->isFieldIncluded('legacyUrl') ||
!$entry->getFieldValue('legacyUrl')
) {
$decodeValidator = new FilterValidator([
'filter' => 'urldecode',
'skipOnEmpty' => true,
'skipOnArray' => true,
]);
$decodeValidator->validateAttribute($entry, 'field:legacyUrl');
$relativeValidator = new FilterValidator([
'filter' => [UrlHelper::class, 'rootRelativeUrl'],
'skipOnEmpty' => true,
'skipOnArray' => true,
]);
$relativeValidator->validateAttribute($entry, 'field:legacyUrl');
}
});
P.S. – Apologies if this thread is veering off topic. Let me know if I should move this to another platform?
from cms.
- The
skipOnEmpty
parameter seems to be ignored byFilterValidator::validateAttribute()
(but can be managed by manually checking for a value before calling the validator method)
Sorry, yeah, looks like that’s only ever factored in from validateAttributes()
, which we’re not going through here.
- Unable to apply the validation rules when an entry is created for the first time
Entries created in the control panel will start as “unpublished drafts”, and then when you press “Create entry”, they will be saved as an unpublished draft one last time with any last-minute changes, before shedding the draft state.
The first time they’re saved as a normal entry is when the draft state is removed (this is when firstSave
will be true
). No actual content changes are being applied at this point, so we pass false
to the runValidation
argument for saveElement()
:
Line 402 in 6183b53
If you want to apply your code regardless of whether validation is being run, maybe EVENT_BEFORE_SAVE
would make more sense, which will always be called.
- Saving the entry for the second time applies the validation rules to the canonical element as well as, strangely, to the first revision of the entry. All subsequent saves seem to work as expected.
The first revision will just be a duplication of the entry after its first full save.
from cms.
Related Issues (20)
- [5.x]: Plugin table not found during site install HOT 3
- [5.x]: Missing Database Backup Button HOT 3
- [5.x]: Front end save-user not uploading to asset fields HOT 2
- [5.x]: Link Field Type Allowed Link Types Phone or Email HOT 1
- [5.x]: SearchScore return null - Graphql HOT 1
- [5.x]: Link field with only one link type defined doesn't show up in some scenarios HOT 1
- [5.x]: Saving element in afterSave results in integrity constraint violation exception HOT 5
- [5.x]: OperationAbortedException isn't shown during a codeception run, causing much confusion
- [5.x]: Global set shows Multi-Site dropdown with sites that user is not allowed to see. HOT 2
- [5.x]: Website button in CP no longer redirects to website HOT 2
- [5.x]: 'Entries' field during save populated with other field content which is initially the same field HOT 1
- [5.x]: fields/merge allow outgoing field instructions/label to persist in field layouts HOT 3
- [5.x]: Field of type "Entries" fails when opened in a modal HOT 3
- [5.x]: Craft Native Link Field - the native link field don't have the custom title input field HOT 2
- [4.x]: `.env` variable normalization of float instead of string results in truncation HOT 1
- [5.x]: Able to save entry with a matrix field entry that does not meet validation requirements
- [5.x]: `redirectInput` ignored with `actionInput('users/set-password')` HOT 6
- [5.x]: Upgrading from 4 -> 5 applying m240302_212719_solo_preview_targets Exception: 1205 Lock wait timeout exceeded; HOT 4
- [5.x]: Link field errors on changing Allowed Link Types
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from cms.