notification on new user registration - php

I used two tutorials to create code which notifies me on a new user registration. mail-notifications and redirect-login-register-add-method/
This works now
I am just sending a notification to default email defined here in my User model:
This is my desired goal
I want to send newly registered user's email in the emailed notification. Also, I wish to customize recipient for this specific HelloUser notification.
public function routeNotificationForMail()
{
return 'name#gmail.com';
}
The notification is fired by the code in my RegisterController.php:
protected function redirectTo()
{
$user = Auth::user();
$user->notify(new \App\Notifications\HelloUser());
return '/'; // redirects to main page
}
The above solution works, but I after many attempts I am still unable to get these extended result:
to do 1
I will have several notifications, which I want to mail to 2-3 emails, not just one.
My attempt
In my notification file App\Notifications\\HelloUser.php I tried to add
extra line. (Note tha the line is now commented)
public function toMail($notifiable)
{
return (new MailMessage)
->line('The introduction to the notification.')
->action('Notification Action', url('/'))
->line('The new user email is ');
// ->to('name#gmail.com');
}
... but I failed. I could define global recipient as described here in the documentation
My question #1
How I can I define a separate recipient for each Notification?
TO DO 2
I also tried to get the newly registered user's email into the emailed notification.
For this purpose I tried to copy my solution from Mailables. So in RegisterControler.php I tried to pass $user variable in the function:
protected function redirectTo()
{
$user = Auth::user();
$user->notify(new \App\Notifications\HelloUser($user));
return '/';
}
and then in my Notification file App\Notifications\HelloUser I did this:
public $user;
public function __construct($user)
{
$email = $user->email;
}
in a hope that this piece would produce a notification with new user's email:
public function toMail($notifiable)
{
return (new MailMessage)
->line('The introduction to the notification.')
->action('Notification Action', url('/'))
->line('The new user email is '.$user->email);
}
The result: I just get Undefined variable: user.
TO DO
How to make this code work?
Thank you for your time.
=============
Edit:
upon #Arun_jai request I place here the dd($notifiable). It seems that it is generated properly and the object is just instance of the User model. The relevant part:
#attributes: array:6 [▼
"name" => "Peter attempt 16"
"email" => "name#gmail.com"
"password" => "$2t9WHLFY14XNf0$nj7TYDYAxiZ/kdfrUy$1vC2"
"updated_at" => "2018-01-18 07:08:12"
"created_at" => "2018-01-18 07:08:12"
"id" => 270
]

Regarding the Undefined variable: user error, try instantiating the $user variable in your constructor, instead of the email. Your constructor should look like this:
public function __construct($user)
{
$this->user = $user;
}
Then you will be able to get the users' email by calling it with $this->user in any place of that class, so your MailMessage creation would look like this:
return (new MailMessage)
->line('The introduction to the notification.')
->action('Notification Action', url('/'))
->line('The new user email is ' . $this->user->email);

Related

Laravel redirect to blank page after logging in with encrypted user email

I have a client which want to save his data encrypted in the database (email, name etc.). He wants to log in with the email too. I made the functionality to log in with encrypted email but the problem is that after log in I am redirected to a blank page with the url /login when I should be redirected to /business-accounts. If I delete manually the /login from the url I am redirected to the /business-accounts which I need to be redirected. Before doing the ecrypted email authentication everything worked fine.
AuthenticatedSessionController.php
public function store(LoginRequest $request)
{
//check user is validated
User::all()->filter(function ($user) use ($request) {
if($user->email == $request->email){
if($user && $user->status==0){
throw ValidationException::withMessages([
'validation' => 'Account not verified.'
]);
}
//get user email crypted for login
$request->merge(['email' => User::find($user->id)->get_email_crypted()]);
$request->authenticate();
$request->session()->regenerate();
//set user session
UserService::set_session($user);
return redirect()->intended(RouteServiceProvider::HOME);
}
});
}
I printed a dd() before the return and seems like everything is working fine till there.
LoginRequest.php
public function authenticate()
{
$this->ensureIsNotRateLimited();
if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
RateLimiter::hit($this->throttleKey());
throw ValidationException::withMessages([
'email' => __('auth.failed'),
]);
}
RateLimiter::clear($this->throttleKey());
}
RouteServiceProvider.php
public const HOME = '/business-accounts';
Encryption and decryption is made in the User model with get/setEmailAttribute. In the authenticate() method I could see that it is not entering the if where Auth::attempt is located.
I tried to make it work in PasswordResetLinkController too but all I could get is the same blank page with the url /forgot-password and no email received in the inbox.
My Laravel version is 8.x.
L.E. I dumped something before the return redirect() and I saw that in the browser after the login submit I am redirected back to the /login form, so I am thinking that I am getting in a loop or something.
L.E.2.
I somehow resolved this. The thing that I done was changing the crypting method on the email column. I changed the Laravel cripting method with the openssl_crypt function.
User Model
public function setEmailAttribute($value)
{
if (!is_null($value)) {
$this->attributes['email'] = openssl_encrypt($value, "AES-128-ECB", env('EMAIL_ENCRYPT_KEY', false));
}
}
Where the crypting key is located in .env.
AuthenticatedSessionController.php
public function store(LoginRequest $request)
{
//check user is validated
$user = User::where('email', openssl_encrypt($request->email, "AES-128-ECB", env('EMAIL_ENCRYPT_KEY', false)))->first();
if($user && $user->status==0){
throw ValidationException::withMessages([
'validation' => 'Contul nu este verificat'
]);
}
//set email from request to encrypted email
$request->merge(['email' => User::find($user->id)->get_email_crypted()]);
$request->authenticate();
$request->session()->regenerate();
UserService::set_session($user);
return redirect()->intended(RouteServiceProvider::HOME);
}
you can take a look in the log file in /storage/logs/laravel.log for the error message
also, you could change the .env file to show_errors = true and it will show the error in the browser
Use return redirect()->route('your-route-name'); maybe can help you.

