ZF2 how to populate a form that failed a POST request into a GET redirect - zend-framework

I'm following REST standard where you use a POST action to create a resource and GET to show data.
That includes using GET to show a creation form and POST to handle the actual creation of the resource (AKA, saving to database).
In the case the POST request fails (lets say, a duplicate email address), a 302 is returned as a response, redirecting the user back to the form (kind of as a GET /resource/create with 302).
How do I persist the data sent from POST after the redirection in ZF2?
Or, maybe ZF2 doesn't support this/we're not supposed to do a 302 redirection?

I just think you want to pass data back to form. And display data on each fields. Usually, for failed request you don't need make redirection. Just display the form and data. Just use redirect when the process success (saving to database).
$form = new Form(); // your form
$request = $this->getRequest();
if ($request->isPost()) {
$form->setData($request->getPost());
if ($form->isValid()) {
// saving data here then redirect
$this->redirect()->toRoute('route', array('action' => 'name'), array('param => 1'));
}
}
return array('form' => $form);

Related

TYPO3: How can I allow third party query string parameters?

I have created a multistep order form extension which collects some user data and then redirect to different third party solutions for the payment step.
This third party solutions (like PayPal) require a return url to find the way back to my form. So the current workflow looks like this:
My form: collect user data
My form: redirect to payment provider with return url
Payment provider: collect user data for payment
Payment provider: redirect back to my form
My form: show order details with collected data
Payment provider: submit order
My form: redirect to success page
I hope this is comprehensible so far. I stuck into step 4, because every redirect from a payment provider back to my form ends in a 404, because the payment provider add some query string parameters which my form don't know. I think the problem here is that this parameters are not cHash calculated and this is the reason because they fail.
In step 2 I generate the return url of my form, this looks like this:
$returnUrl = $this->uriBuilder
->reset()
->setTargetPageUid($returnPageId)
->setCreateAbsoluteUri(true)
->setSection('form-multistep')
->uriFor('step5', null, 'CouponItem', 'bookingmanager', 'p2');
The generated output looks like this:
https://example.com/coupon/?tx_bookingmanager_p2%5Baction%5D=step5&tx_bookingmanager_p2%5Bcontroller%5D=CouponItem&cHash=565dc2e51a8d43bf3836b43b994e98d0#form-multistep
So this is the url which will be send to different payment providers and if I copy paste this url into my browser this url is working, BUT the payment providers manipulate this url and add some custom query string parameters to it before they redirect.
For example PayPal add the following query string parameters:
paymentId, token and PayerID
So the generated redirect url from PayPal to my form looks like:
https://example.com/coupon/?paymentId=XXXXXX&token=XXXXXX&PayerID=XXXXXX&tx_bookingmanager_p2%5Baction%5D=step5&tx_bookingmanager_p2%5Bcontroller%5D=CouponItem&cHash=565dc2e51a8d43bf3836b43b994e98d0#form-multistep
And this url ends in a 404 because I think the cHash is not valid for this manipulated url anymore or am I wrong?
Furthermore I have tried to predefine this parameters, like so:
$returnUrl = $this->uriBuilder
->reset()
->setTargetPageUid($this->settings['returnPageId'])
->setCreateAbsoluteUri(true)
->setSection('form-multistep')
->setArguments(['paymentId' => '', 'token' => '', 'PayerID' => ''])
->uriFor('step5',null,'CouponItem','bookingmanager','p2');
If I do something like that then the redirect from PayPal to my form is working, BUT unfortunately not as expected, because PayPal don't know that the parameters already exist in the given return url and still add this parameters again, so the generated url looks like:
https://example.com/coupon/?paymentId=&token=&PayerID=&tx_bookingmanager_p2%5Baction%5D=step5&tx_bookingmanager_p2%5Bcontroller%5D=CouponItem&cHash=bf642fb35a66033689b7d4ff772b3cf9#form-multistep&paymentId=XXXX&token=XXXX&PayerID=XXXX
Furthermore I can't access the query string parameters which PayPal added to the url :(. So I have tried something like this:
$returnUrl = $this->uriBuilder
->reset()
->setTargetPageUid($this->settings['returnPageId'])
->setCreateAbsoluteUri(true)
->setSection('form-multistep')
->uriFor('step5',null,'CouponItem','bookingmanager','p2');
$this->uriBuilder
->reset()
->setTargetPageUid($this->settings['returnPageId'])
->setCreateAbsoluteUri(true)
->setSection('form-multistep')
->setArguments(['paymentId' => '', 'token' => '', 'PayerID' => ''])
->uriFor('step5',null,'CouponItem','bookingmanager','p2');
So I have sent the "normal" return url (with only my extension parameters) to PayPal and register a second url with the uri builder to get the redirect from PayPal to my form working (this already seems to be bad).
So the redirect to PayPal through my form and the redirect from PayPal back to my form is working, BUT now I got the same problem as my last try. I got the right parameters in my URL, but I can't access them:
DebuggerUtility::var_dump(GeneralUtility::_GET());
array(3 items)
tx_bookingmanager_p2 => array(2 items)
action => 'step5' (5 chars)
controller => 'CouponItem' (10 chars)
cHash => 'cbe7c08c1a45e85404a06877c453cb63' (32 chars)
id => '175' (3 chars)
So how can I allow custom query string parameters which are generated by a third party app for a specific controller action?
You need to exclude those parameters from cHash.
In the InstallTool you will find a value for
$GLOBALS['TYPO3_CONF_VARS']['FE']['cHashExcludedParameters']
where you enter the list of paramters (comma separated) you do not want to be consiered in the cHash.

