How to submit a form in Zend? - php

I have following action to display a form
public function showformAction() {
$this->view->form = new Form_MyForm();
$this->view->form->setAction( 'submitform' );
}
above action shows a form successfully with only one textarea and submit button.
And I am using following action to submit above form:
public function submitformAction() {
$form = new Form_MyForm();
$request = $this->getRequest();
if ( $request->isPost() ) {
$values = $form->getValues();
print_r($values);die();
} else {
echo 'Invalid Form';
}
}
Above action is showing following output:
Array ( [myfield] => )
It means it is not posting values correctly and always shows empty array or I am not getting posted values correctly. How to post values to submitformAction().
Thanks

I think you must use the isValid() before accessing the values of a submitted form, because it's right there that the values are checked and valorized
public function submitformAction() {
$form = new Form_MyForm();
$request = $this->getRequest();
if ( $request->isPost() ) {
if ($form->isValid( $request->getPost() )) {
$values = $form->getValues();
print_r($values);die();
}
} else {
echo 'Invalid Form';
}
}

In complement to #VAShhh response. With some more details:
You need to do two things, populate your form fields with the POSTed data and applying security filters and validators to that data. Zend_Form provides one simple function which perform both, it's isValid($data).
So you should:
build your form
test you are in a POST request
populate & filter & validate this data
either handle the fact in can be invalid and re-show the form wich is now
decorated with Errors OR retrieve valid data from the form
So you should get:
function submitformAction() {
$form = new Form_MyForm();
$request = $this->getRequest();
if ( $request->isPost() ) {
if (!$form->isValid($request->getPost())) {
$this->view->form = $form;
// here maybe you could connect to the same view script as your first action
// another solution is to use only one action for showform & submitform actions
// and detect the fact it's not a post to do the showform part
} else {
// values are secure if filters are on each form element
// and they are valid if all validators are set
$securizedvalues = $form->getValues();
// temporary debug
print_r($securizedvalues);die();
// here the nice thing to do at the end, after the job is quite
// certainly a REDIRECT with a code 303 (Redirect after POSt)
$redirector = $this->_helper->getHelper('Redirector');
$redirector->setCode(303)
->setExit(true)
->setGotoSimple('newaction','acontroller','amodule');
$redirector->redirectAndExit();
} else {
throw new Zend_Exception('Invalid Method');
}
}
And as said in the code re-showing the form you shoudl really try to use the same function for both showing and handling POST as a lot of steps are really the same:
building the form
showing it in the view in case of errors
By detecting the request is a POST you can detect you are in the POST handling case.

Related

Form doesn't validate after submitting in moodle

I am trying to create a form whose structure depends on the parameter in the url. If no parameter is specified in the url, an error message should be displayed. Depending on the id, a database query is performed and the form is filled with data.
example url: http://127.0.0.1/local/group/signin.php?groupid=14
Unfortunately, my form doesn't validate after I submit the form via pressing the action button. It jumps to http://127.0.0.1/local/group/signin.php and because there is no parameter present in the url the error message 'No group found' is displayed.
What have I done wrong here?
signinform.php:
class signinform extends moodleform {
public function definition() {
global $DB;
global $USER;
$mform = $this->_form;
$urlid = $this->_customdata['id']; // get the passed group id
$message = 'No group found';
if(is_null($urlid)){
$mform->addElement('html', '<h3>'.\core\notification::error($message).'</h3>');
}
else{
// build the form, sql query etc.
$this->add_action_buttons(true, 'Submit');
}
}
function validation($data, $files) {
return array();
}
}
signin.php:
$PAGE->set_url(new moodle_url('/local/schedule/signin.php?'));
$PAGE->set_context(\context_system::instance());
$PAGE->set_pagelayout('base');
$PAGE->set_title("Sign up");
$PAGE->set_heading("Sign up for a group");
global $DB;
global $USER;
$urlid = $_GET["id"];
$to_form = array('id' => $urlid); // pass group id to form
$mform = new signinform(null, $to_form);
$homeurl = new moodle_url('/');
if ($mform->is_cancelled()) {
redirect($homeurl, 'Cancelled.'); // Just for testing, never enters here
} else if ($fromform = $mform->get_data()) {
redirect($homeurl, 'Validation in process'); // Just for testing, never enters here
}
echo $OUTPUT->header();
$mform->display();
echo $OUTPUT->footer();
You need to add a hidden field to the form that contains the 'id' that has to be passed to the page, otherwise, when the form is submitted, that id will no longer be present in the params for that page.
e.g. (in definition())
$mform->addElement('hidden', 'id', $urlid);
$mform->setType('id', PARAM_INT);
Also, in Moodle, you should never access $_GET directly - use the wrapper functions required_param() or optional_param(), as these:
Clean the parameter to the declared type
Automatically take parameters from either $_GET or $_POST (which will be important in this case, as the 'id' param will be part of the POST data, when you submit the form)
Handle missing parameters by either applying a default (optional_param) or stopping with an error message (required_param)
So your access to $_GET['id'], should be replaced with:
$urlid = optional_param('id', null, PARAM_INT);

Symfony 2 - Adding error to form element on preSubmit event subscriber

I have a preSubmit event in an event subscriber for a form, and for a specific case I want to add an error to a form field. My method within the subscriber is as follows:
public function onPreSubmit(FormEvent $event)
{
$sourceData = $event->getData();
$form = $event->getForm();
$identifier = &$sourceData['identifier'];
if ($identifier) {
if ($this->identifierIsUrl($identifier)) {
$parser = $this->getIdParser();
$identifier = $parser->getIdentifier($identifier);
if (is_null($identifier)) {
$form->get('identifier')->addError(new FormError('You have either entered an incorrect url for the source or it could not be parsed'));
}
}
$event->setData($sourceData);
}
}
However when I print the form error in the view, it is empty. Is it possible to do this in a preSubmit event? Am I looking at this the wrong way?
The issue is related to the Symfony\Component\Form\Form::submit method that removes all of the form field specific errors assigned after the PRE_SUBMIT event.
During Form::submit it iterates over all the forms child objects (which are also themselves Form objects as noted by the other answers) and calls their submit methods individually. Resulting in the form element errors that were added during the parent's PRE_SUBMIT event to be reset to an empty array.
This is why you can use $form->addError() in the parent PRE_SUBMIT event or set the form element to error_bubbling => true and it will display as the parent form errors, but not to a specific form element.
Here's the example of what occurs without looking through the entire codebase for Symfony Forms.
class Form
{
public function addError($error)
{
if($this->parent && $this->config->getErrorBubbling()) {
$this->parent->addError($error); //element had error_bubbling => true, attach the error to the parent.
} else {
$this->errors[] = $error; //add it to the current object's errors array
}
}
public function submit()
{
$this->errors = array(); //resets the errors of the current object
$this->preSubmitEvent();
foreach($this->children as $child) {
$child->submit(); //errors in child object are reset
}
}
}
So it results in
Form:
submitMethod:
preSubmitEvent
children:
submitMethod:
preSubmitEvent
To get around the issue you can add a PRE_SUBMIT event directly to your form element to validate that element and add errors to it.
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->add('identifier', Form\TextType::class);
//...
$builder->get('identifier')->addEventListener(FormEvents::PRE_SUBMIT, [$this, 'validateIdentifier']);
}
Then alter your onPreSubmit method accordingly.
public function validateIdentifier(FormEvent $event)
{
$identifier = $event->getData();
$element = $event->getForm();
if ($identifier) {
if ($this->identifierIsUrl($identifier)) {
$parser = $this->getIdParser();
$identifier = $parser->getIdentifier($identifier);
if (null === $identifier) {
$element->addError(new FormError('You have either entered an incorrect url for the source or it could not be parsed'));
}
}
$event->setData($identifier);
}
}
It should be possible if you look at the Form::submit(), you'll see that the errors are reset before the PRE_SUBMIT data is dispatched. Also Validation Listener doesn't reset the errors on the form, and the Violation Mapper only adds Errors to the form. And AFIK there aren't any listeners that reset the form's errors. So maybe you're doing something else wrong.
It is possible that errors are just not displayed in your layout.
To debug, make sure your form is actually made invalid by your custom check (if you show your controller I may be able to help with actual code).
If that is in fact your issue, you may want to add error_bubbling => true to your field definition.

