Laravel Prevent Form from Re submitting - php

I have a form of Adding Album in database
{!! Form::open(['method' => 'POST', 'route' => ['admin.album.store'], 'enctype' => 'multipart/form-data', 'id' => 'CreateAlbumForm']) !!}
<input type="hidden" name="_token" value="{{ csrf_token() }}">
// other fields
{!! Form::submit(trans('global.app_save'), ['class' => 'btn btn-danger']) !!}
{!! Form::close() !!}
It is working perfectly.
What i need is to prevent user from clicking submit button multiple times. which i know is possible with jquery ( disabling submit button on click).
But i want to make it using csrf protection(Server side) when user does not have javascript enabled.
After a lot of search i found below solution :
What i have tried
Adding Below function in VerifyCsrfToken.php
protected function tokensMatch($request)
{
$token = $request->input('_token') ?: $request->header('X-CSRF-TOKEN');
if (!$token && $header = $request->header('X-XSRF-TOKEN')) {
$token = $this->encrypter->decrypt($header);
}
$tokensMatch = ($request->session()->token() == $token) ? TRUE : FALSE;
if($tokensMatch) $request->session()->regenerateToken();
return $tokensMatch;
}
And adding _token inside $dontFlash array in file app\Http\Requests\FormRequest.php
protected $dontFlash = ['password', 'password_confirmation', '_token'];
It gives me Token Mismatch error But when i click on submit button more than 2 times. And record is inserted 2 times which is unwanted behaviour.
It should give me error on 2nd attempt on submit at same time.
So in short What i need is if a user clicks on submit button single time it should insert record. and if he clicks on submit more than one time than it should give TokenMismatch Error.

You could set a token when you serve the form and check that against the database. When you submit the form, the token is checked and you can't submit it any more. Of course, it is still a good idea to do it front-end too as it is more visual for the user.
https://laracasts.com/discuss/channels/laravel/stopping-multiple-form-submission
Just searching for relevant answer and found this. Hope it will help in some way.

Related

Symfony3 change form field type in the controller

