Jump to content

Validation for own Inputfield/Fieldtype modules


bfncs
 Share

Recommended Posts

Hello everyone,

I'm currently working on a simple module (view this thread for more details) and ran into some problems with the validation of input data.

I did some research, but I wasn't able to find answers to these questions:

1. If you develop your own Inputfield/Fieldtype module, where do you do validation?

I've seen other modules do it in Fieldtype::sanitizeValue() (apigen), but this seems rather strange to me. Also, as far as I got it, they didn't throw any errors back to the user but just quietly changed everything, that doesn't make sense without the user knowing it. This leads to the next questions:

2. What is the correct way to get visual validation error reports to the user?

3. How to prevent a page from saving with validation errors?

With this fairly basic stuff (where I wasn't really able to find something meaningful about) out of the way, what I really want to know is this:

4. How do you add validation with error reporting to an existing Fieldtype module?

I want to add my custom validation with error reporting to the standard FieldtypeTextarea module (only when some conditions are meet in the corresponding Inputfield for sure), but I have no clue what would be the way to go there and wasn't able to find any module that does it like this.

Please shed some light on these question, would be very much appreciated!

Marc

  • Like 1
Link to comment
Share on other sites

Well, that is the default behavior: sanitize and let it be. It was just recently when we got the "required" which is only thing that throws inline error on top of the field. Maybe search the source how it does it? (I don't know myself, haven't taken a look yet)

The few times I have done something similar I have hooked into page save and throwing just general error message: $this->error("You have too many characters in your body-field, some cropping occured");

This might be for a little help: https://github.com/formmailer/SchedulePages/blob/master/SchedulePages.module#L102

  • Like 1
Link to comment
Share on other sites

Hey apeisa,

thanks a lot for the quick reaction, this really helped a lot, that pointer to how it is done with "required" in particular, didn't think of it before.

As I see it in InputfieldWrapper::__processInput(), if required fields are empty, the Wire->error() method is called to produce an error notice, and that's it, it's still saved to the DB (or did I overlook something while grep-ing through the core?).

While this might be ok for most use cases (with sanitizing before outputting it), I don't think that this should be the only means of validating here. Imagine you want to validate for a certain string length, your user has entered something before that was short enough and now expands over the length limit and saves. He now ends up with a new string in the DB that is too long (and probably gets shortened uglily on the front end) instead of his old string that fitted the limits.

I think it would be at least desireable to choose, whether you want to store or keep the old value, when validation doesn't work. In my opinion, this definitely is something that should be thought about since validation might be one of the core needs of part of the target audience of Pw.

To get around the practical problem, I now think the most comfortable way might be to hook before Fieldtype::___savePageField(), as this is exactly the place, where the field is going to be saved. This makes more sense to me than to hook to page save where it has to be called on every page, even if the fieldtype to be validated isn't included.

In the hook, it should be possible to throw a visual notice and prevent the saving. Is there some explicit way to stop the hooked to method from a prehook? Else a workaround would be to just set the field's value to the default value.

I'm going to try this later, not enough time for it now. Still, any help on this is appreciated a lot.

Link to comment
Share on other sites

Some reading about the subject. There were some discussion about this in the forums.

...

Ryan has some explanation I'm not sure where this was discussed why and what and how, basicly it's not something he needed really (me neither). And only because where in the beginning still, there's for sure some more things happening on this subject. For me it's not a required or important feature for our projects anyway, but it's possible. Also use google to search and not the forum ;)

Link to comment
Share on other sites

Thanks a lot Soma, amazingly quick and helpful as always!

I already skimmed through most of these threads which are really informative, but wasn't really pleased with what I found there. I'm really hoping that there's some more structure coming to validation in Pw. While I can understand, that it doesn't fit Pw's philosophy to trash default Inputfields up with all kinds of validation possibilities, it would still be great to find preparations for validation in the API so it can be easily implemented by non-core modules.

I have something in my head that might be roughly described as some kind of interface on Fieldtypes where you can hook into, have the variable passed, do your checks and return some ValidationResult object including things like:

  • Was the value validated? (true/false)
  • If not: an error message (localizable)
  • Should the value still be saved (true/false)

Also an array of ValidationResults to show multiple validation errors would be needed. This can be evaluated before finally saving the field then.

Is this understandable and does it make sense for you? I imagine, that it should be possible without forcing anyone to use it but making it easier to use than now.

Link to comment
Share on other sites

Validation in a CMS can get really tricky, if you scan enough deep you'll maybe find or read what Ryan says about it and that something is in the pipeline, it's not that you're the first with comming up for this.

If you want to do something custom it's easy as pie (found by posts linked):

public function init() {
       $this->addHookAfter("Inputfield::processInput",$this,"validateField");
   }
   public function validateField(HookEvent $event){
       $inputfield = $event->object;
       $name = $inputfield->name;
       if($name == "sidebar"){
           if(strlen($inputfield->attr("value")) > 10){
               $inputfield->error($this->_("Too long"));
           }
       }
   }

Custom validation for fields, ready with custom error message localized, and if an error is found after processInput the page won't get published.

To not save it, well that's where it can get tricky for a system and user. But let's see what Ryan will come up with.

Link to comment
Share on other sites

Greetings,

I think there are many ways to approach validation, and a lot of what I've been reading lately makes me think that all Web apps need to consider new (better) ways to validate form entries. But whatver the latest thinking is, validation is a central and vital part of any application, and I'm sure there are ways to do this in ProcessWire!

It seems like the validation challenge comes from submissions using the admin interface, and there are more validation options when building front-end forms?

Thanks,

Matthew

Link to comment
Share on other sites

If you are using the dev branch, you can validate the data using a regex pattern. Just enter a vaild pattern in the Field Settings (Pattern is hidden in the Input Tab) and It should validate both client side and server side.

In the moment it is not possible to get custom validation messages, but this should be easy to add at least for client side validation, as it is enough to add a title attribute to the inputfield with the message.

Edit:

For some reason I can't see a pattern inputfield on Textareas.

Of course for client side validation a modern browser is needed, as long pw doesn't include a polyfill javascript.

Link to comment
Share on other sites

Validation in a CMS can get really tricky, if you scan enough deep you'll maybe find or read what Ryan says about it and that something is in the pipeline, it's not that you're the first with comming up for this.

Thanks for pointing this out, I did read what Ryan said about this and also I know, that I'm not the first one missing this. That's just why I'm writing: it just seemed like there wasn't any clear plans and I wanted to stress that I would appreciate to see this in core a lot. ;)

