I'm trying to think of the best way to approach validating two emails fields for a form: I have an email field and a retype email field, and I want to compare them to see if they match. If not then display an error message saying so until the user enters in their email correctly for both fields.
I know there can be custom fields classes made to allow for custom validation but I'm using 2 separate fields, not one, and I'm not sure a custom class will work in this case since I'm not validating just one field.
I followed the example found here which involves adding custom validation to the form's submit function: https://docs.silverstripe.org/en/3/developer_guides/forms/validation/
However, I don't want to redirect anywhere -- I want to stay on the form.
I'd like to make this as simple as possible if I can help it.
Here is what I have so far. Other than the issue I mentioned, everything else works fine:
public function DemoForm() {
$fieldsArr = array();
$firstName = new TextField('first_name', 'First Name');
$firstName->setAttribute('placeholder', 'First Name');
array_push($fieldsArr, $firstName);
$lastName = new TextField('last_name', 'Last Name');
$lastName->setAttribute('placeholder', 'Last Name');
array_push($fieldsArr,$lastName);
$companytName = new TextField('company_name', 'Company Name');
$companytName->setAttribute('placeholder', 'Company Name');
array_push($fieldsArr,$companytName);
$email = new EmailField('email', 'Email');
$email->setAttribute('placeholder', 'Email');
array_push($fieldsArr, $email);
$retypeemail = new EmailField('retype_email', 'Retype Email');
$retypeemail->setAttribute('placeholder', 'Retype Email');
array_push($fieldsArr, $retypeemail);
$fields = new FieldList($fieldsArr);
$actions = new FieldList(
new FormAction('submit', 'Submit')
);
$validator = new RequiredFields('first_name', 'last_name', 'email', "retype_email");
return new Form($this, 'DemoForm', $fields, $actions, $validator);
}
public function submit($data, $form) {
if($data['email'] != $data['retype_email']){
$form->addErrorMessage('Email', 'The emails do not match.', 'bad');
return $this->redirectBack();
}
else{
$demoRequest = new DemoFormSubmission();
$demoRequest->FirstName = $data['first_name'];
$demoRequest->LastName = $data['last_name'];
$demoRequest->EmailAddress =$data['email'];
$demoRequest->CompanyName = $data['company_name'];
$demoRequest->write();
$email = new Email();
$email->setTo('sara.dejaneiro#innismaggiore.com');
$email->setFrom($data['email']);
$email->setSubject("Demo request submission");
$messageBody = "
<p><strong>First Name:</strong> {$data['first_name']}</p>
<p><strong>Last Name:</strong> {$data['last_name']}</p>
<p><strong>Email: </strong> {$data['email']}</p>
<p><strong>Company Name:</strong> {$data['company_name']}</p>
";
$email->setBody($messageBody);
$email->send();
return $this->redirect("demo-request-submission-thank-you-page");
}
}
Given your reply to my comment, I think you would probably be best to either:
Create your own form field that extends CompositeField and then add a custom validate function (as per https://docs.silverstripe.org/en/3/developer_guides/forms/validation/#form-validation)
Creating a custom validator, by extending RequiredFields (https://github.com/silverstripe/silverstripe-framework/blob/3.6.2/forms/RequiredFields.php) and then overloading the PHP method (https://github.com/silverstripe/silverstripe-framework/blob/3.6.2/forms/RequiredFields.php#L82).
Number 2 might be the less fiddly approach. You can write your own custom logic and then if the validation fails simply return "false" and then SilverStripe will cancel form submission and return the user to the form with their data still in the form fields.
Related
I want to store the email body in my communications table. My controller:
Mail::to($user->email)->send(new WelcomeEmail($subscription));
Communication::create([
'to' => $user->email,
'subject' => 'Welcome Email',
'body' => '???'
]);
My email goes out (successfully) and I am able to create a Communication record, but have no idea how to retrieve the email body.
After reading the Mail manual, I thought I could work with an event:
protected $listen = [
'Illuminate\Mail\Events\MessageSending' => [
'App\Listeners\LogSentMessage',
],
];
But here I get only the full plain text email. If I create an instance of the mail, with:
$email = Mail::to($user->email)->send(new WelcomeEmail($subscription));
the outcome of dd($email); is null.
Some extra info, in my WelcomeEmail.php, I am using a view:
public function build()
{
return $this->view('emails.welcome_email');
}
You can directly do this to render Mailable and store it in a variable
$html = (new WelcomeEmail($subscription))->render();
I found a solution, hopefully it can help other people.
$body = View::make('emails.welcome_email')
->with('subscription', $this->subscription)
->with('template', $this->template)
->render();
I rendered the view and saved it in the body variable.
I am new in Zend.
I have tried to create registration form in Zend.
I have get an array of form but it return false.
It returns me every time bye .
I don't know why???
It's Simple:
First, create your registration form under application/forms or use zend tool
zf enable form
zf create form registration
this will create a file under application/forms entitled Registration.php
class Application_Form_Registration extends Zend_Form
{
public function init()
{
$firstname = $this->createElement('text','firstname');
$firstname->setLabel('First Name:')
->setRequired(false);
$lastname = $this->createElement('text','lastname');
$lastname->setLabel('Last Name:')
->setRequired(false);
$email = $this->createElement('text','email');
$email->setLabel('Email: *')
->setRequired(false);
$username = $this->createElement('text','username');
$username->setLabel('Username: *')
->setRequired(true);
$password = $this->createElement('password','password');
$password->setLabel('Password: *')
->setRequired(true);
$confirmPassword = $this->createElement('password','confirmPassword');
$confirmPassword->setLabel('Confirm Password: *')
->setRequired(true);
$register = $this->createElement('submit','register');
$register->setLabel('Sign up')
->setIgnore(true);
$this->addElements(array(
$firstname,
$lastname,
$email,
$username,
$password,
$confirmPassword,
$register
));
}
}
This is a simple form with limited validation (only validation for fields that are required!)
Then you have to render the form in a view using the corresponding action:
as example in your UserController add action called
public function registerAction() {
//send the form to the view (register)
$userForm = new Application_Form_Registration();
$this->view->form = $userForm;
//check if the user entered data and submitted it
if ($this->getRequest()->isPost()) {
//check the form validation if not valid error message will appear under every required field
if ($userForm->isValid($this->getRequest()->getParams())) {
//send data to the model to store it
$userModel = new Application_Model_User();
$userId = $userModel->addUser($userForm->getValues());
}
}
}
The last two, is to render the form in the view called register
using
<?= $this->form ?>
and add method in your model called addUser() to handle the insertion of data into the database
I have a form for the creation of new "groups". I now added a small "go back" image with which the user should be able to go back one step. I don't know why, but when I click this new image, the controller and action used for the form which I want to leave (/admin/creategroup) is called again with HTTP POST set. Therefore, the form validation is done, and I'm stuck at this form with the validation errors displayed.
This is a snippet of the code from my form with both image-buttons. I wan't the "go back"-image to redirect me to the specified controller without validating the form:
$this->addElement('image', 'btnBack', array (
'name' => 'btnBack',
'id' => 'btnBack',
'label' => '',
'title' => 'Go back',
'alt' => 'Go back',
'src' => '/img/undo.png',
'onClick' => "window.location='/admin/groupoverview'"
));
$this->addElement('image', 'btnSave', array (
'name' => 'btnSave',
'id' => 'btnSave',
'label' => '',
'title' => 'Save this new group',
'alt' => 'Save this new group',
'src' => '/img/save.png',
'onClick' => "document.forms[0].submit();"
));
Edit:
I already thought of the possibility to check in /admin/creategroup whether it was called from the 'btnBack'-image or the 'btnSave'-image and skip form validation and redirect correctly if the source was the 'btnBack'-image.
I just think that there should be a nicer solution to directly redirect from the form and circumvent calling /admin/creategroup again.
Edit2:
My view script:
<div id="createGroupMask">
<br/>
Use the form below to create a new group
<?php
$this->form->setAction($this->url());
echo $this->form;
?>
</div>
My action in the controller:
public function creategroupAction()
{
$form = new Application_Form_CreateGroup();
$request = $this->getRequest();
if ($request->isPost()) {
if ($form->isValid($request->getPost())) {
// Data for new group is valid
...
} else {
// Form data was invalid
// => This is where I land when pressing the 'back' image
// No further code here
}
}
$this->view->form = $form;
}
Now there is something to work with:
The isValid() loop is incorrect, your form will never evaluate as inValid with respect to the elements you've presented, you will never get to the else.
public function creategroupAction()
{
$form = new Application_Form_CreateGroup();
$request = $this->getRequest();
if ($request->isPost()) {
if ($form->isValid($request->getPost())) {
// Data for new group is valid
...
} else {
/* This is incorrect */
// Form data was invalid
// => This is where I land when pressing the 'back' image
// No further code here
}
}
$this->view->form = $form;
}
My problem is that I'm not sure what is going to be submitted from your form, I'm not really familiar with how your using "onClick" and what I presume is javascript. It looks like element btnBack should redirect on click and element btnSave should POST. However this does not seem to be happening.
I have done this type of thing in PHP and ZF with submit buttons, perhaps the flow of what I did will help:
NOTE: for this type of flow to work you must give the button element a label. The label is used as the submit value.
//psuedoCode
public function creategroupAction()
{
$form = new Application_Form_CreateGroup();
$request = $this->getRequest();
if ($request->isPost()) {
if ($form->isValid($request->getPost())) {
//I would probably opt to perform this task with a switch loop
if ($form->getValue('btnBack') === some true value) {
$this->_redirect('new url');
}
if ($form->getValue('btnSave') === some true value) {
//Process and save data
}
} else {
//Display form errors
}
$this->view->form = $form;
}
I think when all is said and done the crux of your problem is that you did not give your button elements a label.
I tried adding labels to my images, but this didn't work.
I also tried to use the isChecked() method on my btnBack-image like this:
if ($form->btnBack->isChecked()) {
// 'Go back' image was clicked so this is no real error, just redirect
}
This didn't work either.
I finally was able to check which image was clicked via the following method as answered in Zend form: image as submit button:
public function creategroupAction()
{
$form = new Application_Form_CreateGroup();
$request = $this->getRequest();
if ($request->isPost()) {
if ($form->isValid($request->getPost())) {
// Data for new group is valid
...
} else {
// Form data was invalid
if (isset($this->_request->btnBack_x)) {
// 'Go back' image was pressed, so this is no error
// -> redirect to group overview page
$this->_redirect('/admin/groupoverview');
}
}
}
$this->view->form = $form;
}
I guess this doesn't thoroughly answer the original question as the validation is still done and I'm only checking for this 'special case' where the 'Go back' image was clicked, but I'll mark it as answered anyways.
Tim Fountain suggested an even cleaner approach in my somewhat related question:
Zend forms: How to surround an image-element with a hyperlink?
I have the following code to generate an input field for user's email address
$email = new Zend_Form_Element_Text('email');
$email->setLabel('Email:')
->addFilters(array('StringTrim', 'StripTags'))
->addValidator('EmailAddress')
->addValidator(new Zend_Validate_Db_NoRecordExists(
array(
'adapter'=>Zend_Registry::get('user_db'),
'field'=>'email',
'table'=>'tbl_user'
)))
->setRequired(true)
->setDecorators(array(
array('Label', array('escape'=>false, 'placement'=>'append')),
array('ViewHelper'),
array('Errors'),
array('Description',array('escape'=>false,'tag'=>'div')),
array('HtmlTag', array('tag' => 'div')),
));
$this->addElement($email);
now the problem is if user enter invalid hostname for email, it generate 3 errors. lets say user enter 'admin#l' as email address, and the errors will be
* 'l' is no valid hostname for email address 'admin#l'
* 'l' does not match the expected structure for a DNS hostname
* 'l' appears to be a local network name but local network names are not allowed
I just want it to give only one custom error instead of all these. If I set error message "Invalid Email Address" by addErrorMessage method, it will again generate the same message against the db_validation.
Well, it's a late answer but I think is always useful.
Simply add true as second param of addValidator()
From Zend docs (http://framework.zend.com/apidoc/1.8/):
addValidator (line 67)
Adds a validator to the end of the chain
If $breakChainOnFailure is true, then if the validator fails, the next
validator in the chain, if one exists, will not be executed.
return: Provides a fluent interface
access: public
Here the signature:
Zend_Validate addValidator (Zend_Validate_Interface $validator, [boolean $breakChainOnFailure = false])
Zend_Validate_Interface $validator
boolean $breakChainOnFailure
So the code is:
$email = new Zend_Form_Element_Text('email');
$email->setLabel('Email:')
->addFilters(array('StringTrim', 'StripTags'))
->addValidator('EmailAddress', TRUE ) // added true here
->addValidator(new Zend_Validate_Db_NoRecordExists(
array(
'adapter'=>Zend_Registry::get('user_db'),
'field'=>'email',
'table'=>'tbl_user'
), TRUE )
);
You have to create an instance of the Zend_Validate_EmailAddress class and call the setMessages method and then override the messages that you like, to remove the ones that you mention it would be something like this:
$emailValidator->setMessages(array(
Zend_Validate_EmailAddress::INVALID_FORMAT => "Your error message",
Zend_Validate_Hostname::INVALID_HOSTNAME => "Your error message",
Zend_Validate_Hostname::LOCAL_NAME_NOT_ALLOWED => "Your error message"
));
I hope this help somebody :-)
$email->addErrorMessage("Please Enter Valid Email Address");
you can use custom validator. create a file Email.php inside folder Validate in your library folder at the root of project
class Validate_Email extends Zend_Validate_Abstract
{
const INVALID = 'Email is required';
protected $_messageTemplates = array(
self::INVALID => "Invalid Email Address",
self::ALREADYUSED => "Email is already registered"
);
public function isValid($value)
{
if(preg_match($email_regex, trim($value))){
$dataModel = new Application_Model_Data(); //check if the email exists
if(!$dataModel->email_exists($value)){
return true;
}
else{
$this->_error(self::ALREADYUSED);
return false;
}
}
else
{
$this->_error(self::INVALID);
return false;
}
}
}
and in you form.php file
$mailValidator = new Validate_Email();
$email->addValidator($mailValidator, true);
Don't know if it works or not but for me it worked in case of telephone. Courtesy of http://softwareobjects.net/technology/other/zend-framework-1-10-7-telephone-validator/
It seems to be missing quite a few lines...
probably should use this:
$mailValidator = new Zend_Validate_EmailAddress();
you can also do some other validations see here: http://framework.zend.com/manual/en/zend.validate.set.html
Using a custom validator is the only way I found to avoid this problem.
If what you want is:
Having only one error message if the email address is in a wrong format
If the format is good, then validate if the email address is already in the database
Then I suggest you to do something like this:
$where = array('users', 'email', array('field' => 'user_id',
'value' => $this->getAttrib('user_id')));
$email = new Zend_Form_Element_Text('email');
$email->setLabel('E-mail:')
->setRequired(true)
->setAttrib('required name', 'email') // html5
->setAttrib('maxlength', '50')
->addFilter('StripTags')
->addFilter('StringTrim')
->addFilter('StringToLower')
->addValidator('email', true)
->addValidator('stringLength', true, array(1, 50))
->addValidator('db_NoRecordExists', true, $where)
->addDecorators($this->_elementDecorators);
$this->addElement($email);
$this->getAttrib('user_id') represents the current user's id.
There are three validators here, all of them have their second parameter $breakOnFailureset to false, so if a validator fails, the other ones won't be called.
The first validator is email, which is my own custom validator:
class My_Validate_Email extends Zend_Validate_EmailAddress
{
public function getMessages()
{
return array('invalidEmail' => 'Your email address is not valid.');
}
}
You can add this validator in your library, in /application/library/My/Validate for example, and then add
$this->addElementPrefixPath('My_Validate', 'My/Validate', 'validator');
into your form. Of course, you need to replace "My" by the name of your library.
Now if an email is in the wrong format, it will always display 'Your email address is not valid.'. If your email is too long and doesn't fit into your database field (VARCHAR(100) for example), it's going to show your stringLength validator errors, and in the last case, if an entry already exists in the database, only this error will be shown.
Of course you can add more methods into your custom validator and overload setMessages, so that you can display your own messages whatever the form you are working on.
Hope it can help someone!
Is there any good solution for the following requirements:
A form with one field for zip code and default validators like number, length, etc.
After submission, the form is checked against a database.
If the zip code is not unique we have to ask for an city.
Examples:
Case 1: Submited zip code is unique in database. Everything is okay. Process form
Case 2: Submited zip code is not unique. Add a second field for city to the form. Go back to form.
We want to handle this in an generic way (not inside an controller). We need this logic for
a lot of forms. First thought was to add it to isValid() to every form or write a
validator with logic to add fields to the form. Subforms are not possible for us, because we need this for different fields (e.g. name and street).
Currently I'm using isValid method inside my forms for an User Form to verify the password and confirm password field. Also, when the form is displayed in a New Action, there are no modifications, but when displayed in an Edit Action, a new field is added to the form.
I think that is a good option work on the isValid method and add the field when the validation return false, and if you want something more maintainable, you should write your own validatator for that purpose.
Take a look at my code:
class Admin_Form_User extends Zf_Form
{
public function __construct($options = NULL)
{
parent::__construct($options);
$this->setName('user');
$id = new Zend_Form_Element_Hidden('id');
$user = new Zend_Form_Element_Text('user');
$user->setLabel('User:')
->addFilter('stripTags')
->addFilter('StringTrim')
->setAllowEmpty(false)
->setRequired(true);
$passwordChange = new Zend_Form_Element_Radio('changePassword');
$passwordChange->setLabel('Would you like to change the password?')
->addMultiOptions(array(1 => 'Sim', 2 => 'Não'))
->setValue(2)
->setSeparator('');
$password = new Zend_Form_Element_Password('password');
$password->setLabel('Password:')
->addFilter('stripTags')
->addFilter('StringTrim')
->setRequired(true);
$confirm_password = new Zend_Form_Element_Password('confirm_password');
$confirm_password->setLabel('Confirm the password:')
->addFilter('stripTags')
->addFilter('StringTrim')
->addValidator('Identical')
->setRequired(true);
$submit = new Zend_Form_Element_Submit('submit');
$submit->setLabel('Save');
$this->addElements(array($id,$name,$lastname,$group,$user,$passwordChange,$password,$confirm_password,$submit));
$this->addDisplayGroup(array('password','confirm_password'),'passwordGroup');
$this->submit->setOrder(8);
$this->setDisplayGroupDecorators(array(
'FormElements',
array('HtmlTag', array('tag' => 'div','id' => 'div-password'))
)
);
$passwordChange->clearDecorators();
}
public function addPasswordOption()
{
$this->changePassword->loadDefaultDecorators();
$this->getDisplayGroup('passwordGroup')
->addDecorators(array(
array('HtmlTag', array('tag' => 'div','id' => 'div-password'))
)
);
$this->password->setRequired(false);
$this->confirm_password->setRequired(false);
}
public function setPasswordRequired($flag = true)
{
$this->password->setRequired($flag);
$this->confirm_password->setRequired($flag);
}
public function isValid($data)
{
$confirm = $this->getElement('confirm_password');
$confirm->getValidator('Identical')->setToken($data['password']);
return parent::isValid($data);
}
}
So, in my controller:
public function newAction()
{
$this->view->title = "New user";
$this->view->headTitle($this->view->title, 'PREPEND');
$form = $this->getForm();
if($this->getRequest()->isPost())
{
$formData = $this->_request->getPost();
if($form->isValid($formData))
{
$Model = $this->getModel();
$id = $Model->insert($formData);
$this->_helper->flashMessenger('The user data has beed updated.');
$this->_helper->redirector('list');
}
}
$this->view->form = $form;
}
public function editAction()
{
$this->view->title = "Edit user";
$this->view->headTitle($this->view->title, 'PREPEND');
$id = $this->getRequest()->getParam('id');
$form = $this->getForm();
// Add yes or no password change option
$form->addPasswordOption();
$Model = $this->getModel();
if($this->getRequest()->isPost())
{
$formData = $this->getRequest()->getPost();
// Change password?
if($formData['changePassword'] == 2) $form->setPasswordRequired(false);
if($form->isValid($formData))
{
$Model->update($formData);
$this->_helper->flashMessenger('The user data has beed updated.');
$this->_helper->redirector('list');
}
}
$data = $Model->getById($id)->toArray();
$form->populate($data);
$this->view->form = $form;
}
You will probably need a Javascript form validator for that. In the submit function perform an AJAX call to check if the zipcode is unique. If not, show an extra city field.
But you still have to perform the validation server side: never trust user input, even if it's validated on the client side.