How-to Form confirmation response - laravel

I am a Laravel newbie. I have gone through tutorials successfully...this is my first production app for a client, using Laravel
I have a form and am able to get the submitted data into a database. The user is given a generic error though. After successful submission, I cannot redirect the user to a confirmation page. Any help is appreciated.
Here is the code from my controller (the store function):
/**
* Store a newly created resource in storage.
*
* #return Response
*/
public function store()
{
$rules = array(
'lastname' => 'required',
'email' => 'required|email',
'phone' => 'required',
'date' => 'date|date_format:"m/d/Y"'
);
$validator = Validator::make(Input::all(), $rules);
// process the login
if ($validator->fails()) {
return Redirect::to('nerds/create')
->withErrors($validator)
->withInput(Input::except('password'));
}
else
{
// store
$registration = new Registration;
$registration->firstname = Input::get('firstname');
$registration->lastname = Input::get('lastname');
$registration->email = Input::get('email');
$registration->date = date("Y-m-d", strtotime(Input::get('date')));
$registration->phone = Input::get('phone');
$registration->venue = Input::get('venue');
$registration->venueCity = Input::get('venueCity');
$registration->save();
// redirect
Session::flash('message', 'Successfully submitted, someone will contact you soon!');
return Redirect::to('thankyou');
}
}

Please make sure the app/storage folder has the full permissions. Apache should be able to write to that folder.

There must be a route to handle the 'Redirect::to'
The tutorial I followed failed to mention this fact.
Along with the code from my controller's store() function, the following route is necessary.
Route::get('/registration/thankyou', function()
{
return View::make('thankyou');
});

Related

Laravel 8 reset password in PWA

