symfony2: multipage form - passing entities - php

stack: symfony2/doctrine2/php/mysql
a multipage form constist of two steps. each step is realized in a controller action.
in step1, the form is displayed. form-input is validated in the same action. if the form is valid the user should be redirected to the second step/action. in the second step the user has to confirm his input. after confirmation the data should be stored in the db.
thus form-entities/form-data are/is needed in the second step/action. however i do not want to store it in the db before confirmation.
do i really need to serialize all objects? to the session?
is there a better approach?
any suggestions?

First of all, I would recommend validating the input via JavaScript before posting and not in the controller action on the server.
If you don't want to serialize the data to the session you can simply pass it on to the next page when you receive it in the first action and then post it to the second action, I'm imagining something like this:
firstAction() {
$exampleData = $_POST['exampleData'];
// Do whatever you need, then pass the data on to the next page
return $this->render('SomeBundle:Views:secondPage.html.php',
array('exampleData' => $exampleData));
On the second page you then just have to access $exampleData with JavaScript and best put it in some hidden input field inside the form.
<!-- secondPage.html.php -->
<script type="text/javascript">
var exampleData = <?php echo $exampleData ?>;
$('#hiddenInput').val(exampleData);
</script>
The second controller action will then receive $exampleData as well without having it serialized in the session.
Sorry if there are any syntax errors, haven't used symfony2 in a while :)

tried to use serialization, but entities are quite complex with "many" associations. thus serialization is too slow. even after detaching.
first solution (simplified):
store the POST variables to the session inside the first step/action.
$postParams = $this->getRequest()->request;
$session = $this->getRequest()->getSession();
if (!$session) {
$session = new Session();
}
$session->set($sessionKey, $postParams);
in the second step/action i used the form to repopulate my entity.
$cancellation = $manager->initCancellationSomehow();
$session = $this->getRequest()->getSession();
if (!$session) {
$session = new Session();
}
$parameterBag = $session->get($sessionKey);
$cancellation = $this->getCancellation($customerId);
$form = $this->createForm(
new CancellationType(),
$cancellation,
array(
'em' => $this->getDoctrine()->getManager())
);
$form->bind($parameterBag->get('form'));
[..]
second solution:
well my first thought was to store cancellation in the db. therefore i added a state attribute (active/temp/..). unconfirmed cancellations get marked as temp. if the user confirms the state gets changed form temp to active. temp collections get deleted after on hour by a garbarge collector which runs at a low priority.
i like the second solution because the user has to confirm the final cancellation, which is already stored in the db. if the frontend does not work as expected the user will likely notice corrupted cancellations (e.g. wrong entries selected). if he confirms, only the state is changed. feels safe. in the first solution the user confirms what should be stored in the db, but isn't till now. feels unsecure.

Related

CakePHP 4 - setting request data

We have a CakePHP 3.x app which we've updated to the latest CakePHP 4.x. As part of this work we've also changed from PHP 7 to PHP 8.
Whilst testing the app we noticed a feature that had stopped working.
The app is a searchable database and is integrated with Redis for caching. One of the features means that the users search is retained between page reloads. This works by writing serialized form data to Redis, and then re-populating that back into the input fields in the template. This means the user sees the search criteria they entered; they do not need to re-enter their search criteria when the page is refreshed.
The code in the CakePHP 3.x app which re-populated the input form fields looked like this:
$form_fields = ['f1', 'f2', 'f3'];
The $form_fields array contains the names of the form input's in the template. As an example:
<input type="text" name="f1">
The next part of the code re-populates the form. In this case $user_search is an array of data that has been obtained and unserialized from Redis. As an example we might have $user_search['f1'] and $user_search['f3'] containing Redis data; f2 is unpopulated because the user didn't search using that field.
foreach ($form_fields as $form_field) {
$this->request->getData()[$form_field] = (isset($user_search[$form_field])) ? $user_search[$form_field] : '';
}
In the Cake 3.x app the above works fine. When the page is reloaded the form fields are set due to setting the request data, e.g. in the loop above, it evalulates to:
$this->request->getData()['f1'] = 'foo';
$this->request->getData()['f3'] = 'bar';
This means the request data has "foo" as f1 and "bar" as f3. There is nothing in f2 so it gets set to an empty string as per the : ''; condition.
In the CakePHP 4.x app this does not work; all form fields are unpopulated on page reload. I've confirmed that they are not being set to empty strings by modifying the : ''; condition mentioned above to : 'test'; and ensured the string "test" is not being shown in the fields.
The data exists in Redis and I've confirmed that $user_search contains what's shown above - in other words the data is not missing so we've ruled that out.
When I read over https://book.cakephp.org/4/en/controllers/request-response.html I can't see an example of setting request data. There is a method getData() which does what you'd expect - it reads the request data.
Is there a way to set the request data in Cake 4.x such that the above code would work?
In vanilla PHP what we're doing is equivalent to
$_POST['f1'] = 'foo';
$_POST['f2'] = ''; // empty string as no value set by user
$_POST['f3'] = 'bar';
AFAIK this was - and still is - valid in PHP; you can set/overwrite request data with anything in your code. If this is wrong please advise what I should be doing instead.
For clarity the reason we are setting request data in this manner is because the search for works via an ajax call. When the user enters their search criteria initially, the page has not been reloaded so the form fields appear to be populated correctly. This issue occurs on page reload. In that instance we want to repopulate the form with the values they entered prior to the page being reloaded.
The function you're looking for is withData. Remember that the request object is immutable, so you need to assign the result of that function call back into the request object, e.g. with $this->setRequest($this->getRequest()->withData('f1', 'foo')->withData('f3', 'bar')).

Joomla 3.x, how to keep user data if validation fail

i have a problem when coding my new Joomla component.
I have some input form in my custom joomla component (/components\com_custom\views\myview\tmpl\default.php )
<input type="text" class="form-control" name="jform[checkindate]" id="checkindate" value="<?php if (isset($post)) {echo $post; } else { echo 'Checkin date'; }?>">
Of courses, at the top i have this:
$jin = JFactory::getApplication()->input;
$post = $jin->get('checkindate', 'Checkin date', 'STR');
My point is to keep user post data if they faile in validation so they don't need to retype all the form, they only need to fix some error data before they can submit it again, but there is no success.
Could you please help me on this?
Thank you so much!
I'm assuming you are using Joomla jform for building forms
In jform, certain things need to take care if you want to maintain the form session.
Store the submitted form data in session - Before redirecting the user to form view after validation. you need to store the user data in user state. This is generally done in the controller file. This done by following way
$app = JFactory::getApplication();
$app->setUserState('componentname.formname.data', $data);
Please note that the first parameter is the key for your data which is stored in the user state. the second parameter is the Jinput data post by the user.
Load form data from the session if available - Till now we have saved the data in the session now we need to check whether form data available in session or not. This Is done in the load form data method written in the model.
`
protected function loadFormData()
{
$data = JFactory::getApplication()->getUserState('componentname.formname.data', array());
if (empty($data))
{
// This is the my component function to load form data
$data = $this->getData();
}
return $data;
}
3 . If the form data saved successfully then don't forgot to clear it from the session.
$app->setUserState('componentname.formname.data', null);

Laravel store session in cookie

I have a website where the front page contains a search form with several fields.
When the user performs a search, I make an ajax call to a function in a controller.
Basically, when the user clicks on the submit button, I send an ajax call via post to:
Route::post('/search', 'SearchController#general');
Then, in the SearchController class, in the function general, I store the values received in a session variable which is an object:
Session::get("search")->language = Input::get("language");
Session::get("search")->category = Input::get("category");
//I'm using examples, not the real variables names
After updating the session variable, in fact, right after the code snippet shown above, I create (or override) a cookie storing the session values:
Cookie::queue("mysite_search", json_encode(Session::get("search")));
And after that operation, I perform the search query and send the results, etc.
All that work fine, but I'm not getting back the values in the cookie. Let me explain myself.
As soon as the front page of my website is opened, I perform an action like this:
if (!Session::has("search")) {
//check for a cookie
$search = Cookie::get('mysite_search');
if($search) Session::put("search", json_decode($search));
else {
$search = new stdClass();
$search->language = "any";
$search->category = "any";
Session::put("search", $search);
}
}
That seems to be always failing if($search) is always returning false, and as a result, my session variable search has always its properties language and category populated with the value any. (Again: I'm using examples, not the real variables names).
So, I would like to know what is happening here and how I could achieve what I'm intending to do.
I tried to put Session::put("search", json_decode($search)); right after $search = Cookie::get('mysite_search'); removing all the if else block, and that throws an error (the ajax call returns an error) so the whole thing is failling at some point, when storing the object in the cookie or when retieving it.
Or could also be something else. I don't know. That's why I'm here. Thanks for reading such a long question.
Ok. This is what was going on.
The problem was this:
Cookie::queue("mysite_search", json_encode(Session::get("search")));
Before having it that way I had this:
Cookie::forever("mysite_search", json_encode(Session::get("search")));
But for some reason, that approach with forever wasn't creating any cookie, so I swichted to queue (this is Laravel 4.2). But queue needs a third parameter with the expiration time. So, what was really going on is that the cookie was being deleted after closing the browser (I also have the session.php in app/config folder set to 'lifetime' => 0 and 'expire_on_close' => true which is exactly what I want).
In simple words, I set the expiration time to forever (5 years) this way:
Cookie::queue("mysite_search", json_encode(Session::get("search")), 2592000);
And now it seems to be working fine after testing it.

how to reinitialize Zend_Form_Element_Hash?

On my web page I have Zend_Form with CSRF hash. It submits to the same page and it is used to update user's data (name, surname, birthdate and so on...). I would like to reinitialize CSRF hash after every valid post submission. How I could do it?
Now when I first time submit 'user data' form I get proper behavior (user data gets updated). But if I submit again (right after first, proper submit) to change another form field I get error saying:
The two given tokens do not match
Is there anyway to reinitialize hash properly?
Your problem comes from this function in Zend_Form_Element_Hash the $session->setExpirationHops is set to 1 hop, so if you try to resubmit the same form it will always fail. This is by design and is the root of the security you're seeking.
However you can change this behavior, simply extend Zend_Form_Element_Hash and override this method to set the expirationHops to a value you prefer (you can also set the session options manually at any time if you prefer).
public function initCsrfToken()
{
$session = $this->getSession();
$session->setExpirationHops(1, null, true);
$session->setExpirationSeconds($this->getTimeout());
$session->hash = $this->getHash();
}
It goes without saying that it would be in the interest of security to perform a full refresh and repopulate the form to perform any edits. This should reset the hash.
My problem came from tampering with Zend_Form_Element_Hash init methods, I had this:
$_csrf = new Zend_Form_Element_Hash($this->_csrfName);
$_csrf->setSalt(md5($name));
$_csrf->setAttrib('id', '');
$_csrf->initCsrfToken();
The last line should never be there. When I removed it, everything started to act correctly.
#RockyFord: The initCsrfToken() function was place I was looking at the beginning, but
for me (I mean my intuition) it just couldn't be that. So I just debugged, debugged, debugged,
and at last commenting out 4th line was proper solution. Sorry for holding off reply, I totally forgot.

Drupal 6 Validation for Form Callback Function

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.

Categories