On my website, I have user accounts that are configurable with forms that allow users to update everything from first and last names to privacy settings. I use the following function to update the database with that input. (Note that the following code uses WordPress-specific features.)
function update_account() {
global $current_user; get_currentuserinfo();
require_once( ABSPATH . WPINC . '/registration.php' );
$uid = $current_user->ID;
// First Name
if(isset($_POST['first_name']) && $_POST['first_name'] <> $current_user->first_name) {
wp_update_user( array(
'ID' => $uid, 'first_name' => esc_attr($_POST['first_name'])
));
}
// ...and so on 43 more times...
}
This feels like the wrong way to process forms. This also looks like it will negatively impact server performance when there are multiple users and frequent updates, given that the if-then-else conditions for every field, even fields not on a particular page, force checking each field for input.
Moreover, since form data can be expected to remain relatively constant, I added the <> operator to prevent the function from updating fields where there has not been any change, but I suspect this also means that every field is still evaluated for change. To make matters worse, adding new fields -- there are already 44 fields in total -- is an unwieldy process.
What's a better way to process form data?
Keep an array of the fields you will be processing with this code, and iterate over it. This works if all your attributes are strings, for example. If you have different data types such as boolean flags to handle differently from the strings, you may wish to group them into their own array.
// All the fields you wish to process are in this array
$fields = array('first_name', 'last_name', 'others',...'others99');
// Loop over the array and process each field with the same block
foreach ($fields as $field) {
if(isset($_POST[$field]) && $_POST[$field] != $current_user->{$field}) {
wp_update_user( array(
'ID' => $uid, $field => esc_attr($_POST[$field])
));
}
}
There's a lot of things missing with your implementation. I don't know what kinds of data you're allowing the user to manipulate but most usually have some kind of requirements to be acceptable. Like not having certain characters, not being blank, etc. I don't see any validation occurring, so how do you handle values that might be undesirable? And what happens when you receive bad data? How do you inform the user of the bad data and prompt them to correct it?
If we abstract the situation a bit we can come up with generalizations and implement an appropriate solution.
Basically form fields [can] have a default value, a user specified value [on form review], validation requirements and validation errors [with messages]. A form is a collection of fields that upon form submit needs to be validated and if invalid, re-displayed to the user with instructive corrective prompts.
If we create a form class that encapsulates the above logic we can instantiate and use it to pass around our controller/views. Oops, I was just assuming you were using an Model/View/Controller type framework, and I'm not really familiar with wordPress so I don't know if that is exactly applicable. But the principle still applies. On the page where you both display or process the form, here's some pseudo logic how how it might look.
function update_account()
{
// initialize a new form class
$form = new UserAccountInfoForm();
// give the form to your view for rendering
$this->view->form = $form;
// check if form was posted [however your framework provides this check]
if(!Is_Post())
return $this->render('accountform.phtml');
// check if posted form data validates
if(!$form->isValid($_POST))
{
// if the form didn't validate re-display the form
// the view takes care of displaying errors, with the help of its
// copy of the $form object
return $this->render('accountform.phtml');
}
// form validated, so we can use the supplied values and update the db
$values = $form->getValues(); // returns an array of ['fieldname'=>'value']
// escape the values of the array
EscapeArrayValues($values);
// update db
wp_update_user($values);
// inform the user of successful update via flash message
$this->flashMessage('Successfully updated profile');
// go back to main profile page
$this->redirect('/profile');
That makes your controller relatively clean an easy to work with. The view gets some love and care to, utilizing the $form value to display the form correctly. Technically, you can implement a method in the form class to give you the form html, but for simplicity I'm just going to assume your form html is manually coded in accountform.phtml and it just uses $form to get field info
<form action='post'>
<label>first name</label> <input class='<?=$this->form->getElement('first_name')->hasError() ? "invalid":""?>' type='text' name='first_name' value="<?=$this->form->getElement('first_name')->getValue()"/> <span class='errmsg'><?=$this->form->getElement('first_name')->getError()?></span><br/>
<label>last name</label> <input class='<?=$this->form->getElement('last_name')->hasError() ? "invalid":""?>' type='text' name='last_name' value="<?=$this->form->getElement('last_name')->getValue()"/> <span class='errmsg'><?=$this->form->getElement('last_name')->getError()?></span><br/>
<label>other</label> <input class='<?=$this->form->getElement('other')->hasError() ? "invalid":""?>' type='text' name='other' value="<?=$this->form->getElement('other')->getValue()"/> <span class='errmsg'><?=$this->form->getElement('other')->getError()?></span><br/>
<input type='submit' value='submit'/>
</form>
Here the pseudo code relies on the form class method "getElement" which returns the field class instance for the specified field name (which would be created an initialized in the constructor of your form class). Then on the field class methods "hasError" and "getError" to check if the field validated correctly. If the form has not be submitted yet, then these return false and blank, but if the form was posted and invalid, then they will have been set appropriately in the validate method when it was called. Also "getValue" would return either the value supplied by the user when the form was submitted, or if the form has not been submitted, the default value as specified when the field class was instantiated and initialized.
Obviously this pseudo code is relying on a lot of magic that you'd have to implement if you roll your own solution--and it's certainly doable. However, at this point I'll direct you to the Zend Framework Zend_Form components. You can use zend framework components by themselves without having to utilize the entire framework and application structure too. You might also find similar form component solutions from other frameworks but I wouldn't know about those (we are a Zend Framework shop at my work place).
Hopefully this hasn't been too complicated, and you know where to go from here. Of course just ask if you need any clarification.
Related
Going to try my best to explain this...
In my app I have the need to display the validation errors on a form when the user loads the form initially. In short, they've entered data and saved it, but now the data has been checked and we've detect errors they need to correct before they can fully submit the form. (It's multi step form that can be filled out over multiple sessions...think big.)
Previously I was doing something like:
THIS DOESN'T WORK IN SYMFONY >=2.8.10 See this answer
$entity // passed in as param on action method
$form = $this->createForm(..., $entity);
$csrfToken = 'random_string'; // retrieved from FormConfigInterface
// perform the submit but don't clear missing
$form->submit(['_token' => $csrfToken], false);
This was working, but seems to have broken in Symfony 2.8.10+, but works in 2.8.9. In 2.8.10+ there are no validation errors (the form is considered valid).
I'm able to retrieve the validation errors in a ConstraintViolationListInterface, but I can't find a way to "merge" that with the form (which I think would be the prefered way). Since I couldn't, I tried the above which "fake" submits the form...which seems like a bad idea.
Is there a better/proper way?
(Note: the form is much more complicated and includes validation groups...but I'm not concerned about that or the error in 2.8.10+ at this point.)
I have a custom behavior, specified in AppModel.php, that automatically creates a field based on the selected language. Thus, depending on the chosen language, name_eng -> name or name_fra -> name.
...
$virtualField = sprintf($model->name.'.'.$name.'_%s', Configure::read('Config.language'));
$virtualFields[$name] = $virtualField;
$model->virtualFields = $virtualFields;
...
This part works.
The issue arises when I submit the edit form, get a validation error and the field isn't available when the edit view is displayed with error prompts. I believe this is due to either my behavior not being called in this process or $this->request->data being created using form data?
I figured that I would initialize the values using beforeValidate. However, it's not working out: the field still doesn't exist once I've submitted the form which gives me this error:
In AppModel.php:
function beforeValidate(array $options = array()){
//hard coded for test purposes
$this->data['CertificateType']['name'] = $this->data['CertificateType']['name_'.Configure::read('Config.language')]
return true;
}
In the view (edit.ctp):
echo $this->request->data['CertificateType']['name'];
Essentially, how can I replicate the functionality of my custom behavior and initialize my field after a form has been submitted but doesn't validate?
The needed logic was eventually put in AppController.php. Everything works fine now.
I am making a basic signup page using PHP Codeigniter.
On the signup page, I ask the user to select from a selection of categories (via a <select> html element). These categories are stored in a list in my MySQL Database.
My current method is to fetch this list from the DB when the user calls the function to load the page and then display it to them. However, if the user enter's incorrect data and the page must be reloaded with the validation errors, the variable that holds the data in the list seems to be cleared, and I must refetch the list data from the database before redisplaying the page.
I believe there's something in the documentation about how to set this variable to be permanently available but upon looking again, I had no luck in finding it.
Could anyone possibly point me in the right direction? It seems silly to me to have to need to refetch this data every time (I know that people won't be putting in wrong info often, but this will come in handy in a few situations).
NOTE: This is not an issue regarding remembering user selections.
this example is for a select drop down list using ci form helper
(i'm modifying this from another form so hopefully its all correct)
the Array of select values is: $categoryarray
the drop down field name is 'category'
the default value is $defaultcategory
a css class to style (bootstrap etc): $dropclass = 'class="input-medium"';
the line of code is:
form_dropdown('category',$categoryarray,set_value('category',$defaultcategory),$dropclass).form_error('category');
the form_error('category'); at the end is for showing a validation error message
and even though there is a default value - if the form fails validation from another field in the form - this will 'remember' what the user selected.
EDIT !
ok there is good and bad news.
bad news - if the categories are coming from a database then you need to get them again.
good news - CI remembers what category the user selected from the drop down list.
and the bad news actually isn't that big a deal - if you create the category array in your model. then its just one line of code to add to the validation.
// In the Model
function makedropdown() {
// get your category list
$cats = $this->getAllCategories() ;
$categoryarray = array();
// make the array
foreach ( $cats->result() as $row ) {
$categoryarray[$row->category] = $row->category ; }
return $categoryarray ;
}
someone has filled out the form, we run validation, validation fails. in the controller:
if ( $this->form_validation->run($formrules) == FALSE ) {
// get the categoryarray
$data['categoryarray'] = $this->categorymodel->makedropdown() ;
// load form again
$this->load->view( 'myform', $data ); }
So even though we are getting the categories again from the db to dynamically populate the select list - CI still remembers the users choice from the first time they did the form.
And what about a default category for the drop down? if its not going to change then it can be set as a config. if the category values are coming from a database and they can change - then the default category could be created in the model.
EDIT 2
gosh i always do this anyway so why didnt i think of it for this. so yeah this is yet another reason to make a specific method for showing your view
function _showCategoryForm(){
// get the categoryarray
$data['categoryarray'] = $this->categorymodel->makedropdown() ;
// anything else thats needed for the view
// load form view
$this->load->view( 'myform', $data ); }
NOW we dont have any repeated code, and its easy to customize the validation failure with an error message if needed.
// since i'm grinding on this - the validation should happen in a model
// and that method returns true or false
if ( $this->somemodel->validateCategoryForm() == FALSE ) {
// custom obnoxious error message
$this->formerrormessage = "What part of required is eluding you?" ;
$this->_showCategoryForm() ; }
This is much better because if the needs of your form changes - the change is only in one place. Also i added an underscore to remind us all that private methods are a good practice. And the form validation should be separate in a model, that is called by the controller.
You just need to set it as the default value, for example
<input type="text" name="username" value="<?php isset($_POST['username']) echo $username;?>" />
That way, $_POST['username'] will always be available.
I am using Symfony 1.3.2 with Propel ORM on Ubuntu 9.10.
I have developed a form that dynamically populates a select widget with cities in a selected country, using AJAX.
Before the data entered on the form is saved, I validate the form. If validation fails, the form is presented back to the user for correction. However, because the country list is dynamically generated, the form that is presented for correction does not have a valid city selected (it is empty, since the country widget has not changed yet).
This is inconvenient for the user, because it means they have to select ANOTHER country (so the change event is fired), and then change back to the original country they selected, then FINALLY select the city which they had last selected.
All of this is forced on the user because another (possibly unrelated) field did not vaildate.
I tried $form->getValue('widget_name'), called immediately after $form->bind(), but it seems (infact, IIRC, if form fails to validate, all the values are reset to null) - so that does not work.
I am currently trying a nasty hack which involves the use of directly accesing the input (i.e. tainted) data via $_POST, and setting them into a flash variable - but I feel its a very nasty hack)
What I'm trying to do is a common use case scenario - is there a better way to do this, than hacking around with $_POST etc?
What I do for this exact issue is that I post the form to the same action that generated it, and in that action, I grab any selected countries/regions/cities as POST variables and pass them back to the template (regardless of validation). In the template, I then use JQuery to set the select values to what they were. When validation passes, they get used. When not, they get passed back to template.
If you can tolerate a little PHP in your JQuery, you could do this in the template:
$(document).ready(function()
{
$("#country-select").val('<?php echo $posted_country; ?>');
});
If you use this approach, don't forget to initialise $this->posted_country in your template the first time around or Jquery will get confused.
I guess you could also use $this->form->setWidget(...)->setDefault(...) or something similar, but I havent found a way around using $_POST as accessing the elements seems to need binding the form otherwise.
UPDATED CODE IN RESPONSE TO COMMENTS BELOW:
if($_POST['profile']['country_id'] != '')
{
$this->posted_country = $_POST['profile']['country_id'];
$q = Doctrine_Query::create()
->select('c.city_id, c.city_name')
->from('City c')
->where('c.country_id = ?', $this->posted_country);
$cities = $q->execute(array(), Doctrine_Core::HYDRATE_NONE);
foreach($cities as $city) $list[$city[0]] = $city[1];
$this->form->setWidget('city_id', new sfWidgetFormChoice(array('choices' => array('' => 'Please select') + $list)));
}
So... I get the country from the post, I query db with that, get cities, and craft cities back into a dropdown. Then in the template, you can set a default selected city with something like $this->posted_city (which would be a POST variable too, if exists).
I have a simple form with a select menu on the node display page. Is there an easy way to validate the form in my callback function? By validation I don't mean anything advanced, just to check that the values actually existed in the form array. For example, without ajax, if my select menu has 3 items and I add a 4th item and try to submit the form, drupal will give an error saying something similar to "an illegal choice was made, please contact the admin."
With ajax this 4th item you created would get saved into the database. So do I have to write validation like
if ($select_item > 0 && $select_item <= 3) {
//insert into db
}
Or is there an easier way that will check that the item actually existed in the form array? I'm hoping there is since without ajax, drupal will not submit the form if it was manipulated. Thanks.
EDIT:
So I basically need this in my callback function?
$form_state = array('storage' => NULL, 'submitted' => FALSE);
$form_build_id = $_POST['form_build_id'];
$form = form_get_cache($form_build_id, $form_state);
$args = $form['#parameters'];
$form_id = array_shift($args);
$form_state['post'] = $form['#post'] = $_POST;
$form['#programmed'] = $form['#redirect'] = FALSE;
drupal_process_form($form_id, $form, $form_state);
To get $_POST['form_build_id'], I sent it as a data param, is that right? Where I use form_get_cache, looks like there is no data. Kind of lost now.
Since you're already using AJAX, why not just write a bit of jQuery to only allow form submission if the choice is within the list of legal choices? This can be done within the custom module it already looks like you're working on (using drupal_add_js()).
It is not especially 'easy', but the standard way to do it would be to use Drupals Forms API for the callback submission as well - that way, you'll get the same validation that would happen on a non js submit.
Take a look at Adding dynamic form elements using AHAH. While it does not match your scenario exactly (they rebuild the form on the callback to add new elements, not to save data), the explanation of the processing workflow is pretty helpful.
Then there are several modules that try to offer AJAX form submission in a generic way - you could check their code on how to do it (or maybe just use them ;)
Ajax submit (only has a dev version)
Ajax (has an 'official' release)
Finally, there are efforts to put better support this functionality into core in Drupal 7 - the related discussions might also help.