I am trying to establish a password reset system in a Laravel + SPA environment with Vuejs. Except for the form that starts the process ('I have forgotten my password'), the rest of the actions I want to be carried out in the standard Laravel way, outside the spa and using Laravel Auth. So I have:
web.php
//This sends a custom email notification.
Route::post('/forgot-password', 'UserController#reestablecerPassword')->middleware('guest')->name('password.email');
//Shows the standard view auth.passwords.reset, when we can reset password values.
Route::get('/reset-password/{token}', function ($token) {
return view('auth.passwords.reset', ['token' => $token]);
})->middleware('guest')->name('password.reset');
//Route to custom method in order to receive and process the previous form
Route::post('/reset-password', 'UserController#procesarReestablecerPassword')->middleware('guest')->name('password.update');
//this returns a simple view where it is reported that the password has been changed
Route::get('/changedpassword', function () {
return view('changedpassword');
})->name('changedpassword');
Methods in UserController.php
//Handles the response by email and the response to the front-end to inform that the email has been sent
public function reestablecerPassword(Request $request){
$request->validate(['email' => 'required|email']);
$status = Password::sendResetLink(
$request->only('email')
);
Log::info('status:');
Log::info($status);
$status == 'passwords.sent' ? $respuesta = response('OK', 200) : $respuesta = response('KO', 400);
return $respuesta;
}
//Process the request that contains the form data and reset the password. The program flow does not execute this method in production, the log is not written
public function procesarReestablecerPassword(Request $request) {
Log::info('entra a procesarReestablecerPassword con estos valores en la petición:');
Log::info($request);
$request->validate([
'token' => 'required',
'email' => 'required|email',
'password' => 'required|min:8|confirmed',
]);
$status = Password::reset(
$request->only('email', 'password', 'password_confirmation', 'token'),
function ($user, $password) use ($request) {
$user->forceFill([
'password' => Hash::make($password)
])->save();
$user->setRememberToken(Str::random(60));
event(new PasswordReset($user));
}
);
return $status == Password::PASSWORD_RESET
? redirect()->route('changedpassword')->with('status', __($status))
: back()->withErrors(['email' => [__($status)]]);
}
The issue appears when we submit the reset-password view form, when I get the error:
The GET method is not supported for this route. Supported methods: POST.
Observing the operation in the web browser:
In the local environment, where the operation is correct, two actions happen after pressing the submit button of the form:
A first POST type is sent with the form data. Such as:
https://appname.com/reset-password
A 'Location' header is received to redirect to a second URL via GET that includes the user token. Being of type:
https://appname.com/reset-password/jkladjfñl9iu08adDjfjnnakRfpaiw
Well, in the production environment, this token is not found in the url received in the Location header (I don't know why this occurs)
Thanks in advance.

Laravel: Feature Test fails because of added middleware

After a user signs up and verifies their email, they must complete their signup with additional information. This happens at /register/complete-signup
The issue makes absolutely no sense to me.
For whatever reason, when I added my Middleware has-not-completed-signup, the test starts failing because a App\User no longer has the associated App\Account which is happening in the controller via attach()
As soon as I remove my middleware from the route, it works fine.
My middleware is there to prevent a user who has completed the signup already from visiting or POSTing to those routes. I tested in the browser and the redirect works. The controller method is being used in the test and i can dd($account->users) and get the correct response. But if I do $user->accounts, the collection is empty.
Once I remove my middleware, $user->accounts is no longer empty. But I did a dd() inside my middleware and it's not even running (which is correct because the user doesn't have an account).
So why would this make it fail? I'm completely lost.
I tried to include all relevant information below. If there is something else you need, please let me know.
Edit:
In my middleware, I've commented out the functionality. Something about checking an eloquent relationship makes me test fail. I have no idea why.
This makes the test fail:
if (!auth()->user()->accounts->isEmpty()) {
//return redirect(RouteServiceProvider::HOME);
}
If for example I change it to something useless like this, it works:
if (auth()->user()) {
//return redirect(RouteServiceProvider::HOME);
}
I can do $account->users , but $user->accounts returns empty collection on the controller when I use my middleware
Original:
Here are my routes:
// auth scaffolding
Auth::routes(['verify' => true]);
// main app routes
Route::middleware('verified', 'auth')->group(function() {
// User verified and has an App\Account
Route::middleware('completed-signup')->group(function() {
Route::get("/", 'HomeController#index' )->name('home');
Route::get('/paywall', 'BillingController#paywall')->name('paywall');
});
// The user hasn't attached an App\Account to their User
Route::middleware('has-not-completed-signup')->group(function() {
Route::get("/register/complete-signup", 'AccountController#showCompleteSignup' )->name('complete-signup');
Route::post('/register/complete-signup', 'AccountController#storeCompleteSignup')->name('complete-signup.store');
});
});
has-not-completed-signup Middleware:
public function handle($request, Closure $next)
{
if (auth()->user()->hasCompletedAccountSetup()) {
return redirect(RouteServiceProvider::HOME);
}
return $next($request);
}
App/User method:
Class User extends Authenticatable implements MustVerifyEmail {
...
public function accounts() {
return $this->belongsToMany('App\Account', 'account_role_user')->withPivot('role_id');
}
public function hasCompletedAccountSetup() {
return !$this->accounts->isEmpty();
}
...
AccountController#storeCompletedSignup:
public function storeCompleteSignup(Request $request) {
$validatedData = $request->validate([
'company' => 'required|max:255',
'contact_state' => 'required|max:255',
'contact_country' => 'required|max:255',
'contact_zip' => 'required|max:255',
'contact_city' => 'required|max:255',
'contact_phone' => 'nullable|max:255',
'contact_address_1' => 'required|max:255',
'contact_address_2' => 'nullable|max:255',
'contact_first_name' => 'required',
'contact_last_name' => 'required',
'contact_email' => 'required'
]);
$user = auth()->user();
$account = new Account($validatedData);
$account->contact_first_name = $user->first_name;
$account->contact_last_name = $user->last_name;
$account->contact_email = $user->email;
$account->save();
$account->users()->attach(
$user->id,
['role_id' => Role::where('name', 'owner')->first()->id ]
);
return $request->wantsJson()
? new Response('Signup Completed Successfully', 201)
: redirect()->route('/');
}
My Test:
/**
* #test
*/
public function a_user_can_complete_signup()
{
$user = Factory('App\User')->create();
$this->actingAs($user);
$accountAttributes = factory('App\Account')->raw([
'contact_first_name' => "TEST",
'contact_last_name' => $user->last_name,
'contact_email' => $user->email,
'contact_country' => "USA"
]);
$res = $this->post('/register/complete-signup', $accountAttributes);
$res->assertSessionDoesntHaveErrors();
$this->assertTrue( !$user->accounts->isEmpty() ); // THIS FAILS
$this->assertTrue( $user->accounts->first()->company == $accountAttributes['company']);
$this->assertTrue( $user->accounts->first()->contact_first_name == $user->first_name );
}
The issue wasn't actually with the middleware, but it was because I had to refresh the model after the POST on the test.
$this->assertTrue( !$user->accounts->isEmpty() );
needed to become
$this->assertTrue( !$user->fresh()->accounts->isEmpty() );
which passed the test.
I knew about the fresh and refresh() methods, but the middleware causing the issue didn't make sense to me.

Laravel Socialite - Avatar is to slow

I am using the Laravel 5.0 with the Socialite Library. Everything works fine except i am a little disappointment with the size of the avatar.
Is it possible to get a bigger avatar?
Looking at the source code of Socialite https://github.com/laravel/socialite/blob/2.0/src/Two/FacebookProvider.php
You can see at line 91 that the url for the avatar appends a static ?type=normal at the end. The Facebook graph API documentation says that you can request an avatar size using an ENUM or custom width/height so you can modify line 91 and append an ENUM or custom width/height like ?type=large
More details can be found in the Facebook graph API documentation.
However, this is only for the Facebook driver so you will need to dig in a similar fashion for other providers. If their APIs do not allow such freedom as in the case of Facebook then you will need to do with the avatar being returned.
Updated March 12, 2015
Do not customize the original Socialite package, fork the repository and then make the change. You can then plug your forked repository into your project and also send a pull request to the original author in case he thinks its worth having the functionality you have implemented. Also, your forked repository will not be updated/maintained as is the case with the original package. In case you choose to update to the new package, your changes will be lost.
Fix for Facebook / Google / Twitter Small Avatar Photo
I have created this helper method on my AuthController
public function getBigAvatar($user, $provider)
{
return ($provider == "google") ? $user->getAvatar()."0" : $user->avatar_original;
}
AND THIS IS HOW I CALL IT:
$user = Socialite::driver( $provider )->user();
$userPhoto = $this->getBigAvatar($user, $provider);
So Simple in case its Google, Well just append 0 to the end of the url and well get a 500px avatar. And for twitter and Facebook, The Providers already offers an avatar_original attribute as seen in
FacebookProvider.php
protected function mapUserToObject(array $user)
{
$avatarUrl = $this->graphUrl.'/'.$this->version.'/'.$user['id'].'/picture';
return (new User)->setRaw($user)->map([
'id' => $user['id'], 'nickname' => null, 'name' => isset($user['name']) ? $user['name'] : null,
'email' => isset($user['email']) ? $user['email'] : null, 'avatar' => $avatarUrl.'?type=normal',
'avatar_original' => $avatarUrl.'?width=1920',
]);
}
TwitterProvider.php
return $instance->map([
'id' => $user->uid, 'nickname' => $user->nickname,
'name' => $user->name, 'email' => $user->email, 'avatar' => $user->imageUrl,
'avatar_original' => str_replace('_normal', '', $user->imageUrl),
]);
Since Google does not map this, And gets a default 50px image, we simple change it to 500px with is great for avatar.
WORKED PERFECT FOR ME, LET ME KNOW IF IT HELPS YOU!
Just append your required size after the ?type=normal like this:
graph.facebook.com/v2.2/{user}/picture?type=normal &width=76&height=76
this will override the type=normal
For use original avatars size from Facebook and Google:
public function handleProviderCallback($provider){
$userData = Socialite::driver($provider)->user();
$user = new User;
...
Google:
$user->avatar = preg_replace('/\?sz=[\d]*$/', '', $userData->avatar);
Facebook:
$user->avatar = $userData->avatar_original;
here is another simpler way for the avatar
public function redirectToProvider($provider)
{
return Socialite::driver($provider)->redirect();
}
public function handleProviderCallback($provider)
{
$user = Socialite::driver($provider)->user();
// dd($user);
if ($user) {
$authUser = $this->findOrCreateUser($user, $provider);
Auth::login($authUser, true);
return redirect()->route('home');
}
return 'something went wrong';
}
private function findOrCreateUser($user, $provider)
{
$found = User::where('provider_id', $user->id)->first();
if ($found) {
return $found;
}
// so the default is G+, change according to your needs
$avatar = str_replace('?sz=50', '', $user->avatar);
if ($provider == 'facebook' || $provider == 'twitter') {
$avatar = $user->avatar_original;
}
return User::create([
'username' => $user->name,
'email' => $user->email,
'avatar' => $avatar,
'provider' => $provider,
'provider_id' => $user->id,
]);
}
this way u dont change ur code much and easier for maintenance ,
however for some reason the facebook avatar doesnt show up in my app , plz if anyone can help i would deeply appreciate it.
here is my the current fb avatar link i get
https://graph.facebook.com/v2.6/xxxxxxxxxxx/picture?width=1920
and the view
<img class="user-avatar" src="{{ $user->avatar }}" alt="user avatar">
Here is code I have came up with
if($file = $user->getAvatar()) {
if ($provider == 'google') {
$file = str_replace('?sz=50', '', $file);
} elseif ($provider == 'twitter') {
$file = str_replace('_normal', '', $file);
} elseif ($provider == 'facebook') {
$file = str_replace('type=normal', 'type=large', $file);
}
}
Happy coding everyone :)
This is basically just replacement or a part of avatar image URL so it would return the bigger one.
getAvatar() function return the image url, so I stored it in variable $file
And depending on the provider url structure is different, so for each provider you need to change image URL string accordingly
Google
remove '?sz=50' from image url string
Twitter
remove '_normal' from image url string
Facebook
replace 'type=normal' with 'type=large' in image url string

Update profile function

I have a function that check updates the users profile info. Currently, if I put |unique:users in the validator every time I try to update the profile info on the form it will not let me because a user (which is me) has my email. So I figured out the unique means that nobody, including the current user can have the email that is being updated.
So I need to compare the current auth email to the one in the database. If it matches then it is ok to update the profile info. I know this is simple but I am not sure how to implement it and if that is the right logic.
So where in this code would I post if (Auth::user()->email == $email){..update email...} http://laravel.io/bin/GylBV#6 Also, is that the right way to do this?
public function editProfileFormSubmit()
{
$msg = 'Successfully Updated';
$user_id = Auth::id();
$user = User::find($user_id);
$first_name = Input::get('first_name');
$last_name = Input::get('last_name');
$email = Input::get('email');
$phone_number = Input::get('phone_number');
$validator = Validator::make(Input::all(), array(
'email' => 'required|email',
'first_name' => 'required',
'last_name' => 'required',
'phone_number' => 'required'
));
if ($validator->fails()) {
return Redirect::route('edit-profile')
->withErrors($validator)
->withInput();
}else{
if(Input::hasFile('picture')){
$picture = Input::file('picture');
$type = $picture->getClientMimeType();
$full_image = Auth::id().'.'.$picture->getClientOriginalExtension();
if($type == 'image/png' || $type == 'image/jpg' || $type == 'image/jpeg'){
$upload_success = $picture->move(base_path().'/images/persons/',
$full_image);
if($upload_success) {
$user->picture = $full_image;
} else {
$msg = 'Failed to upload picture.';
}
}else{
$msg = 'Incorrect image format.';
}
}
$user->first_name = $first_name;
$user->last_name = $last_name;
$user->email = $email;
$user->phone_number = $phone_number;
$user->save();
return Redirect::route('invite')->with('global', $msg);
}
}
Worry not, Laravel has already considered this potential issue! If you take a look at the docs for the unique validation rule you'll see that it can take some extra parameters. As it happens, you can give it an id to ignore when looking at the unique constraint. So what you need to do is work out the id for the current model to update and pass that in. In the case of updating a logged-in user's profile it's made easy by Auth::id() as you already have in your code.
$rules = [
'email' => ['required', 'email', 'unique:users,email,'.Auth::id()],
'first_name' => ['required'],
// etc...
];
Obviously I chose to use the array syntax for validation rules there, but you can do the same with the pip syntax too. In a less specific system (create-or-add in a crud postSave type action) you can still do it by simply dong something like $model = Post::find($id) and then if $model is null you're creating and you just use 'unique' whereas if $model is not null, use 'unique:table,field,'.$model->getKey().

Laravel: Auth Attempt failed

I have tried the following codes. But the auth attempt failed.
// validate the info, create rules for the inputs
$rules = array(
'email' => 'required|email', // make sure the email is an actual email
'password' => 'required|alphaNum|min:3' // password can only be alphanumeric and has to be greater than 3 characters
);
// run the validation rules on the inputs from the form
$validator = Validator::make(Input::all(), $rules);
// if the validator fails, redirect back to the form
if ($validator->fails()) {
return Redirect::to('login')
->withErrors($validator) // send back all errors to the login form
->withInput(Input::except('password')); // send back the input (not the password) so that we can repopulate the form
} else {
// create our user data for the authentication
$userdata = array(
'Email' => Input::get('email'),
'Password' => Input::get('password')
);
// attempt to do the login
if (Auth::attempt($userdata)) {
// validation successful!
// redirect them to the secure section or whatever
// return Redirect::to('secure');
// for now we'll just echo success (even though echoing in a controller is bad)
echo 'SUCCESS!';
} else {
// validation not successful, send back to form
return Redirect::to('login')->with('message', 'Login Failed');
}
}
I have the following columns in Users table.
id,
Username,
Email,
created_at,
updated_at,
Password
I have changed the table name in model. Please advice. What's wrong?
Usually the convention is to make database columns lowercase. With the email column, that should not be a problem. As the documentation states, the 'email' field is only used as an example. http://laravel.com/docs/security#authenticating-users
To make Laravel use your capitalised password column. Open your user eloquent model and change:
public function getAuthPassword()
{
return $this->password;
}
to:
public function getAuthPassword()
{
return $this->Password;
}
Pretty sure that'll do the trick,

Resources