Can't get my form to be valid

I want to make a ReST infrastructure with a Symfony server (using FOSRestBundle).
there is
a Contact entity, (id, name, email)
a AddType form to add a new contact
On the client side, I have a form which sends a POST request whose body is
{"the_name_of_my contact_form":{"name":"foo", "email": "foo#example.org"}}
My controller (which extends FOSRestController) can see the data in the request
$request->request->get($form->getName()) returns {"name":"foo", "email": "foo#example.org"}
But whether I use $form->handleRequest($request) or $form->submit($data)
$form->isValid() is always false
I hope this is clear enough... can anyone help?
This problem is related to the CRSF validation. You have to disable it for the user requesting the service. You can disable it in you config.yml. You'll have to set something like this:
fos_rest:
disable_csrf_role: ROLE_API
Just make sure that the user requesting your service has this role. You can read more about user roles here.
Also, you'll have to submit you form, not handle it. Here is an snippet of it:
$form = $this->createForm(ProjectType::class, $project);
$form->submit($request->request->all());
if ($form->isValid()) {

playframework 2.x form redirect with prefill

I'm trying to implement a user login form, I want to achieve:
when username doesn't exist in DB, return a flash message with previous
form data prefilled.
if any server side validation errors happened, return back to previous page, with old data pre-filled, display the errors
messages along side.
The problem now is, if I use flash scope, I need to use Redirect after post, but this will lose the pre-filled data.
If I use any status other than Redirect, I can't put data into flash scope.
What did I missing?
Don't use a Redirect for a failed login. You can return the same Form back to the login view with extra errors attached to it.
Something like this:
loginForm.bindFromRequest.fold(
formWithErrors => views.html.login(formWithErrors),
credentials => {
if(authenticate(credentials)) // dummy implementation
Redirect(controllers.Application.index)
else
BadRequest(views.html.login(loginForm.fill(credentials).withGlobalError("Incorrect login credentials")))
}
)
Then your view signature would look something like this:
#(loginForm: Form[Credentials])
#* Displays the first global error from the form, if any. *#
#loginForm.globalError.map{error =>
<h3>#error.message</h3>
}
And you'd pre-fill the form with values as before (I hope).
If there are multiple global errors, you can access them with globalErrors as it will access a Seq[FormError] instead of Option[FormError].
You can also attach errors to specific Form keys.
loginForm.withError("email", "I don't like your email.")
And would access them similarly:
#loginForm.error("email").map{ error =>
#error.message
}
You are using wrong concept, take a look to the Handling form submission doc, section: Validating a form in an Action
If form contains errors you are returning BadRequest (not Redirect)
If form IS valid anyway next check (i.e. DB query) returns an error you should do exactly the same thing as in formWithErrors so render the view passing incoming form to it (userData)
Finally if everything's OK, you can make your operation and redirect the user i.e. to main page or something...
Pseudo code (basing on doc):
def userPost = Action { implicit request =>
userForm.bindFromRequest.fold(
formWithErrors => {
BadRequest(views.html.user(formWithErrors))
},
userData => {
// Check whatever you need...
if (afterCheckSomethingIsWrong){
// if something's wrong fill the `userForm` with `userData` and render the same view again...
// You can use flash scope here i.e. for placing error message
BadRequest(views.html.user(userForm.fill(userData))).flashing("error" -> "The account doesn't exist")
} else {
// if everything is OK, redirect to some page, outside the form handling process, i.e. main page
Redirect(routes.Application.index).flashing("success" -> "Fine you're logged now")
}
}
)
}