Why is Zend's FlashMessenger getMessages method not working?

I am using Zend 1.12 and php 5.4.3 and flashMessenger->getMessages() has suddenly stopped working in a controller action.
In AController, a certain type of account is created and it takes 9 steps to create it, so I have 9 actions create1-9Action
On each step I pass the form data to the next step using flashmessenger.
This is the typical structure of an action:
public function create5Action()
{
$form = new My_Form();
$messages = $this->_helper->flashMessenger->getMessages();
$data = $messages[0];
if ($this->_request->isPost())
{
if ($form->isValid($this->_request->getPost()))
{
/* form treatment */
$this->_helper->flashMessenger->addMessage($data);
$this->_redirect($this->_helper->url("create6", "A", null)); // redirect to next step
}
}
$this->_helper->flashMessenger->addMessage($data);
$this->view->form = $form;
}
In this action (create5) the data is intact when arriving from create4Action, it is intact when adding it as a message before $this->view->form = $form;, but when I add new elements to the form and submit it, $messages = $this->_helper->flashMessenger->getMessages(); is null and I do not know why, since it is working for all other actions.
You may have missed adding the else in the isPost() loop.
public function create5Action()
{
$form = new My_Form();
$messages = $this->_helper->flashMessenger->getMessages();
$data = $messages[0];
if ($this->_request->isPost())
{
if ($form->isValid($this->_request->getPost()))
{
/* form treatment */
$this->_helper->flashMessenger->addMessage($data);
$this->_redirect($this->_helper->url("create6", "A", null)); // redirect to next step
}
} else {
$this->_helper->flashMessenger->addMessage($data);
$this->view->form = $form;
}
}
However this is not the anticipated use of the flash messenger. I think a part of your problem is that every time a request is made the flash messenger namespace is cleared during the postDispatch() portion of the dispatch loop.
You may be better off using your own Zend_Session_Namespace instance instead of relying on the instance that flash messenger is using.
public function create5Action()
{
$session = new Zend_Session_Namespace('data');//set elsewhere and forwarded and can be persistent
$form = new My_Form();
$data = $session->data;
if ($this->_request->isPost())
{
if ($form->isValid($this->_request->getPost()))
{
/* form treatment */
$session->newData = $newData;//forward data if needed, old data will persist as you require
$this->_redirect($this->_helper->url("create6", "A", null)); // redirect to next step
}
} else {
$this->view->form = $form;
}
}

