Jump to content

Custom form field validation via hook in init.php or ready.php (How to)?


Juergen
 Share

Recommended Posts

Hello @ all,

I have studied several posts in this forum (fe. https://processwire.com/talk/topic/969-input-validation-in-back-end-fields/?do=findComment&comment=58931), but I am not able to perform a custom validation of one of my input fields

Goal: Check if the date of one field is higher than the other, output an error if the condition doesnt match

Here is what I have tried so far:

$form->addHookAfter("processInput",function($event) {
    $form = $event->object;
    $firstdate = $form->get("date_end");//date field 1
    $lastdate = $form->get("recurringdateend");// date field 2
    if($firstdate >= $lastdate){ //check if the date of field 1 is higher than the value of field 2
        $lastdate->error("Last date must be higher than the first date");
    }
});

I have tried to insert it in init.php and also in ready.php but without success

Has somebody tried to achieve something similar and know how to deal with this situation?

Best regards

Link to comment
Share on other sites

Ok, I have read the docs and I put the following code into ready.php

$pages->addHookBefore('saveReady', function($event){
$form = $event->object;
$page = $event->arguments[0];
 if($page->template == "events"){
        $lastdate = $page->getUnformatted("recurringdateend");
        $firstdate = $page->getUnformatted("date_end");
        if($firstdate > $lastdate){
            $form->error("First date could not be after the last date.");
        }
 }
});

The validation works, but:

  1. The page will be saved, but it should not because there is an error
  2. The error message is not next at the form field. It is only at the top bar. I know that form->error has no relation with the field itself but using fe
    $form->get("recurringdateend")->error....

    doesnt work.

Link to comment
Share on other sites

I took a look at https://github.com/ryancramerdesign/ProcessWire/blob/master/wire/modules/Process/ProcessPageEdit/ProcessPageEdit.module

My code is

$pages->addHookAfter("ProcessPageEdit::processInput", function($event){
$form = $event->object;
$firstdate = $form->get("date_end");//date field 1
$lastdate = $form->get("recurringdateend");//field 2
$this->message("Datum: {$firstdate} ");
});

But if I add this to my init.php or ready.php I dont get the value of date field 1 in the message. So it seems that after submission of the form the value will not be grabed. I only put the message line to the code to see if the hook works.

I dont know what I should do: I use a hook after, I use processInput which is for processing input data, I use the form as my object and I try to grab the values of the form fields with the get() method - In my opinion this should be the correct way.:undecided:

 

Link to comment
Share on other sites

Try Hooking into the inputfield instead like so. Note, example is from module context. See also notes below:

public function init() {
	$this->pages->addHookAfter("InputfieldDatetime::processInput", $this, "hookAfter");
}

public function hookAfter(HookEvent $event) {

	$field = $event->object;

	if($field->name == 'recurringdateend'){// @note: just a quick example; you might want to hook into 'date_end' as well
		$page = $this->wire('modules')->ProcessPageEdit->getPage();
		#$this->message($field->name);// @note: testing only
		#$this->message($page->id);// @note: testing only		

		/*
		// @note: testing only + FOR INFO
		$oldDate = $page->get($field->name);// existing DB value
		$newDate = $field->value;// incoming/NEW data
		$this->message("old date is: $oldDate");// returns timestamp
		$this->message("new date is: $newDate");// returns timestamp
		*/
		// @note: quick example, not useful to you but a good to know 'how-to'
		// @note: what you really want I suppose is to compare incoming values (from input)
		// @see above for that ($oldDate and $newDate)
		$firstdate = $page->get("date_end");//date field 1
		$lastdate = $page->get("recurringdateend");// date field 2

		if($firstdate >= $lastdate){ //check if the date of field 1 is higher than the value of field 2
			// error here; @note $lastdate is not a field!
			// error example:
			$field->error('Some Error');
		}
	}


}

@Note that in my example we are comparing existing DB values (not very useful). In your case, you want to compare incoming values, right? See the $oldDate and $newDate example. However, you would need to Hook into both 'date_end' and 'recurringdateend' in that case (i.e. run your Hook code if either is the field name). 

Edit2: See complete answer in next post.

Edited by kongondo
more info
  • Like 2
Link to comment
Share on other sites

Played a bit more with this. The following works for me. I am not sure if it is foolproof and/or if there are better approaches. Please note:

  1. From what I can tell, when saving a page, inputs (fields) will be sent in the order they appear in a template. 
  2. date1 refers to the first date and date2 to the last date.
  3. We need to temporarily store the value of date1 in order to later compare it to date2. I stored the value in a session. You could do it differently, e.g. cache it.
  4. The code doesn't prevent a save of the 2 date fields. You can do that if you wish.
  5. This code is necessarily verbose in places; just to make things clear.
public function init() {
	$this->pages->addHookAfter("InputfieldDatetime::processInput", $this, "hookAfter");
}

public function hookAfter(HookEvent $event) {
	$field = $event->object;

	if($field->name == 'date1' || $field->name == 'date2') {

		// @note: from what I can tell, inputs are sent in the order of the fields in the page
		// ...so, date1 will be hooked into before date2
		$date1 = '';// first date
		$date2 = '';// last date
		$session = $this->wire('session');
		
		// save date1 to session; we'll retrieve it to compare against date2
		if($field->name == 'date1') {
			$date1 = $field->value;
			$session->set('date1', $date1);
		}
		elseif($field->name == 'date2') {
			$date2 = $field->value;
			$date1 = $session->get('date1');
			
			// compare the first and last dates
			if($date1 >= $date2) $field->error("Last date must be greater than the first date");
			//else $this->message("Those dates are fine matey!");// @note: for testing only
			// delete the temporary session date1
			$session->remove('date1');
		}

	}

}

 

Edited by kongondo
  • Like 3
Link to comment
Share on other sites

Thanks @kongondo, I will give it a try. To me it seems a bit complex using sessions for this. I guess there will be another way, which works like an $input->post validation, but I am struggeling with this problem for a while.

Its also unclear to me why

$form->get("date_end");

doesn´t grab the value of the input field "date_end" after form submission

 

Many thanks for your solution.

Link to comment
Share on other sites

On 25/01/2017 at 5:53 AM, Juergen said:

Its also unclear to me why


$form->get("date_end");

doesn´t grab the value of the input field "date_end" after form submission

If it's not grabbing it and the fact that we know there is a value sent, it means it is not the correct implementation. In other words, that is not how you grab it :).

On 25/01/2017 at 5:53 AM, Juergen said:

To me it seems a bit complex using sessions for this. I guess there will be another way, which works like an $input->post validation, but I am struggeling with this problem for a while.

The session is only temporary (it is deleted immediately after) since you need to be able to store the value of the first date somewhere in order to do the comparison.  Below is a different approach. We directly check the values in the $input->post. Seems a bit hackish (?) but it works. Here, we make sure 'erroneous' values are not saved. We revert to older values in the database or blanks if there were no saved dates and show a field error on the first date input. Also note that if no first date is input, the field error will be displayed. You can play around with the if() logic to change the behaviour.

public function init() {
	$this->pages->addHookAfter("InputfieldDatetime::processInput", $this, "hookAfter");
}

public function hookAfter(HookEvent $event) {
	$post = $this->wire('input')->post;
	// change date string to timestamp	
	$firstdate = strtotime($post->date);
	$lastdate = strtotime($post->date2);

	// if first date greater than or equal to last date
	if($firstdate >= $lastdate) {
		$field = $event->object;			
		if($field->name == 'date' || $field->name == 'date2') {
			// get the page being edited
			$page = $this->wire('modules')->ProcessPageEdit->getPage();				
			// ensure we don't save 'erroneous' dates; we revert to older saved (db) dates or blank
			$oldDate = $page->get($field->name);
			$field->value = $oldDate;
			// only show error on first date field
			if($field->name == 'date') $field->error("Last date must be greater than the first date");
		}
	}

}

 

  • Like 1
Link to comment
Share on other sites

Hello @kongondo

thanks for the explanation. I have implemented your first version and it works well, means that it shows the error message at the top bar and next to the field. My previous attempts showed only the error message at the top bar and not next to the field. So using a session is not a problem. I only thought that the same process could be achived with simplier methods. Anyway, it works very well.

I think it will be a good feature for PW if every field has a custom validation field in its field settings where you can add additional php code for validation and you will have access to the api variables.:rolleyes:

Link to comment
Share on other sites

  • 6 months later...

Hello,

I'm trying to make users email unique and for this I try the following code:

Can someone tell me why the form still saving values even if the form display the email error "Email already in use" ?  I need to block form submission until the user enter a valid email.

<?php namespace ProcessWire;

/**
 *
 * ProcessWire 3.x
 *
 */
class GeneralTools extends WireData implements Module {
  
  public function init() {
    // Ensure Uniqueness of Emails across site
    $this->pages->addHookAfter("InputfieldEmail::processInput", $this, "validEmail");
  }

  /**
  * Ensure Uniqueness of Emails across site
  *
  * @param \ProcessWire\HookEvent $event
  *
  */
  public function validEmail(HookEvent $event) {
    $field = $event->object;

    if($field->name == 'email') {
       // for test: always display field error
    	$field->error("Email already in use");
    }
  }
}

Thanks Guys :D!

Link to comment
Share on other sites

Hi Robin thanks for your answer,

Actually my module is on autoload mode (below my module GeneralTools.info.php):

<?php

/**
 * GeneralTools.info.php
 * 
 * Return information about this module.
 *
 */

$info = array(

	// Your module's title
	'title' => "General Tools",

	// A 1 sentence description of what your module does
	'summary' => "General helper functions.",

	// Module version number: use 1 for 0.0.1 or 100 for 1.0.0, and so on
	'version' => 100,

	// Icon to accompany this module (optional), uses font-awesome icon names, minus the "fa-" part
	'icon' => 'cogs',

	// URL to more info: change to your full modules.processwire.com URL (if available), or something else if you prefer
	'href' => '',
  'autoload' => true,
  'singular' => false,
  'requires' => "ProcessWire>=3.0",
);

The after hook "InputfieldEmail::processInput" is triggered well because when I submit the User add/edit form I can see the error message "Email already in use" set by the hook, that part work fine, the problem is that even if the form contains errors (this one ---> $field->error("Email already in use") it update the values. I expect that when I set the field error the form will display the error and the it will not update User values because an error is displayed and the it must be resolved.

Have you a idea why the form is not waiting until the error "$field->error("Email already in use")" is resolve before updating User values?

Thank you so much :).

Link to comment
Share on other sites

9 minutes ago, Elkin Angulo said:

Have you a idea why the form is not waiting until the error "$field->error("Email already in use")" is resolve before updating User values?

This is deliberate and Ryan has talked about it but I don't have a link handy. Probably because there can be situations where a field contains a lot of data in which case the user should be notified of a problem but not have all their work thrown out.

See this example from @Soma where he shows how you can set a field back to its previous value when you want to reject a submission:

 

  • Like 3
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

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...