If you want to do something custom it's easy as pie (found by posts linked)

[...]

Custom validation for fields, ready with custom error message localized, and if an error is found after processInput the page won't get published.

Oh, this is really helpful, must have overlooked it... sorry.

To not save it, well that's where it can get tricky for a system and user. But let's see what Ryan will come up with.

I'm still going to play around with Fieldtype::___savePageField(), looks like the right place to try this at the moment. Just need to conjure up some more time first...

edit@interrobang:

I just read your answer after posting, really helpful though. I already read about the regex support in the dev branch. Didn't try it already, but it is certainly a very nice addition. Still, what I was going for was more targeted on something else, namely a good structure in the API to hook custom validation with all bells and whistles in simple modules.

Especially that bit about "saving when it doesn't validate" is something I want to get around. This is something that works the other way around in most other frameworks/cms/cmf I know so far by default and it always made sense to me.

  • Like 1
Link to comment
Share on other sites

Hey Soma,

after reading again and this time more thoroughly through everything you wrote and linked to I have the feeling I made a bit too much of a problem out of it, because you're totally right with that everything is already possible. The code you posted really helped here, thanks again!

To solve the problem with saving was also pretty easy: just doing a untrackChange('value') in my hook after Inputfield::processInput was everything needed (see here).

  • Like 2
Link to comment
Share on other sites

Fieldtype::sanitizeValue is the function that gets called every time you set a value to a page. So if you set $page->title = 'something'; then the value gets sent through FieldtypePageTitle::sanitizeValue before the value gets set to the $page. The purpose here is mainly to sanitize for type, potentially add some API setting convenience, and do it quickly. So a sanitizeValue() for an integer field would typically do nothing more than typecast whatever value is sent to be a integer. This function is not intended to be the place to sanitize/validate user input (Fieldtypes exist outside of user input context). Instead, this function is just here to make sure that whatever $page field you are setting does not end up containing the wrong type or something else obviously incorrect. If you want your Fieldtype to have some additional built-in validation or convenience here (to account for API setting of values), that's fine too. But this function is called every time a value is set to a page (whether you set it or PW sets it), so ideally it is fairly minimal and fast. Technically, implementation of Fieldtype::sanitizeValue is optional, but definitely recommended when getting into object-based values (like FieldtypePage::sanitizeValue, where it plays a pretty extensive role).

Where you sanitize and validate user input is with Inputfield::processInput($input). That function literally takes the value from $input and populates it to the 'value' attribute of the Inputfield object. As an example, see the processInput from InputfieldText which is where we perform that regex validation.

One thing you'll see in a lot of PW's core Inputfields is that we're actually doing the validation in the setAttribute() method, like in this example (again from the Text inputfield). In these cases, we see if the attribute being set is 'value' and if it is, we perform some additional validation on it. So whether the 'value' attribute is being set from processInput, or from the API, it gets validated in the same manner. This is the way to go if you want to validate both form input and values set manually, in one shot. But technically, you don't need to validate outside of processInput() unless you want to.

  • Like 1
Link to comment
Share on other sites

Thanks Ryan for shedding some light on this, that was really helpful!

I think both methods, with Inputfield::processInput() as well as with the setAttribute() method make perfectly sense, the latter being the more stable one from what you said. I still think, that it would be great to have validation a little more obvious, but from everything I read, this is already a medium term target, which is great!

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
 Share

×
×
  • Create New...