Validating input created by Javascript with Zend_Form

I creating a text box dynamically (based on user selection)using jquery ..is there any way to provide validation for that text box from zend form..?
Yes there is.
most credits go to jeremy kendall see http://www.jeremykendall.net/2009/01/19/dynamically-adding-elements-to-zend-form/
The way i solved it is by doing a jquery/ajax call to a action which adds get a form element something like:
$.ajax({
type : "POST",
url : "/<controller>/addfield/",
success : function(newElements) {
// Insert new element before the submit button
$("#productsNew-submit-element").before(newElements);
}
});
What this does is it call a action that generates a form element and returns the html which you can then add
public function addfieldAction()
{
//use $ajaxContext = $this->_helper->getHelper('AjaxContext'); in the init to make it return html via ajax
$element = new Zend_Form_Element_Text("extraElement_1");
$element->setBelongsTo("yourForm");
$element->setLabel('myElementName');
/*
set other stuff like decorators or so
*/
//now create the html
$elements .= $element->__toString();
$this->view->fields = $elements;
}
After that you will get a new element in your form via ajax
Now when you submit you have to do that again but then pre validation
first check if your form has extraElements if so add them again
fill the add element with the posted value
validate
public function saveAction()
{
function findFields($field) {
// return field names that include 'extraElement_'
if (strpos($field, 'extraElement_') !== false) {
return $field;
}
}
//set all stuff you need especially the form
if($this->getRequest()->isPost()) {
$postValues = $this->getRequest()->getPost();
//step 1
$extraFields = array_filter(array_keys(current($postValues)), 'findFields');
//add the element before validation
if(count($extraFields) !== 0) {
foreach(extraFields as $extraField) {
$this->addFields($postValues[$extraField]); <-- step 2 add the field(s)
}
}
//step 3 validate
if($this->_form->isValid($postValues)) {
//do post validation stuff
} else {
//show errors
}
}
}