Redirect to referer after a POST Request

I have a web application in Play. The web application consists of several pages. In every page there is a small flag that enables the user to change the language (locale) from german to english and back.
I handle this with a redirect to referer:
def referer(implicit request: Request[AnyContent]) =
request.headers.get(REFERER).getOrElse(mainUrl)
def locale(l: String) = Authenticated { user =>
implicit request =>
Redirect(referer).withCookies(Cookie(LANG, if (l == "de" || l == "en") l else "de"))
}
It is working fine. Well, at least for GET requests.
I have a specific page where the user has to input data in a form. This form is then POSTed to the server. Were errors found, the form is displayed again with the error messages, as usual. Now, if the user wants to change the language (by clicking on the flag), the redirect to referer does not work, because it tries to use a GET request, and Play complains that a GET route does not exist for this method (which is true).
I am solving this by caching the form and defining another method where the form is taken from the cache:
# User data is POSTed to the server
POST /create/insert controllers.MyCreate.insert()
# After a redirect the cached form is displayed again
GET /create/insert controllers.MyCreate.insertGet()
It works, but I don't like this solution. It does not seem normal to have to create another entry in the routes and another method just to adress this problem. I would need to add this hack for every POST route in my application!
Is there a more elegant solution to this?
You could change it into something like this (untested):
def changeLang(lang:String, returnUri:String) = Action {
Redirect(returnUri)
.withCookies(Cookie(LANG, if (lang == "de" || lang == "en") lang else "de"))
}
In you template you would output the route to changeLang in the link, you can get the uri via the request
#routes.Application.changeLang("en", request.uri).url
I suggest you make request implicit in your action and define it as implicit in your template so you don't need to pass it on to each template.
// in the controller
def myUrl = Action { implicit request =>
Ok(views.html.myTemplate("something"))
}
// in the template
#(title:String)(implicit request:play.api.mvc.RequestHeader)
Edit
As for the POST requests, it common (for these types of framework) to have POST requests simple handle stuff and then redirect to another page. The usual flow is like this:
Form submits to a handler
Handler does something with the form information
Handler redirects to a page
An example:
// Hooked up to a GET route
def edit(id:Long) = Action {
// render the view with a form that displays the element with given id
// if the flash scope contains validation information, use that in display
}
// Hooked up to a POST route
def editHandler = Action {
// validate the form
// if validation succeeds
// persist the object
// redirect to edit
// else
// put the form information into the flash scope
// put any validation messages into the flash scope
// redirect to edit
}
If you do not want to use this flow you need to have both a GET and POST route anyway. The user might do a page reload on the resulting page.

move from one action (signup) to confirm action (show the data submited) and success page Zend Framework

I am a newbie to Zend and creating a simple signup form but which has many fields. So I want to create a confirm page after the user signup action.
this is how my flow goes:
signup -> confirm ->success/error
My main reason for having a separate confirm form page is the data fields are so many so the user must go through to make sure they are all correctly filled.
using forms signup and confirm (with field disabled), I want to know if there is a way to pass the data from the signup form to confirm form?
Please any helpful ideas and suggestions welcomed
;)
public function signupAction()
{
$users = new Application_Model_Users();
$form = new Application_Form_RegistrationForm();
$this->view->form=$form;
if($this->getRequest()->isPost()){
if($form->isValid($_POST)){
$data = $form->getValues();
//some checks before sending data to confirm page
//not sure how the data can be passed to the confirm page from here
$this->_redirect('auth/confirmsignup');
}
}
}
public function confirmsignupAction()
{
$users = new Application_Model_Users();
$form = new Application_Form_ConfirmRegistrationForm();
$this->view->form=$form;
if($this->getRequest()->isPost()){
if($form->isValid($_POST)){
$data = $form->getValues();
//some checks before
unset($data['confirmPassword']);
$users->insert($data);
$this->_redirect('auth/login');
}
}
}
When redirecting, you will lose the POST data, unless:
You store it in session in signup and then read in confirmsignup
You don't redirect at all. Instead, after first submit check for existence of special data in your form, it may be a random token like hash of session id etc., but not easily guessable like "confirm=1". If the token does not exist, add a hidden field with this token to your form and show it to the user again in the same action, with data filled in - this will be the confirmation phase. If you have a POST in signup again, you will receive the token and by checking it exists, you will know this is the second submit with confirmation and you may proceed with the signup. I hope I didn't overcomplicate this.