I have a form builder which builds a form
public function buildForm(FormBuilderInterface $builder, array $options)
{
$builder->
add('typeTask',TextType::class,array('label'=>"Вид заявка"))->
add('description',TextareaType::class,array('label'=>"Описание"))->
add('term',DateType::class, array(
'widget' => 'choice',
'label'=>"Краен срок"
))->
add('designer',TextareaType::class,array('label'=>"Дизайнер",
"required"=>false))->
add('executioner',TextareaType::class,array('label'=>"Под изпълнител",
"required"=>false))->
add("file",TextType::class,array('label'=>"Файл",
"required"=>false))->
add("ergent",CheckboxType::class,array('label'=>"Спешно",
"required"=>false))->add("approved",HiddenType::class,array(
"required"=>false
))->add("rejected",HiddenType::class,array(
'required'=>false
));
}
As you see I have 2 fields which are "approved" which can be true or false and rejected which can also be true and false. Usually they are hidden because only 1 type of user can access them - ROLE_ADMIN and the rest is for ROLE_EDITOR. In my case the ADMIN needs to only approve or reject it and the EDITOR can't do that. The biggest issue is that I don't need a whole form, but rather 2 buttons - "Approve" and "Reject" when the Project is shown ("show" action), but the action which changes the Project is "edit" and so what I tried so far is from "show" to send a form to "edit" and then when the edit action is over to load the "show" action again.I tried achieving this by creating 2 forms - approveForm and rejectForm which can hold only 1 property each and send and flush them to "edit" function, but the edit function doesn't accept the form and also if it did it would have deleted everything else. Here is my code so far
In show action -
$projectFormApprove = $this->createForm('AppBundle\Form\ProjectType', $project,array(
"method"=>"post"
));
$projectFormApprove->remove("description");
$projectFormApprove->remove("designer");
$projectFormApprove->remove("executioner");
$projectFormApprove->remove("term");
$projectFormApprove->remove("typeTask");
$projectFormApprove->remove("file");
$projectFormApprove->remove("ergent");
$projectFormApprove->remove("approved");
$projectFormApprove->remove("rejected");
$projectFormApprove->add("approved",HiddenType::class,array(
"data"=>true
));
$projectFormReject = $projectFormApprove;
$projectFormReject->remove("approved");
$projectFormReject->add("rejected",HiddenType::class,array(
'data'=>true
));
This will create 2 forms each having 1 property and here is what happens in my twig template
<tr>
<td>
{{ form_start(approveForm, {'action': path('project_edit', { 'id': project.id })}) }}
{{ form_widget(approveForm) }}
<input type="submit" value="Approve" />
{{ form_end(approveForm) }}
</td>
</tr>
<tr>
<td>
{{ form_start(rejectedForm,{'action': path('project_edit', { 'id': project.id })}) }}
{{ form_widget(rejectedForm) }}
<input type="submit" value="Reject" />
{{ form_end(rejectedForm) }}
</td>
</tr>
I need two forms since there are 2 buttons which simply submit them and no one actually changes the value ( this is the reason why in "show" function the created property have "data"=>true. If the form is submitted it will do it automatically.
Here is what is in my "edit" function -
/** #var $user User */
$user = $this->getUser();
$project = new Project();
$form = $this->createForm('AppBundle\Form\ProjectType', $project);
if($user->getType() != "LittleBoss"){
$form->remove("designer");
$form->remove("executioner");
}
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$project->setFromUser($user->getUsername());
$project->setDepartment($user->getDepartment());
$project->setIsOver(false);
$project->setDate(new \DateTime());
$project->setSeenByDesigner(false);
$project->setSeenByExecutioner(false);
$project->setSeenByLittleBoss(false);
$project->setSeenByManager(false);
$em = $this->getDoctrine()->getManager();
$em->persist($project);
$em->flush();
return $this->redirectToRoute('project_show', array('id' => $project->getId()));
}
return $this->render('project/new.html.twig', array(
'project' => $project,
'form' => $form->createView(),
));
Now to my actual problem - As you see I first remove "approved" field and then I add new one with predefined value. What I want is to change not the values, but the type of description and the rest fields. Is there a way to say $form->change(); or anything that can change the types of the fields without having to remove them. The type I want them to be is HiddenType and set their data so that when I submit one of the 2 forms it will be accepted as valid in the "edit" action then flushed to the database and everything will be fine. So far when one of the buttons - "Approve" or "Reject" is clicked in the "edit" action $edit_form->IsSubmited() returns false.
I suggest you to create seperate forms, one for editor and another for admin. Then in controller use the form you need by permissions of the logged in user.
if ($this->authorizationChecker->isGranted('ROLE_EDITOR')) {
$form = $this->createForm(EditorType::class);
} elseif ($this->authorizationChecker->isGranted('ROLE_ADMIN')) {
$form = $this->createForm(AdminType::class);
}
$form->handleRequest($request);
In both forms you can use same entity, but different fields.

User Submitted Posts Store function using Laravel