Zend_Form: Element should only be required if a checkbox is checked

I've got a Form where the user can check a checkbox "create new address" and can then fill out the fields for this new address in the same form.
Now I want to validate the fields of this new address ONLY if the checkbox has been checked. Otherwise, they should be ignored.
How can I do that using Zend_Form with Zend_Validate?
Thanks!
I think that the best, and more correct way to do this is creating a custom validator.
You can do this validator in two different ways, one is using the second parameter passed to the method isValid, $context, that is the current form being validated, or, inject the Checkbox element, that need to be checked for validation to occur, in the constructor. I prefer the last:
<?php
class RequiredIfCheckboxIsChecked extends Zend_Validate_Abstract {
const REQUIRED = 'required';
protected $element;
protected $_messageTemplates = array(
self::REQUIRED => 'Element required'
);
public function __construct( Zend_Form_Element_Checkbox $element )
{
$this->element = $element;
}
public function isValid( $value )
{
$this->_setValue( $value );
if( $this->element->isChecked() && $value === '' ) {
$this->_error( self::REQUIRED );
return false;
}
return true;
}
}
Usage:
class MyForm extends Zend_Form {
public function init()
{
//...
$checkElement = new Zend_Form_Element_Checkbox( 'checkbox' );
$checkElement->setRequired();
$dependentElement = new Zend_Form_Element_Text( 'text' );
$dependentElement->setAllowEmpty( false )
->addValidator( new RequiredIfCheckboxIsChecked( $checkElement ) );
//...
}
}
I have not tested the code, but I think it should work.
I didn't actually run this, but it should work within reason. I've done something similar before that worked, but couldn't remember where the code was.
<?php
class My_Form extends Zend_Form
{
public function init()
{
$checkbox = new Zend_Form_Element_Checkbox("checkbox");
$checkbox->setValue("checked");
$textField = new Zend_Form_Element_Text("text");
$this->addElements(array("checkbox", "text"));
$checkbox = $this->getElement("checkbox");
if ($checkbox->isChecked() )
{
//get textfield
$textField = $this->getElement("text");
//make fields required and add validations to it.
$textField->setRequired(true);
}
}
}
Here's what I do if I need to validate multiple elements against each other
$f = new Zend_Form();
if($_POST && $f->isValid($_POST)) {
if($f->checkbox->isChecked() && strlen($f->getValue('element')) === 0) {
$f->element->addError('Checkbox checked, but element empty');
$f->markAsError();
}
if(!$f->isErrors()) {
// process
...
...
}
}
I've been wondering how to do that in ZF as well, though never had to implement such form feature.
One idea that comes to mind is to create a custom validator that accepts the checkbox field as a parameter, and run it in a validator chain, as documented. If the checkbox is checked, validator could return failure. Then you can check whether all validations failed and only then treat form as having failed validation.
That level of customization of form validation could be inconvenient, so maybe using form's isValidPartial method would be better.
I created a custom validator that will make your element required based on the value of another zend form element.
Here's the full code. I hope this helps someone.
The idea is to create a custom validator and pass in the name of the conditional element and the value of that conditional element into the constructor. Zend_Validor's isValid method already has access to the value of the element you are attaching the validtor to along with all the form element names and values.
So, inside the isValid method you have all the information you need to determine if your form element should be a required element.
On Zend 1 extend the isValid method, where you set required depending on posted data for example:
public function isValid($data)
{
if (!empty($data['companyCar'])) {
$this->getElement('carValue')->setRequired(true);
}
return parent::isValid($data);
}
Thank you JCM for your good solution.
However 2 things I've noticed:
The isValid method of your validator has to return true in case of success.
The validator will not be executed during form validation without pass the allowEmpty option to false to the text field.

Categories