Giter Club home page Giter Club logo

Comments (7)

brandonkelly avatar brandonkelly commented on August 16, 2024

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.

rungta avatar rungta commented on August 16, 2024

Ah right, the when param makes much more sense here! Thanks for the detailed explanation @brandonkelly.

from cms.

rungta avatar rungta commented on August 16, 2024

@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.

i-just avatar i-just commented on August 16, 2024

Are you overwriting the field handle in the layout and using the original handle in your rule instead of the changed one?

from cms.

brandonkelly avatar brandonkelly commented on August 16, 2024

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.

rungta avatar rungta commented on August 16, 2024

@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 by FilterValidator::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.

brandonkelly avatar brandonkelly commented on August 16, 2024

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():

if ($draft->hasErrors() || !Craft::$app->getElements()->saveElement($draft, false, false)) {

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.