So in addition to my prior topic (thank you guys for helping) routes with compact
I am now facing troubles with my store function, I am getting the error : "Trying to get property of non-object.
This is my store function in my Controller
public function store(Request $request)
{
// validate the data
$this->validate($request, array(
'title' => 'required|max:255',
'body' => 'required'
));
// store in the database
$userpost = new Usp;
$userpost->title = $request->title;
$userpost->body = $request->body;
$userpost->save();
Session::flash('success', 'The blog post was successfully saved!');
return redirect()->route('admin.userposts.show', $userpost->id);
}
This is the view to create userpost(to make it more clear, p.s. the same form with different route ofcourse, works for my simple posts but not for my user submitted posts)
#extends('index')
#section('index-stylesheets')
{!! Html::style('css/parsley.css') !!}
#endsection
#section('content')
<h1>Create New User Post</h1>
<hr>
{!! Form::open(array('route' => 'admin.userposts.store', 'data-parsley-validate' => '')) !!}
{{ Form::label('title', 'Title:') }}
{{ Form::text('title', null, array('class' => 'form-control', 'required' => '', 'maxlength' => '255')) }}
{{ Form::label('body', "Post Body:") }}
{{ Form::textarea('body', null, array('class' => 'form-control', 'required' => '')) }}
{{ Form::submit('Create Post', array('class' => 'btn btn-success btn-lg btn-block', 'style' => 'margin-top: 20px;')) }}
{!! Form::close() !!}
#endsection
#section('index-scripts')
{!! Html::script('js/parsley.min.js') !!}
#endsection
Method of showing the post:
public function show($id)
{
$userpost = Usp::find($id);
return view('admin.userposts.show', compact('userpost'));
}
So the fact is that the problem was not the store method but the routes.
Route::get('/userposts/{id}', 'UserPostsController#show')->name('admin.userposts.show');
Route::get('/userposts/create', 'UserPostsController#create')->name('admin.userposts.create');
Route::post('/userposts/store', 'UserPostsController#store')->name('admin.userposts.store');
When registering the routes in that order, when laravel will iterate over your routes, it will first encounter the show one, and it will therefore take "create" as the id. Therefore, it will go into the show method and it won't find any post that matches, the post being null, you get the error.
So, there are two ways of fixing this.
The first one (the easiest, works in all cases, maybe not the best) is to put the create route before the show route.
The second one, the best in my opinion, is to add a condition to the id (doesn't work in the case of a slug). As the ids are only integers, you get :
Route::get('/userposts/{id}', 'UserPostsController#show')->name('admin.userposts.show')->where('id', '[0-9]+');
Therefore, create won't match the regular expression and it won't go in the show method.
For "resource creations" (storing in database), I wouldn't use a "field-by-field" method.
Instead, I'd do something like this :
$userpost = Usp::create($request->only('title', 'body'));
I feel this is more talkative.
But, it won't work, laravel protects* us against such things. To make it work, you have two options.
(The best option in my opinion)
In your model, add a protected variable called $fillable with all your columns that you allow to mass assign*. In this case you would put :
protected $fillable = ['name'];
(The option if you are sure of what are you doing)
In your model, you can say, hey, I know what I'm doing here, just let me do my stuff without guarding me. In this case you would put :
protected $guarded = [];
Notes :
$request->only('field1', ...) gives you an array of the fields that you want with the fields name as keys, in this case it gives you ['field1' => $request->field1]. In your case it will give you ['title' => $request->title, 'body' => $request->body].
Mass assignment is when you give an array to the model and it puts all attributes to the fields of the array. More informations here https://laravel.com/docs/5.4/eloquent#mass-assignment
When I mean laravel protects us against those things, it does't really protect us because it isn't a bad practice (instead, I find it more readable), but because it does allow you to make mistakes (for exemple, setting fields that don't exist).

Laravel/Blade Form PUT Method, dd(Input::all());

Hi I send a form in my contact.blade.php. I read in order to use the PUT method you have to create a hidden input field which contains the method.
#if($do == 'edit')
{{ Form::model($contact, array('method' => 'PUT', 'route' => array('contact.update', $contact->id), 'id' => $do=='edit' ? $do.$contact->id : $do.$contact_type_id, 'form_id' => $do=='edit' ? $do.$contact->id : $do.$contact_type_id)) }}
{{ Form::hidden('_method', 'PUT') }}
#endif
....
{{ Form::submit('speichern', array('class' => 'btn btn-primary')) }}
</div>
{{ Form::close() }}
The route:
Route::put('/contact/{id}', array(
'uses' => 'ContactController#update',
'as' => 'contact.update'
));
The Controller:
public function update($id)
{
dd(Input::all());
// //get user account data
// $user = User::find( Auth::id() );
// // validate input
// $v = Contact::dataValidation( Input::all() );
return Redirect::Route('user.edit', 1)->withSuccess("<em>Hans</em> wurde gespeichert.");
Q1:
As soon as I call dd(Input::all()); I don't get redirected any more, instead I see a json with my form values.
Q2:
I'm just debugging this so I didn't program it. So my second question is:
From my understanding dd(Input::all()); gets all my form data. So don't I need to store it anyways somewhere?
Q1: dd() terminates the script, hence why you are not getting redirected. It's used as a tool to essentially break and examine what is going on.
http://laravel.com/docs/4.2/helpers
Q2: You will still need a model to feed the Input::all data into. Input::all simply fetches the submitted data, it doesn't do anything with it. It ultimately depends on your use case, sometimes you may want to email the data, but obviously most times you would what to store it against your persistence layer (read database / datastore)
Question 1
when you use DD, it will show the data and stop at that line.
DD
Dump the given variable and end execution of the script.
more information you can read it here DD in DD session.
Question 2
I'am not sure about 2nd question but if you want to get value from all input you could us Input::all();
more information All input in Getting All Input For The Request session

What is the correct way to persist input through a confirmation page and into a submission page in Laravel?

I've been working on migrating several of our forms to Laravel, but there's one last step I'm not entirely sure on how to go about. I have a form that does an Insert into a database, but instead of just having 2 pages--the form and the submission page--I have 3: the form, a confirmation and a submission page.
Here is what I have at the moment:
Routes:
Route::any('application/housing-form', array('as'=>'application.form', 'uses'=>'ApplicationController#form'));
Route::post('application/confirmation', array('as'=>'application.confirmation', 'uses'=>'ApplicationController#confirmation'));
Route::post('application/submit', array('as'=>'application.submit', 'uses'=>'ApplicationController#submit'));
ApplicationController:
public function form()
{
$application = new Application;
return View::make('application/form')->with(array('application'=>$application));
}
public function confirmation()
{
$input = Input::all();
//More here?
return View::make('application/confirmation')->with(array('input'=>$input));
}
public function submit() {
$input = Input::all();
DB::table('application')->insert(
array(
<field1> => $input('field1')
...
)
);
return View::make('application/submit');
}
Views:
//form
{{ Form::model($application, array('route'=>'application.confirmation')
//inputs
{{ Form::submit('Continue') }}
{{ Form::close() }}
//confirmation
{{ Form::open(array('route'=>'application.form') }}
{{ Form::submit('Back to my information') }}
{{ Form::close() }}
{{ Form::open(array('route'=>'application.submit') }}
{{ Form::submit('Submit') }}
{{ Form::close() }}
//submission
<p>Thank you for your submission!</p>
What I am unsure about is how to persist the data from the form through the confirmation page and into the submission page. From what I can tell, I can see a few options:
Reflash all of the input
Use a hidden field (or fields) to send the information
Insert the information into the database in the confirmation page and just do an update with an in-between query with the information.
I'm pretty sure it would be the first one: reflashing the data. But if so, I'm not sure where you're actually supposed to call Session::flash or Session::reflash. Or how many times I need to do it to get it through all of the requests. Any suggestions on how to go about that, or how to streamline the rest of the form would be greatly appreciated.
One extra note as well is that this particular form deals with a large number of input fields (around 60). That's part of why I want to avoid having to request each individual field to a minimum.
What I would do is to flash the input to the session in order to repopulate the form. This can be achieved by using the Input::flash() method like so:
public function confirmation(){
Input::flash(); //this will store the input to the session
return View::make('application/confirmation');
}
Then in your view, use the Input::old() method to retrieve input data from the previous request:
{{ Form::text('fieldname', Input::old('fieldname')) }}

Persist Form Data Across Multiple Steps in Laravel

When I've made multistep forms in the past I would generally store the form data in the session before returning it to the view, that way the data persists if the user refreshes the page or clicks the browser's native back buttons.
Transferring my past logic to Laravel I built the following form consisting of three stages:
[Input -> Confirm -> Success]
Routes.php
Route::group(array('prefix' => 'account'), function(){
Route::get('register', array(
'before' => 'guest',
'as' => 'account-create',
'uses' => 'AccountController#getCreate'
));
Route::post('register', array(
'before' => 'guest|csrf',
'as' => 'account-create-post',
'uses' => 'AccountController#postCreate'
));
Route::get('register/confirm', array(
'before' => 'guest',
'as' => 'account-create-confirm',
'uses' => 'AccountController#getCreateConfirm'
));
Route::post('register/confirm', array(
'before' => 'guest|csrf',
'as' => 'account-create-confirm-post',
'uses' => 'AccountController#postCreateConfirm'
));
Route::get('register/complete', array(
'before' => 'guest',
'as' => 'account-create-complete',
'uses' => 'AccountController#getCreateComplete'
));
});
AccountController.php
<?php
class AccountController extends BaseController {
private $form_session = 'register_form';
public function getCreate()
{
if(Session::has($this->form_session))
{
// get forms session data
$data = Session::get($this->form_session);
// clear forms session data
Session::forget($this->form_session);
// load the form view /w the session data as input
return View::make('account.create')->with('input',$data);
}
return View::make('account.create');
}
public function postCreate()
{
// set the form input to the session
Session::set($this->form_session, Input::all());
$validation_rules = array(
'email' => 'required|max:50|email|unique:users',
'password' => 'required|max:60|min:6',
'password_conf' => 'required|max:60|same:password'
);
$validator = Validator::make(Input::all(), $validation_rules);
// get forms session data
$data = Session::get($this->form_session);
// Return back to form w/ validation errors & session data as input
if($validator->fails()) {
return Redirect::back()->withErrors($validator);
}
// redirect to the confirm step
return Redirect::route('account-create-confirm');
}
public function getCreateConfirm()
{
// prevent access without filling out step1
if(!Session::has($this->form_session)) {
return Redirect::route('account-create');
}
// get forms session data
$data = Session::get($this->form_session);
// retun the confirm view w/ session data as input
return View::make('account.create-confirm')->with('input', $data);
}
public function postCreateConfirm()
{
$data = Session::get($this->form_session);
// insert into DB
// send emails
// etc.
// clear forms session data
Session::forget($this->form_session);
// redirect to the complete/success step
return Redirect::route('account-create-complete');
}
public function getCreateComplete() {
return View::make('account.create-complete');
}
}
create.blade.php
<form action="{{ URL::route('account-create-post') }}" method="post">
Email: <input type="text" name="email" value="{{ (isset($input['email'])) ? e($input['email']) : '' }}">
#if($errors->has('email'))
{{ $errors->first('email') }}
#endif
<br />
Password: <input type="text" name="password" value="">
#if($errors->has('password'))
{{ $errors->first('password') }}
#endif
<br />
Password Confirm: <input type="text" name="password_conf" value="">
#if($errors->has('password_conf'))
{{ $errors->first('password_conf') }}
#endif
<br />
{{ Form::token() }}
<input type="submit" value="Confirm">
</form>
create-confirm.blade.php
Email: {{ $input['email']; }}
Password: {{ $input['password']; }}
<form action="{{ URL::route('account-create-confirm-post') }}" method="post">
{{ Form::token() }}
return
<input type="submit" name="submit_forward" value="Submit">
</form>
The above works fine, however I am wondering if this is the best way to approach multi-step forms in Laravel?
When I have created multi-part forms, I have always done it in a way so that the user can always come back and finish the form later, by making each form persist what it has to the database.
For instance
Step 1 - Account Creation
I would have the user create their authentication details at this step, create the user account (with password) here and also log the user in, redirecting to the dashboard. There I can do a check to see if the user has a profile and if they don't, redirect them to the profile creation form.
Step 2 - Profile Creation
Because we have an authenticated user, the profile creation form can save its data to the currently logged in user. Subsequent sections follow the same process but check the existence of the previous step.
Your question seems to be about confirming whether a user wishes to create an account. What I would do in your situation would be, on the form you created to confirm the user account, I would keep the user's data in hidden input fields.
Email: {{ $input['email'] }}
Password: {{ $input['password'] }}
<form action="{{ URL::route('account-create-confirm-post') }}" method="post">
<input type="hidden" name="email" value="{{ $input['email'] }}">
<input type="hidden" name="password" value="{{ $input['password'] }}">
{{ Form::token() }}
return
<input type="submit" name="submit_forward" value="Submit">
</form>
Although displaying the user's chosen password back to them on this page seems to be a bit superfluous when you ask them to confirm their password on the previous page, plus some users might question why their password is being shown in plaintext on the screen, especially if they are accessing the site from a public computer.
The third option I would suggest would be to create the user account and soft-delete it (Laravel 4.2 Docs / Laravel 5 Docs), returning the user's account number to the new form:
Email: {{ $input['email'] }}
Password: {{ $input['password'] }}
<form action="{{ URL::route('account-create-confirm-post') }}" method="post">
<input type="hidden" name="id" value="{{ $user_id }}">
{{ Form::token() }}
return
<input type="submit" name="submit_forward" value="Submit">
</form>
then undo the soft-delete when the user confirms their account. This has the added bonus that you could track people trying to sign up multiple times for an account and not completing the process and see if there's a problem with your UX.
Conclusion
Of course, you could also still do it the way you always have with a session, all I have tried to do here is show you some other ways you can approach it, as with everything to do with the best way of doing something, this is a highly opinionated subject and is likely to get many opposing views on how it should be done. The best way to do it is the way that works best for you and your users... mainly your users.
There are two ways to do it (that i can think of). I prefer second one.
Client side - everything can be handled by javascript. Basic validation (if field is email, if field has enough characters etc.) would be checked with javascript. After confirmation, AJAX request would go through server side validation and if anything went wrong you could highlight invalid inputs. "check if email is available" button (via AJAX) would be great too.
Server side - pretty much what you did but I would move it to service - it would make it much cleaner.
public function getCreate() {
if ($this->formRememberService->hasData()) {
return View::make('account.create')
->with('input', $this->formRememberService->getData());
}
return View::make('account.create');
}
public function postCreate() {
$this->formRememberService->saveData(Input::all());
// ...
}
public function postCreateConfirm() {
// ...
$this->formRememberService->clear();
return Redirect::route('account-create-complete');
}
Adding "forget me" action would be nice (especially if form requires more private data).
Why getCreate() has Session::forget()? If someone goes back to change something and accidently leaves your site his data will be lost.
1st) Create a custom hidden field in the form containing a random md5 character set to submit it with the form... (it can be the timestamp, the user ip address, and country concatenated together as 3 md5 strings separated by whatever character , or #, so it can be working as a token of the form)
2nd) pass the hidden field into your controller and validate it after getting the user input from the form by generating the same values in your controller, encrypting these values as md5 too, then concatenate them all together, and compare the values that is coming from the user input form with the values you are generating in your controller.
3rd) Put the values of the form in your controller in a session then regenerate the session id every visit to every view the user is going to visit.
4th) update the timestamp in your session according the timestamp the user is visiting every page.
Just because you know Laravel, does not mean you have to do everything in Laravel.
Multi-step forms should never involve server-side magic. The best and easiest you can do is to hide certain steps with display:none; and switch to the next step using javascript toggling visibilities only.

Categories