I have two password fields. That extra is for confirmation.
And there is no error message when both are empty or when passwords don't match.
How to enable or debug this validation which I presume should be working out of the box (http://symfony.com/doc/current/reference/forms/types/repeated.html#validation)?
class MyType extends AbstractType
{
public function setDefaultOptions(OptionsResolverInterface $resolver)
{
$resolver->setDefaults(array(
'data_class' => 'My\\Bundle\\Entity\\User',
));
}
public function buildForm(FormBuilderInterface $builder, array $options)
{
...
$builder->add('password', 'repeated', array(
'invalid_message' => 'Passwords must match.',
'type' => 'password',
'required' => true,
'first_options' => array('label' => 'Password'),
'second_options' => array('label' => 'Repeat Password')
));
form.html.twig
...
<form action="" method="post" novalidate>
...
<tr>
<td>{{ form_label(regForm.password.first) }}</td>
<td>{{ form_widget(regForm.password.first, { 'attr': {'pattern': '.{6,}','title': 'Minimum 6 characters long'} } ) }}</td>
</tr>
<tr>
<td>{{ form_label(regForm.password.second) }}</td>
<td>{{ form_widget(regForm.password.second, { 'attr': {'pattern': '.{6,}', 'title': 'Minimum 6 characters long', 'data-equals': '#register_password_first'} } ) }}</td>
</tr>
Validators declared in validation.yml seem to have no effect on the issue.
My\Bundle\Entity\User:
properties:
...
password:
# this one does not work because of "repeated" type in buildForm()???
- NotBlank:
message: "Password is required."
- MinLength:
limit: 6
message: "Password must have at least {{ limit }} characters."
Controller.php (original version)
...
if (!$form->isValid()) {
$formFields = $form->all();
foreach ($formFields as $key => $field) {
if ($field->getErrorsAsString()) {
$errors = $field->getErrors();
foreach ($errors as $error) {
$messages[$key][] = $error->getMessage();
}
}
}
var_dump($messages);
EDIT: Now errors retrieval looks like this (thanks to alex.dominte):
public static function getFormErrorMessages(\Symfony\Component\Form\Form $form)
{
$messages = array();
foreach ($form->getErrors() as $i => $error) {
$messages[$i] = $error->getMessage();
}
foreach ($form->all() as $child) {
if (!$child->isValid()) {
$messages[$child->getName()] = self::getFormErrorMessages($child);
}
}
return $messages;
}
Modifications suggested by Pedro Cordeiro make it possible to drop recursion but also make field names unavailable.
Two things missing from your definitions:
1) error_bubbling must be manually set to TRUE on your fields, so they can throw errors in the ->getErrors() function.
2) cascade_validation is FALSE by default, you must add it too to your parent forms ($builder->add('propertyName', new YourFormNameType(), array('cascade_validation ' => true));) - See this
Not enough recursion
foreach ($formFields as $key => $field) {
if ($field->getErrorsAsString()) {
$errors = $field->getErrors();
foreach ($errors as $error) {
$messages[$key][] = $error->getMessage();
}
}
}
Try this - should work for all nested errors:
class FormHelper
{
public static function getErrorMessages($form)
{
$errors = array();
if (!$form->isBound()) return $errors;
foreach ($form->getErrors() as $key => $error) {
$template = $error->getMessageTemplate();
$parameters = $error->getMessageParameters();
foreach ($parameters as $var => $value) {
$template = str_replace($var, $value, $template);
}
$errors[$key] = $template;
}
if ($form->hasChildren()) {
foreach ($form->getChildren() as $child) {
if (!$child->isValid()) {
$name = $child->getConfig()->getOption('title', $child->getName());
$errors[$name] = self::getErrorMessages($child);
}
}
}
return $errors;
}
}
you do not show error message
{{ form_errors(regForm.password.first) }}
{{ form_errors(regForm.password.second) }}
Related
** Very new to CodeIgniter so please be kind! **
I have an issue with my two user authentication forms: users/register.php and users/login.php where I cannot pass the post input to functions in my model.
As of now, I'm getting the error Call to member function addUser() on null on the registration form and a validation error on the login form that states the username/password don't match any credentials in the database. Both seem to stem from post being null although it is not.
I have done a var_dump on $login which is defined as $login = $this->request->getPost() as well as inspected the request in Firefox Developers Browser to find all the post data correctly displayed. I am stumped. Why can't I pass this array to my model?
Here is a screenshot of the post request for login.php (the same can be said for registration.php and is not included).
These are my routes:
// Login and Registration
$routes->match(['get', 'post'], 'users/register', 'Users::register');
$routes->match(['get', 'post'], 'users/login', 'Users::login', ["filter" => "noauth"]);
Here is my model UserModel.php in its entirety:
class UserModel extends Model
{
protected $DBGroup = 'default';
protected $table = 'users';
protected $primaryKey = 'username';
protected $useAutoIncrement = false;
protected $insertID = 0;
protected $returnType = 'object';
protected $useSoftDelete = false;
protected $allowedFields = [
'username',
'password',
'id',
'role',
'profile_image',
'profile_views',
'last_login',
'about_me',
'age',
'gender',
'occupation',
'hometown',
'country',
'fav_shape',
'fav_color',
'created',
'modified',
];
// Dates
protected $useTimestamps = true;
protected $dateFormat = 'datetime';
protected $createdField = 'created';
protected $modifiedField = 'modified';
// Callbacks
protected $allowCallbacks = true;
protected $beforeInsert = ['beforeInsert'];
public function __construct()
{
parent::__construct();
}
protected function beforeInsert(array $data)
{
$data = $this->passwordHash($data);
return $data;
}
protected function passwordHash(array $data)
{
if (isset($data['password'])) {
$data['password'] = password_hash($data['password'], PASSWORD_DEFAULT);
}
return $data;
}
public function lastLogin($username)
{
$this->builder()
->where('username', $username)
->update('last_login', date('Y-m-d H:i:s'));
}
public function addUser($newUser)
{
$builder = $this->builder()
->set($newUser)
->insert();
if ($builder->affected_rows() == 1) {
return TRUE;
} else {
return FALSE;
}
}
public function getUser($username)
{
$builder = $this->builder()
->where(['username' => $username])
->limit(1);
if ($builder->countAllResults() === 1) {
return $builder->get()->getRow();
} else {
return FALSE;
}
}
}
Here are excerpts from my controller Users.php:
class Users extends BaseController
{
protected $userModel;
public function __construct()
{
$userModel = new UserModel();
}
public function login()
{
$validation = \Config\Services::validation();
// Set session variable
$session = session();
if ($this->request->getMethod() === 'post' && ! empty($_POST)) {
$validation->getRuleGroup('login');
$validation->setRuleGroup('login');
$validation->withRequest($this->request)->run();
$recaptchaResponse = trim($this->request->getVar('g-recaptcha-response'));
$userIp = $this->request->getIPAddress();
$secret = env('recaptcha2_secretkey');
$credential = [
'secret' => $secret,
'response' => $recaptchaResponse,
'remoteip' => $userIp,
];
$verify = curl_init();
curl_setopt($verify, CURLOPT_URL, 'https://www.google.com/recaptcha/api/siteverify');
curl_setopt($verify, CURLOPT_POST, TRUE);
curl_setopt($verify, CURLOPT_POSTFIELDS, http_build_query($credential));
curl_setopt($verify, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($verify, CURLOPT_RETURNTRANSFER, TRUE);
$response = curl_exec($verify);
$status = json_decode($response, TRUE);
curl_close($verify);
if (empty($validation->getErrors()) && $status['success']) {
$login = $this->request->getPost();
$user = $this->userModel->getUser($login['username']);
// Storing session values
$this->setUserSession($user);
// Storing success message
$session->setFlashdata('success', 'You have successfully logged in!');
// Update last login datetime
$this->userModel->lastLogin($login['username']);
// Redirecting to dashboard after login
if ($user['role'] == 1) {
return redirect()->to('admin/dashboard');
} elseif ($user['role'] == 0) {
return redirect()->to('members/dashboard');
}
} else {
$data = [
'title' => 'Login',
'errors' => $validation->getErrors(),
];
echo view('templates/index_header', $data);
echo view('users/login');
echo view('templates/footer', $data);
}
} else {
$data = [
'title' => 'Login',
];
echo view('templates/index_header', $data);
echo view('users/login');
echo view('templates/footer', $data);
}
}
/**
* Sets session with user id, username, isLoggedIn, and role for use in member/admin site
* #param model user data
* #return boole if session was set successfully
*/
private function setUserSession($user)
{
$data = [
'id' => $user->id,
'username' => $user->username,
'profile_image' => $user->profile_image,
'isLoggedIn' => true,
'role' => $user->role,
];
if (session()->set($data)) {
return true;
} else {
return false;
}
}
public function register()
{
$validation = \Config\Services::validation();
if ($this->request->getMethod() == 'post' && ! empty($_POST)) {
$validation->getRuleGroup('registration');
$validation->setRuleGroup('registration');
$validation->withRequest($this->request)->run();
if (empty($validation->getErrors())) {
$newUser = $this->request->getPost();
if ($this->userModel->addUser($newUser)) {
$this->session->setFlashdata('success', 'Successful Registration');
$data['title'] = 'Login';
echo view('templates/index_header', $data);
echo view('users/login');
echo view('templates/footer', $data);
} else {
$this->session->setFlashdata('error', 'Something went wrong with your registration! Please try again.');
}
} else {
$data = [];
$data = [
'title' => 'Register',
'script' => 'js/click_link',
'errors' => $validation->getErrors(),
];
echo view('templates/index_header', $data);
echo view('users/register', $data);
echo view('templates/footer', $data);
}
} else {
$data = [
'title' => 'Register',
'script' => 'js/click_link',
];
echo view('templates/index_header', $data);
echo view('users/register', $data);
echo view('templates/footer', $data);
}
}
}
These are my validation rules in Config\Validation:
/**
* Registration
*/
public $registration = [
'username' => 'required|is_unique[users.username,username]|min_length[5]|max_length[25]|alpha_dash|badWordsFilter[username]',
'password' => 'required|min_length[8]|max_length[255]|regex_match[/^(?=.*[!##$%^&*-])(?=.*[0-9])(?=.*[A-Z]).{8,255}$/]',
'pass_confirm' => 'required|matches[password]',
'about_me' => 'permit_empty|max_length[250]|alpha_numeric_punct|badWordsFilter[about_me]',
'occupation' => 'permit_empty|max_length[50]|alpha_space|badWordsFilter[occupation]',
'hometown' => 'permit_empty|max_length[50]|alpha_space|badWordsFilter[hometown]',
'age' => 'permit_empty|less_than[100]|greater_than[0]|numeric',
'country' => 'permit_empty',
];
/**
* Password Verification
*/
public $login = [
'password' => 'required|validateUser[username,password]',
];
This is my custom rule to authenticate username and password credentials User_rules:
class User_rules
{
/**
* Checks if input username exists in database and then checks whether the input password matches the hash for that username
* #param string $str is the input password
* #param string $fields are the associated form fields that are being used
* #param array $data is an array containing the values for the fields indexed by field names
* #return boolean true or false depending on if the user exists and the password matches the hashed password stored in the database
*/
public function validateUser(string $str, string $fields, array $data)
{
$userModel = new UserModel();
$user = $userModel->getUser($data['username']);
if(!$user) {
return FALSE;
}
return password_verify($data['password'], $user->password);
}
Lastly, my view for login.php:
<div class='form-container'>
<?= form_open('users/login',['autocomplete' => FALSE]); ?>
<div class='form-header'>
<h2>Login</h2>
</div>
<div class='form-body'>
<div class='form-row'>
<div class='input-container'>
<i class='fas fa-user'></i>
<?php $attributes = [
'type' => 'text',
'name' => 'username',
'class' => 'input-field',
'id' => 'username',
'placeholder' => 'Username',
'required' => TRUE,
]; ?>
<?= form_input($attributes); ?>
</div>
</div>
<div class='form-row'>
<div class='input-container'>
<i class='fas fa-lock'></i>
<?php $attributes = [
'type' => 'password',
'name' => 'password',
'class' => 'input-field',
'placeholder' => 'Password',
'required' => TRUE,
]; ?>
<?= form_input($attributes); ?>
</div`>
</div>
</div>
<div class='captcha-container'>
<div class='g-recaptcha' data-sitekey='<?= env('recaptcha2_sitekey'); ?>'></div>
</div>
<div class='form-footer'>
<?php $submit = [
'name' => 'loginSubmit',
'value' => 'Login',
'class' => 'submit-btn',
];?>
<?= form_submit($submit); ?>
</div>
<h4 style='text-align: center'>Not a member yet? Register
<a href= <?= site_url('users/register'); ?> title = 'Register'> HERE</a>
</h4>
<?= form_close(); ?>
</div>
It was a stupid mistake. Someone on the codeigniter forum answered my question here: CodeIgniter Forum
basically in my constructor I needed $this->userModel = new UserModel(); instead of $userModel = new UserModel();.
My controller acts as if I never click on the submit button.
My controller:
public function listAction(RegionsService $service)
{
$regions = $service->getRegionList();
$editForm = $this->createForm('App\Form\RegionListType');
if ($editForm->isSubmitted())
{
dump('submitted');
die();
}
if ($editForm->isSubmitted() && $editForm->isValid()) {
$task = $editForm->getData();
dump($task);
die();
...
}
return $this->render('parameter/region.list.html.twig', [
'form' => $editForm->createView(),
'regions' => $regions
]);
...
My form :
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('regionsset', TextType::class, array(
'required' => false))
->add('save', SubmitType::class, array(
'attr' => array('class' => 'save')));
}
My view :
{{ form_start(form, {'action' : path('app_region_list')} ) }}
{{ form_widget(form.regionsset, {'attr': {'class': 'foo'}}) }}
{{ form_widget(form.save, { 'label': 'Save' }) }}
{{ form_end(form) }}
When I click on the submit button, the controller never goes into the first test if ($editForm->isSubmitted())
What did I miss ?
You forgot to handle the request in your form. After creating the form, ($editForm), you have to handle the request as follows:
$editForm->handleRequest($request);
After that, the method isSubmitted() will return true.
Try this code. It will resolve the problem.
use Symfony\Component\HttpFoundation\Request;
.
.
.
public function listAction(Request $request, RegionsService $service )
{
$regions = $service->getRegionList();
$editForm = $this->createForm('App\Form\RegionListType');
$editForm->handleRequest($request);
if ($editForm->isSubmitted() && $editForm->isValid()) {
$task = $editForm->getData();
dump($task);
die();
...
}
return $this->render('parameter/region.list.html.twig', [
'form' => $editForm->createView(),
'regions' => $regions
]);
...
I am using the search plugin by CakeDC.
I have filtering between dates working, but after performing a search the Date dropdowns are not repopulated with the searched date.
Can anyone figure out why my date forms are not repopulating? I thought it might be because I am converting the date to a string in the or conditions, but as far as I can tell the or Conditions demand a string.
Here is the code:
Model
public $filterArgs = array(
'from_date' => array('type' => 'query', 'method' => 'orConditionsFromDate'),
'end_date' => array('type' => 'query', 'method' => 'orConditionsEndDate'),
);
public function orConditionsFromDate($data = array()) {
$filterFrom = $data['from_date'];
$fromString = "$filterFrom[year]-$filterFrom[month]-01";
$cond = array(
'MovieStar.reject_date >=' => $fromString,
);
return $cond;
}
public function orConditionsEndDate($data = array()) {
$filterEnd = $data['end_date'];
$endString = "$filterEnd[year]-$filterEnd[month]-$filterEnd[day]";
debug('$data');
debug($data);
$cond = array(
'MovieStar.reject_date <=' => $endString,
);
return $cond;
}
public function getFromDateFormOptions(){
$dateFieldOptions['type'] = 'date';
$dateFieldOptions['div'] = array( 'style' => 'width:180px');
$dateFieldOptions['required'] = false;
$dateFieldOptions['maxYear'] = date('Y');
$fromDateFieldOptions = $dateFieldOptions;
$fromMonth = date('m') - 1;
$fromYear = date('Y');
if($fromMonth == 0){
$fromMonth = 12;
$fromYear = $fromYear - 1;
}
$fromDateFieldOptions['selected'] = array('day' => 1, 'month' => $fromMonth, 'year' => $fromYear );
$fromDateFieldOptions['label'] = 'From';
//$myFromDateFormField = $this->Form->input('from_date', $fromDateFieldOptions);
//return $myFromDateFormField;
return $fromDateFieldOptions;
}
public function getEndDateFormOptions(){
$dateFieldOptions['type'] = 'date';
$dateFieldOptions['div'] = array( 'style' => 'width:180px');
$dateFieldOptions['required'] = false;
$dateFieldOptions['maxYear'] = date('Y');
$endDateFieldOptions = $dateFieldOptions;
$endDate = date('t'); // last day of current month
$endDateFieldOptions['selected'] = array('day' => $endDate, 'month' => date('m'), 'year' => date('Y'));
$endDateFieldOptions['label'] = 'To';
//$myEndDateFormField = $this->Form->input('end_date', $endDateFieldOptions);
//return $myEndDateFormField;
return $endDateFieldOptions;
}
View:
<?php
echo $this->Form->create('MovieStar', array(
'url' => array_merge(array('action' => 'index'), $this->params['pass'])
));
?>
<table>
<tr>
<td>
<?php echo $this->Form->input('from_date', $myFromDateFormOptions); ?>
</td>
<td>
<?php echo $this->Form->input('end_date', $myEndDateFormOptions); ?>
</td>
</tr>
</table>
<?php
echo $this->Form->submit(__('Search'), array());
echo $this->Form->end();
?>
Controller
$myFromDateFormOptions = $this->MovieStar->getFromDateFormOptions();
$myEndDateFormOptions = $this->MovieStar->getEndDateFormOptions();
$this->set(compact('myFromDateFormOptions'));
$this->set(compact('myEndDateFormOptions'));
I have some sample code with 2 tabs. One shows a "form" and the other a "list" (Sample 1.5-1), I want to combine them.
I want to show the "form" tab with a "list" view at the bottom (after submit button). I am getting stuck on how to show this.
Inside IndexController, in the ->getRequest->isPost() area, I try to create and fill up the $list (same as the "list" tab example code):
$list=$this->_getListRandom();
$this->view->list = $list;
Then inside of form.phtml, I append:
<h1>My Report Data></h1>
<?php echo $this->list; ?>
I can see the "My Report Data" text in the web page, so I know I am getting to the right area in the code!
But, I get an error from pm_View_Helper_render::renderList() that it must be an instance of pm_View_List_Simple.
I am trying to create both a pm_Form_Simple and pm_View_List_Simple in the same $this, but not
sure if it is allowed or how to do it.
Thanks for any suggestions!
<?php
class IndexController extends pm_Controller_Action
{
public function init()
{
parent::init();
// Init title for all actions
$this->view->pageTitle = 'Example Module';
// Init tabs for all actions
$this->view->tabs = array(
array(
'title' => 'Form',
'action' => 'form',
),
array(
'title' => 'List',
'action' => 'list',
),
);
}
public function indexAction()
{
// Default action will be formAction
$this->_forward('form');
}
public function formAction()
{
// Init form here
$form = new pm_Form_Simple();
$form->addElement('text', 'exampleText', array(
'label' => 'Example Text',
'value' => pm_Settings::get('exampleText'),
'required' => true,
'validators' => array(
array('NotEmpty', true),
),
));
$form->addControlButtons(array(
'cancelLink' => pm_Context::getModulesListUrl(),
));
if ($this->getRequest()->isPost() && $form->isValid($this->getRequest()->getPost())) {
// Form proccessing here
pm_Settings::set('exampleText', $form->getValue('exampleText'));
$this->_status->addMessage('info', 'Data was successfully saved.');
# Create the LIST
$list = $this->_getListRandom();
$this->view->list = $list;
$this->_helper->json(array('redirect' => pm_Context::getBaseUrl()));
}
$this->view->form = $form;
}
public function listAction()
{
$list = $this->_getListRandom();
// List object for pm_View_Helper_RenderList
$this->view->list = $list;
}
public function listDataAction()
{
$list = $this->_getListRandom();
// Json data from pm_View_List_Simple
$this->_helper->json($list->fetchData());
}
private function _getListRandom()
{
$data = array();
#$iconPath = pm_Context::getBaseUrl() . 'images/icon_16.gif';
for ($i = 0; $i < 15; $i++) {
$data[] = array(
'column-1' => '' . (string)rand() . '',
'column-2' => (string)rand(),
);
}
$list = new pm_View_List_Simple($this->view, $this->_request);
$list->setData($data);
$list->setColumns(array(
'column-1' => array(
'title' => 'Random with link',
'noEscape' => true,
),
'column-2' => array(
'title' => 'Random with image',
'noEscape' => true,
),
));
// Take into account listDataAction corresponds to the URL /list-data/
$list->setDataUrl(array('action' => 'list-data'));
return $list;
}
}
Please, try https://mega.co.nz/#!NxtyzbyS!JQqFdh7ruESQU_pgmhM6vi8yVFZQRTH-sPDeQcAOFws
Following files are changed:
\example-1.5-2\plib\views\scripts\index\form.phtml:
<?php echo $this->renderTabs($this->tabs); ?>
<?php echo $this->test; ?>
<?php echo $this->form; ?>
<?php echo $this->renderList($this->list); ?>
\example-1.5-2\plib\controllers\IndexController.php:
public function formAction() {
...
...
...
$list = $this->_getListRandom();
// List object for pm_View_Helper_RenderList
$this->view->list = $list;
}
and here the result:
hello i have a form where the user can click on a button and dinamically add new elements(with Jquery)
<input name="sconto[]" type="text"><br>
<input name="sconto[]" type="text"><br>
<input name="sconto[]" type="text"><br>
...
I have a custom validator for float numbers in format with comma and dot separation like 20.50 and 20,50
The problem is i can't seem to find how to make zend apply it it to each element of the array.
So how should i declare this element and how to apply the validator? xD
this is my validator
protected $_messageTemplates = array(
self::NON_E_NUMERO => 'non sembra essere un numero'
);
public function isValid($value, $context = null)
{
$pos_virgola = strpos($value, ",");
if ($pos_virgola !== false)
$value = str_replace(",", ".", $value);
if (!is_numeric($value))
{
$this->_error(self::NON_E_NUMERO, $value);
return false;
}
else
return true;
}
}
the form i don't know how to do it, i use this but obviously it doesn't work
$sconto = $this->createElement('text','sconto')->setLabel('sconto');
//->setValidators(array(new Gestionale_Validator_Float()));
$this->addElement($sconto);
...
$sconto->setDecorators(array(//no ViewHelper
'Errors',
'Description',
array(array('data' => 'HtmlTag'), array('tag' => 'td', /*'class' => 'valore_campo', */'id'=>'sconto')),
array('TdLabel', array('placement' => 'prepend', 'class' => 'nome_campo'))
));
If Marcin comment is not what you want to do, then this is another way to create multi text element.
Create a custom decorator 'My_Form_Decorator_MultiText'. You will need to register your custom decorator class. Read Zend Framework doc for details http://framework.zend.com/manual/en/zend.form.decorators.html
class My_Form_Decorator_MultiText extends Zend_Form_Decorator_Abstract {
public function render($content) {
$element = $this->getElement();
if (!$element instanceof Zend_Form_Element_Text) {
return $content;
}
$view = $element->getView();
if (!$view instanceof Zend_View_Interface) {
return $content;
}
$values = $element->getValue();
$name = $element->getFullyQualifiedName();
$html = '';
if (is_array($values)) {
foreach ($values as $value) {
$html .= $view->formText($name, $value);
}
} else {
$html = $view->formText($name, $values);
}
switch ($this->getPlacement()) {
case self::PREPEND:
return $html . $this->getSeparator() . $content;
case self::APPEND:
default:
return $content . $this->getSeparator() . $html;
}
}
}
Now your validation class will validate each element value
class My_Validate_Test extends Zend_Validate_Abstract {
const NON_E_NUMERO = 'numero';
protected $_messageTemplates = array(
self::NON_E_NUMERO => 'non sembra essere un numero'
);
public function isValid($value, $context = null) {
if (!is_numeric($value)) {
$this->_error(self::NON_E_NUMERO, $value);
return false;
}
else
return true;
}
}
This is how you can use the new decorator
$element = new Zend_Form_Element_Text('sconto', array(
'validators' => array(
new My_Validate_Test(),
),
'decorators' => array(
'MultiText', // new decorator
'Label',
'Errors',
'Description',
array('HtmlTag', array('tag' => 'dl',))
),
'label' => 'sconto',
'isArray' => true // must be true
));
$this->addElement($element);
Hope this helps