How to show an alert message to a user with Event Listener

I have created an Event called UserWalletNewTransaction.php and added this to it:
public $transaction;
public function __construct($transaction) {
$this->$transaction = $transaction;
}
Now in order to fire this event at the Controller, I coded this:
$newTransaction = UserWalletTransaction::create(['user_id' => $user_id, 'wallet_id' => $wallet_id, 'creator_id' => $creator_id, 'amount' => $amount_add_value, 'description' => $trans_desc]);
event(new UserWalletNewTransaction($newTransaction));
Then at the Listener, UserWalletNotification.php, I tried:
public function handle(UserWalletNewTransaction $event) {
$uid = $event->transaction->user_id;
$user = User::find($uid);
// now sends alert message to the user
}
So the scenario is, when Admins create a new Transaction for a custom user, a new alert message must be sent for him/her to let him/her know that new transaction was added for him/her.
But I don't really know how to do that.. So if you know, please let me know, I would really appreciate that...
Thanks in advance.
If by alert you mean showing a message on the web interface, use flash data.
https://laravel.com/docs/5.8/session#flash-data
$newTransaction = UserWalletTransaction::create(...);
event(new UserWalletNewTransaction($newTransaction));
$request->session()->flash('status', 'Transaction done.');
return view(...)
<span>{{ session('status') }}</span>
If you mean sending an email, just use the Mail facade in your listener to send a mailable.
https://laravel.com/docs/5.8/mail#sending-mail
public function handle(UserWalletNewTransaction $event) {
$uid = $event->transaction->user_id;
$user = User::find($uid);
Mail::to($user)->send(new TransactionDoneMail($event->transaction)); // TransactionDoneMail being your mailable class, made with "php artisan make:email TransactionDoneMail"
}
There are nice examples on how to build a mailable class in the documentation.
https://laravel.com/docs/5.8/mail#writing-mailables
There are many different things you can do in terms of "alerting" the customer.
One route would be to send an email or text message in your event listener. See https://laravel.com/docs/5.8/mail for help doing it via email.
Another way would be using browser push notifications. You could use OneSignal for this. You would setup the front end to display an alert to a customer user asking if they would like to subscribe to push notifications. When they subscribe, you will get back an ID for that specific user. Make an API call to your Laravel app, and store that ID in the users table (you will need a migration). Then from within your event listener, you can make a call to OneSignal's API and send the user a notification, which will popup on their computer.
Here is an example of using OneSignal to send an event to a user via the API:
Your OneSignal service:
<?php
namespace App\Services;
use App\User;
use GuzzleHttp\Client;
class OneSignalService
{
public function sendNotificationToUser(User $user, string $title, string $message, string $url, string $subtitle = null)
{
if (!$user->one_signal_id) {
return;
}
$fields = [
'app_id' => config('services.onesignal.app_id'),
'include_player_ids' => [$user->one_signal_id],
'headings' => ['en' => $title],
'contents' => ['en' => $message],
'url' => $url,
];
if ($subtitle) {
$fields['subtitle'] = ['en' => $subtitle];
}
$client = new Client([
'base_uri' => 'https://onesignal.com/api/v1/',
'headers' => [
'Content-Type' => 'application/json; charset=utf-8',
'Authorization' => 'Basic <<API_KEY>>',
]
]);
$client->request('POST', 'notifications', [
'json' => $fields
])
}
}
UserWalletNotification:
public function handle(UserWalletNewTransaction $event) {
$uid = $event->transaction->user_id;
$user = User::find($uid);
// now sends alert message to the user
$oneSignal = new OneSignalService();
$oneSignal->sendNotificationToUser($user, 'New Transaction', 'You have a new transaction', 'yourwebsite.com');
}
The way I would go about this would be via broadcasting, which would use websockets to instantly send the customer user an alert to their browser, in which you could then display a popup of some sort. You could install Laravel Echo Server, but to keep things simple you can use Pusher. Follow the guide to install on the front end of your website.
Then, create a private channel specific to a customer user "transaction.created.{{USER ID}}" and listen for it on your front end.
Within Laravel you will install the PHP Pusher SDK via composer.
Then within your .env file set:
BROADCAST_DRIVER=pusher
Next, open up channels.php within your routes directory in Laravel and add:
Broadcast::channel('transaction.created.{id}', function ($user, $id) {
return (int) $user->id === (int) $id;
});
This will verify authentication for your user to the private channel.
Create an Laravel Event:
<?php
namespace App\Events;
use App\User;
use Illuminate\Broadcasting\Channel;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Contracts\Broadcasting\ShouldBroadcastNow;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Queue\SerializesModels;
class TransactionCreated implements ShouldBroadcastNow
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public $user = null;
public $transaction = null;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(User $user, UserWalletTransaction $transaction)
{
$this->user = $user;
$this->transaction = $transaction;
}
public function broadcastWith(): array
{
return $this->transaction->toArray(); //Or whatever information you want to send to the front end
}
public function broadcastAs(): string
{
return 'TransactionCreated';
}
/**
* Get the channels the event should broadcast on.
*
* #return Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('transaction.created.' . $this->user->id);
}
}
Fire the event from UserWalletNotification:
public function handle(UserWalletNewTransaction $event) {
$uid = $event->transaction->user_id;
$user = User::find($uid);
// now sends alert message to the user
event(new TransactionCreated($user, $event->transaction));
}
Lastly, create some sort of popup and display it on the front end when your callback function for the private channel is hit.
If you need anymore help, feel free to comment.
What you want to do I believe, is asynchronous notifications.
Well, if you really mean flash messages - those who are stored in session - it will not be so easy.
Normal steps are create flash message for the user currently logged in on a website, stored in session that is unique for the current user. It can be shown only for this user.
What you want is to create flash message as the admin (from admin perspective) , then only to admin it can be shown.
I would do this, create new table, when these notification messages will be stored. Some table with columns like id, user_id, message, type, created_date, shown_date. Admins will put alert/notification messages for each user. Then create class (can be in controller for example) that will check this table for each user and if there is new not already shown message, show it normally in flash message for that current user. Dont forget to mark that message as shown. That is it.
So much for custom solution. I belive there must be some for example jQuery/other Jvascript plugins or Laravel plugins for asynchronous notifications, please check those.

Laravel Notification - Call to a member function routeNotificationFor() on string

Laravel 5.5
Controller
public function sendBookingSms(){
$checkState = session()->get('checkState');
$staffs = Staff::whereIn('staffId',$checkState)->get();
foreach ($staffs as $staff) {
$email = str_replace(" ","","44".substr($staff->mobile, 1)).'#mail.mightytext.net';
Notification::send($email, new NewBooking($email));
}
return $staffs;
session()->forget('checkState');
return redirect(route('booking.current'))->with('message','Succesfully Send SMS to selected staffs !!');
}
NewBooking.php (Notification)
public function toMail($notifiable)
{
return (new MailMessage)
->line('The introduction to the notification.')
->action('Notification Action', url('/'))
->line('Thank you for using our application!');
}
When calling this controller I am getting this error.
$staffs.
{
"staffId":45,
"forname":"Eldhose",
"surname":"John",
"categoryId":2,
"email":"devhelloworld#gmail.com",
"mobile":"07588593278",
"whatsappNumber":"57656578658",
"gender":1,
"address":"Poole",
"pincode":null,
"modeOfTransport":1,
"pickupLocation":"Office",
"branchId":0,
"zoneId":1,
"bandId":1,
"paymentMode":1,
"payRateWeekday":10,
"payRateWeekNight":20,
"payRateWeekendDay":10,
"payRateWeekendNight":20,
"payRateSpecialBhday":11,
"payRateSpecialBhnight":15,
"payRateBhday":11,
"payRateBhnight":15,
"status":1,
"deleted_at":null,
"created_at":"2018-02-26 22:16:44",
"updated_at":"2018-02-26 22:16:44"
}
Please help me on this.... Thanks
Notification::send() requires the first argument to be an object, usually one that uses the Notifiable trait. You pass just a string holding email address, hence the error.
If you simply want to send a notification to given email address, you'll need to use on-demand notifications. The following should do the trick:
Notification::route('mail', $email)->notify(new NewBooking($email));
For more details see the docs: https://laravel.com/docs/5.6/notifications#sending-notifications
Latest docs: https://laravel.com/docs/9.x/notifications#on-demand-notifications

Using the ResetPassword notification email template for custom emails

I generated a new email object through the artisan command to notify users about cumulative notifications. I succeeded using a custom template through the build method of the Email object:
public function build()
{
return $this->view('email.cumulative-notifications')
->with(['amount' => $this->amount])
->subject('You have some notifications!');
}
I want to use the template that is used for the reset password email which can be filled with lines and links like the ResetPassword does:
public function toMail($notifiable)
{
return (new MailMessage)
->subject('Reset Password')
->greeting('Hello!')
->line('...')
->action('Reset Password', url('password/reset', $this->token))
->line('...');
}
Should I use Notifications objects instead of Emails?

Testing unauthorized user restriction in Laravel PHPUnit

Laravel Version 5.2
In my project, users with role_id = 4 has the admin role and can manage users.
I have defined the following ability in AuthServiceProvider:
public function boot(GateContract $gate)
{
$this->registerPolicies($gate);
$gate->define('can-manage-users', function ($user)
{
return $user->role_id == 4;
});
}
I have used this ability in the UserController __construct method as follows:
public function __construct()
{
$this->authorize('can-manage-users');
}
In ExampleTest, I have created two tests to check if the defined authorization works.
The first test for admin user who has role_id = 4. This test passes.
public function testAdminCanManageUsers()
{
$user = Auth::loginUsingId(1);
$this->actingAs($user)
->visit('users')
->assertResponseOk();
}
The second test is for another user who does not have role_id = 4. I have tried with response status 401 and 403. But the test is failing:
public function testNonAdminCannotManageUsers()
{
$user = Auth::loginUsingId(4);
$this->actingAs($user)
->visit('users')
->assertResponseStatus(403);
}
First few lines of the failure message is given below:
A request to [http://localhost/users] failed. Received status code [403].
C:\wamp\www\laravel\blog\vendor\laravel\framework\src\Illuminate\Foundation\Testing\Concerns\InteractsWithPages.php:196
C:\wamp\www\laravel\blog\vendor\laravel\framework\src\Illuminate\Foundation\Testing\Concerns\InteractsWithPages.php:80
C:\wamp\www\laravel\blog\vendor\laravel\framework\src\Illuminate\Foundation\Testing\Concerns\InteractsWithPages.php:61
C:\wamp\www\laravel\blog\tests\ExampleTest.php:33
Caused by exception 'Illuminate\Auth\Access\AuthorizationException'
with message 'This action is unauthorized.' in
C:\wamp\www\laravel\blog\vendor\laravel\framework\src\Illuminate\Auth\Access\HandlesAuthorization.php:28
I have also tried to use 'see' method as follows:
public function testNonAdminCannotManageUsers()
{
$user = Auth::loginUsingId(4);
$this->actingAs($user)
->visit('users')
->see('This action is unauthorized.');
}
But it's failing too. What am I doing wrong? How can I make the test pass?
The mistake is calling the visit method. The visit method is in the InteractsWithPages trait. This method calls the makeRequest method which in turn calls assertPageLoaded method. This method gets the status code returned and if it gets code other than 200, it catches a PHPUnitException and throws an HttpException with the message
"A request to [{$uri}] failed. Received status code [{$status}]."
This is why the test was failing with the above message.
The test can be successfully passed by using get method instead of visit method. For example:
public function testNonAdminCannotManageUsers()
{
$user = App\User::where('role_id', '<>', 4)->first();
$this->actingAs($user)
->get('users')
->assertResponseStatus(403);
}
This test will pass and confirm that a non admin user cannot access the url.
Since the Auth middleware redirects to a login route when unauthenticated by default you could also perform the following test:
public function testNonAdminCannotManageUsers()
{
$user = Auth::loginUsingId(4);
$this->actingAs($user)
->visit('users')
->assertRedirect('login');
}
Since at least Laravel 5.4, you'll want to use the assertStatus(403) method.
public function testNonAdminCannotManageUsers()
{
$user = Auth::loginUsingId(4);
$this->actingAs($user)
->visit('users')
->assertStatus(403);
}

Categories