I'm trying to catch Vonage API errors in my Laravel job file so that I can determine whether to deduct credits from a user's account.
My job consists of a function called attemptSend. When I log the before and after, I can see that the message attempt, first and second logs are all logging, but not the message failed despite there being an exception thrown and logged by Laravel.
What am I missing here to catch this?
Here's my job file:
<?php
namespace App\Jobs;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Facades\Log;
use App\Notifications\Messages\SendMessageViaVonageEngine;
use App\Models\User;
use App\Models\Message;
use App\Models\Sanctum\PersonalAccessToken;
use Carbon\Carbon;
class ProcessMessage implements ShouldQueue
{
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
/**
* The number of times the job may be attempted.
*
* #var int
*/
public $tries = 1;
/**
* The message id
*/
protected $messageId;
/**
* The user id
*/
protected $userId;
/**
* The API key's UUID.
*/
protected $apiKeyUuid;
/**
* Create a new job instance.
*
* #return void
*/
public function __construct($messageId, $userId, $apiKeyUuid)
{
$this->messageId = $messageId;
$this->userId = $userId;
$this->apiKeyUuid = $apiKeyUuid;
}
/**
* Get API key
*/
public function getApiKey()
{
return PersonalAccessToken::where('tokenable_id', $this->userId)
->where('uuid', $this->apiKeyUuid)
->withSum('credit_transactions AS credit_balance', 'delta')
->first();
}
/**
* Get the message
*/
public function getMessage()
{
return Message::where('id', $this->messageId)
->where('user_id', $this->userId)
->first();
}
/**
* Update message status
*/
public function updateMessageStatus($message, $status = 'sending', $reason = null)
{
$message->status = $status;
// set reason
if ($reason) {
$message->reason = $reason;
}
// mark the sent_at date
if (!$message->sent_at && $status == 'sending') {
$message->sent_at = Carbon::now();
}
$message->save();
}
/**
* Attempt to send
*/
public function attemptSend($message)
{
Log::debug('MESSAGE:ATTEMPT');
try {
Log::debug('MESSAGE:SUCCESS-FIRST');
$sent = Notification::route('vonage', $message->send_to)
->notify(new SendMessageViaVonageEngine($message));
Log::debug('MESSAGE:SUCCESS-SECOND', [
'sent' => $sent
]);
} catch (\Exception $e) {
Log::debug('MESSAGE:FAILED');
throw new \Exception($e->getMessage());
}
}
/**
* Execute the job.
*
* #return void
*/
public function handle()
{
$apiKey = $this->getApiKey();
$message = $this->getMessage();
if (! $message) {
throw new \Exception("Message can't be found, was it deleted? Unable to send message.");
return;
}
if (! $apiKey) {
$this->updateMessageStatus($message, 'dead', "API key not found");
throw new \Exception("API key can't be found, was it deleted? Unable to send message.");
return;
}
if (! $apiKey->credit_balance || $apiKey->credit_balance <= 0) {
$this->updateMessageStatus($message, 'dead', "Insufficient credits");
throw new \Exception("API key: $apiKey->uuid has run out of credits. Unable to send message.");
return;
}
$this->updateMessageStatus($message, 'sending', "Attempting to send");
$this->attemptSend($message);
}
}
Related
I need to send confirmation emails on a checkout page. But with the current way I only pass $checkout.
This is the database structure of what data I need to pass:
this is how the $checkout gets passed to the Mailable
// send confirmation email to customer
Mail::to($request->email)->send(new CheckoutConfirmation($checkout));
Mailable:
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
use App\Models\Events;
class CheckoutConfirmation extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct($checkout)
{
$this->checkout = $checkout;
// get the course data from the database
$event = Events::query()
->where('id', '=', $this->checkout->event_id)
->first();
// this needs to be passed, along $checkout
// $event->title;
// $event->start;
// $event->end;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->view('emails.checkout.customer')->subject('Wir freuen uns schon auf Sie! Hier ist die Bestätigung Ihrer Buchung.')->with('checkout', $this->checkout);
}
}
the issue with how it is currently handeled is that I can only call:
$checkout->xxx
but I also need to call $event->xxx in the email blade.
Look at this example
/**
* The user details.
*
* #var $details
*/
public $details;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct($details)
{
$this->details = $details;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->to(data_get($this->details,'email'), env('APP_NAME'))
->replyTo(config('mail.from.address'), ucwords(data_get($this->details, 'name')))
->subject(data_get($this->details, 'subject'))
->view('emails.contact_us');
}
You can use $details;
then use data_get you can put $this->details then get the key whatever email or anything else .
In Controller when called Class Mail in this way
Mail::send(new ContactUsMail($request->validated()));
Based on this StackOverflow answer, I changed my Mailable to the following:
<?php
namespace App\Mail;
use App\Models\Events;
use App\Models\Checkout;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Queue\SerializesModels;
class CheckoutConfirmation extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct($checkout)
{
$this->checkout = Checkout::query()->where('event_id', '=', $checkout->event_id)->first();;
$this->event = Events::query()->where('id', '=', $this->checkout->event_id)->first();
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
return $this->view('emails.checkout.customer')->subject('Wir freuen uns schon auf Sie! Hier ist die Bestätigung Ihrer Buchung.')->with(['checkout'=>$this->checkout, 'event'=>$this->event]);
}
}
now I can pass arrays to the template: ->with(['checkout'=>$this->checkout, 'event'=>$this->event])
this allows to add as much data as needed.
And also please check the code below Laravel Cron Job the compiler is not go to the if condition after sending mail. Its not deleting or update the status when email fails. Please see the code. What should I do for that. After sending mail, if mail fails means i.e Mail::fails function its not to do any think. I want to change the status if mails fails and delete the record if mail send successfully. Please help me
<?php
namespace App\Console\Commands;
use App\Mail\SendEmail;
use App\Models\CronjobSetting;
use App\Models\ProcessingEmail;
use App\Models\SmtpServer;
use Carbon\Carbon;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Mail;
class SendEmailsBasedonDate extends Command
{
/**
* The name and signature of the console command.
*
* #var string
*/
protected $signature = 'sendemailviadate:cron';
/**
* The console command description.
*
* #var string
*/
protected $description = 'Command description';
/**
* Create a new command instance.
*
* #return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* #return int
*/
public function handle()
{
$cronJob = CronjobSetting::whereDate('created_at', Carbon::today())->first();
if ($cronJob->email_group_type == 1) {
$processingEmails = ProcessingEmail::all();
foreach ($processingEmails as $processingEmail) {
$smtpServer = SmtpServer::where('id', $processingEmail->smtp_id)->first();
Config::set('mail.mailers.smtp.host', $smtpServer->hostname);
Config::set('mail.mailers.smtp.port', $smtpServer->port);
Config::set('mail.mailers.smtp.username', $smtpServer->username);
Config::set('mail.mailers.smtp.password', $smtpServer->password);
$email = new SendEmail($processingEmail);
Mail::to($processingEmail->recipient_email)->send($email);
if (Mail::failures()) {
ProcessingEmail::where('id', $processingEmail->id)->update(
['status' => 3]
);
} else {
ProcessingEmail::destroy($processingEmail->id);
}
}
} else {
$processingEmails = ProcessingEmail::where('email_group_id', $cronJob->email_group_id)->get();
foreach ($processingEmails as $processingEmail) {
$smtpServer = SmtpServer::where('id', $processingEmail->smtp_id)->first();
Config::set('mail.mailers.smtp.host', $smtpServer->hostname);
Config::set('mail.mailers.smtp.port', $smtpServer->port);
Config::set('mail.mailers.smtp.username', $smtpServer->username);
Config::set('mail.mailers.smtp.password', $smtpServer->password);
$email = new SendEmail($processingEmail);
Mail::to($processingEmail->recipient_email)->send($email);
if (Mail::failures()) {
ProcessingEmail::where('email_leads_id', $processingEmail->email_lead_id)->update(
['status' => 3]
);
} else {
ProcessingEmail::where('email_leads_id', $processingEmail->email_lead_id)->delete();
}
}
return Command::SUCCESS;
}
}
}
i have a form in laravel which sends the data to email and database, i am trying to store the image from form in public folder, then the following error happened
Unable to open file for reading [/hermes/walnaweb08a/b1307/as.book/TEIA/storage/app/public/1562913994bannernew.jpg]
the error is occured in the following code for swiftmailer
private function getReadHandle()
{
if (!isset($this->reader)) {
$pointer = #fopen($this->path, 'rb');
if (!$pointer) {
throw new Swift_IoException('Unable to open file for reading ['.$this->path.']');
}
$this->reader = $pointer;
if (0 != $this->offset) {
$this->getReadStreamSeekableStatus();
$this->seekReadStreamToPosition($this->offset);
}
}
return $this->reader;
}
this is my sendmail.php
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use App\Register;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
class SendEmail extends Mailable
{
use Queueable, SerializesModels;
/**
* Create a new message instance.
*
* #return void
*/
public $sub;
public $msg;
public $phot;
public $rec;
public $sig;
public function __construct($subject,$message,$photo,$recipt,$sign)
{
$this->sub = $subject;
$this->msg = $message;
$this->phot = $photo;
$this->rec = $recipt;
$this->sig = $sign;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
$b_subject = $this->sub;
$b_message = $this->msg;
$reg = Register::where('id',$b_message)->first();
$b_phot = $this->phot;
$b_phot = $this->phot;
$recipt = $this->rec;
$sign = $this->sig;
return $this->view('mail.sendmail', compact("reg","b_phot","sign","recipt"))->subject($b_subject);
}
}
the image is stored in the folder and data is sent to database, but the problem is this error occurs and the data is not sent to mail. can anyone please tell what coul be the problem
I am using Laravel 5.3 on homestead. Here's the code that I am using for broadcasting an event through pusher and laravel echo, to notify the users of a new message in real time -
class NewMessageReceived implements ShouldBroadcast
{
use InteractsWithSockets, SerializesModels;
public $message;
public $user;
public $sender;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(Message $message, User $user)
{
$this->message = $message;
$this->user = $user;
$this->sender = \Auth::user();
}
/**
* Get the channels the event should broadcast on.
*
* #return Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('user.'. $this->user->id);
}
}
Here's the code that calls this event-
try{
$m = $privateChat->messages()->save($m);
if( isset(Auth::user()->guide))
event ( new NewMessageReceived( $m, $m->messageable->tourist->user ));
else
event ( new NewMessageReceived( $m, $m->messageable->guide->user ));
return $m;
}
catch(Exception $e){
return $e;
}
There's an ajax call in vue, which receives the response generated by above code i.e $m .
The problem is if I use the code below instead of the above code, response is recieved at least 5 times faster. I just need to remove the event-firing part to make it run faster (which is not desired, since I want to update user in real time)-
try{
$m = $privateChat->messages()->save($m);
return $m;
}
catch(Exception $e){
return $e;
}
It would be helpful if you could help me spot the reason behind this behaviour and how can I make it all happen in real time, instead of the delay existing now.
You should change ShouldBroadcast to ShouldBroadcastNow. That will solve the problem with delay.
Try this:
Move your if logic to event Class
class NewMessageReceived implements ShouldBroadcast
{
use InteractsWithSockets, SerializesModels;
public $message;
public $user;
public $sender;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(Message $message)
{
$this->message = $message;
$this->user = isset(Auth::user()->guide)) ? $message->messageable->tourist->user : $this->user = $message->messageable->guide->user;
$this->sender = \Auth::user();
}
/**
* Get the channels the event should broadcast on.
*
* #return Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('user.'. $this->user->id);
}
}
Now your event call.
$m = $privateChat->messages()->save($m);
event(new NewMessageReceived($m));
return $m;
I need to add Captcha to my login page, I am using GregwarCaptchaBundle and FosUserBundle.
For the moment I have get show the captcha on the login using the following code:
<?php
/*
* This file is part of the FOSUserBundle package.
*
* (c) FriendsOfSymfony <http://friendsofsymfony.github.com/>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace FOS\UserBundle\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Security;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Gregwar\Captcha\CaptchaBuilder;
class SecurityController extends Controller
{
public function loginAction(Request $request)
{
$builtCaptcha = new CaptchaBuilder();
$builtCaptcha->build();
$builtCaptcha->save('captcha.jpg');
/** #var $session \Symfony\Component\HttpFoundation\Session\Session */
$session = $request->getSession();
if (class_exists('\Symfony\Component\Security\Core\Security')) {
$authErrorKey = Security::AUTHENTICATION_ERROR;
$lastUsernameKey = Security::LAST_USERNAME;
} else {
// BC for SF < 2.6
$authErrorKey = SecurityContextInterface::AUTHENTICATION_ERROR;
$lastUsernameKey = SecurityContextInterface::LAST_USERNAME;
}
// get the error if any (works with forward and redirect -- see below)
if ($request->attributes->has($authErrorKey)) {
$error = $request->attributes->get($authErrorKey);
} elseif (null !== $session && $session->has($authErrorKey)) {
$error = $session->get($authErrorKey);
$session->remove($authErrorKey);
} else {
$error = null;
}
if (!$error instanceof AuthenticationException) {
$error = null; // The value does not come from the security component.
}
// last username entered by the user
$lastUsername = (null === $session) ? '' : $session->get($lastUsernameKey);
if ($this->has('security.csrf.token_manager')) {
$csrfToken = $this->get('security.csrf.token_manager')->getToken('authenticate')->getValue();
} else {
// BC for SF < 2.4
$csrfToken = $this->has('form.csrf_provider')
? $this->get('form.csrf_provider')->generateCsrfToken('authenticate')
: null;
}
$t = $request->get('_captcha');
if($t!=$builtCaptcha){
echo 'error';
}
var_dump($t);
return $this->renderLogin(array(
'last_username' => $lastUsername,
'error' => $error,
'csrf_token' => $csrfToken,
'captcha' => $builtCaptcha,
));
}
/**
* Renders the login template with the given parameters. Overwrite this function in
* an extended controller to provide additional data for the login template.
*
* #param array $data
*
* #return \Symfony\Component\HttpFoundation\Response
*/
protected function renderLogin(array $data)
{
return $this->render('FOSUserBundle:Security:login.html.twig', $data);
}
public function checkAction($builtCaptcha)
{
return $this->redirect($this->generateUrl('fos_user_login'));
}
throw new \RuntimeException('You must configure the check path to be handled by the firewall using form_login in your security firewall configuration.');
}
public function logoutAction()
{
throw new \RuntimeException('You must activate the logout in your security firewall configuration.');
}
}
And I overrided the login and register template as the documentation explain (the registration is working fine with the captcha)
The problem is that I don't know how to validate the captcha's code.
I guess that I should do it into the checkAction()
But I am not sure, I am very caught with this problem.
If someone could help me I would be very grateful, Thanks in advance.
I had the same problem when trying to implement Google ReCaptcha into a simple login-form with database provider. This is a working solution based on a listener triggered by SecurityEvents::INTERACTIVE_LOGIN. This event is fired after verification of credentials but before redirecting to default_target_path defined in security.yml.
Note: The example is mixed with some other functionality, because I am also capturing failed login attempts.
<?php
namespace ExampleBundle\EventListener;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Authentication\Token\AnonymousToken;
use Symfony\Component\Security\Core\AuthenticationEvents;
use Symfony\Component\Security\Core\Event\AuthenticationFailureEvent;
use Symfony\Component\Security\Core\Event\AuthenticationEvent;
use Symfony\Component\Security\Core\Exception\BadCredentialsException;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;
use Symfony\Component\Security\Http\SecurityEvents;
use ExampleBundle\Google\GoogleReCaptcha;
use Doctrine\ORM\EntityManager;
use ExampleBundle\Entity\User;
/**
* Class AuthenticationAttemptListener
* #package ExampleBundle\EventListener
*/
class AuthenticationAttemptListener implements EventSubscriberInterface
{
/**
* #var EntityManager
*/
private $entityManager;
/**
* #var GoogleReCaptcha
*/
private $googleReCaptcha;
/**
* #var integer
*/
private $maxFailedLoginAttempts;
/**
* AuthenticationAttemptListener constructor.
*
* #param EntityManager $entityManager
* #param GoogleReCaptcha $googleReCaptcha
* #param integer $maxFailedLoginAttempts
*/
public function __construct(EntityManager $entityManager, GoogleReCaptcha $googleReCaptcha, $maxFailedLoginAttempts)
{
$this->entityManager = $entityManager;
$this->googleReCaptcha = $googleReCaptcha;
$this->maxFailedLoginAttempts = $maxFailedLoginAttempts;
}
/**
* #return array
*/
public static function getSubscribedEvents()
{
return array(
AuthenticationEvents::AUTHENTICATION_FAILURE => 'onAuthenticationFailure',
AuthenticationEvents::AUTHENTICATION_SUCCESS => 'onAuthenticationSuccess',
SecurityEvents::INTERACTIVE_LOGIN => 'onInteractiveLogin'
);
}
/**
* Count failed login attempts and save to database on existing usernames
*
* #param AuthenticationFailureEvent $event
*/
public function onAuthenticationFailure(AuthenticationFailureEvent $event)
{
if ($event->getAuthenticationException() instanceof BadCredentialsException) {
$databaseUser = $this->searchUserinDatabase($event);
// increase failed attempt counter or lock-up account
if ($databaseUser !== null) {
if (!$databaseUser->isEnabled()) {
throw new CustomUserMessageAuthenticationException('user_deactivated');
} else {
$databaseUser->increaseFailedLoginAttempts();
$databaseUser->setFailedLoginLastTimestamp(new \DateTime());
if ($databaseUser->getFailedLoginAttempts() == $this->maxFailedLoginAttempts) {
$databaseUser->setIsActive(0);
}
$this->entityManager->persist($databaseUser);
$this->entityManager->flush();
}
}
}
}
/**
* #param AuthenticationSuccessEvent $event
*/
public function onAuthenticationSuccess(AuthenticationEvent $event)
{
// Attention: Event will be thrown on every request if you have session-based authentication!
// Reset of attempt counter may occur if user is logged in while brute force attack is running
}
/**
* Check incoming google recaptcha token and reset attempt-counter on success or throw exception
*
* #param InteractiveLoginEvent $event
*/
public function onInteractiveLogin(InteractiveLoginEvent $event)
{
$reCaptchaResponse = $event->getRequest()->get('g-recaptcha-response');
$captchaRequest = $this->googleReCaptcha->checkReCaptcha($reCaptchaResponse);
if (is_array($captchaRequest) && $captchaRequest['success'] === true) {
// reset attempt counter because of successful login and positive recaptcha response
$databaseUser = $this->searchUserinDatabase($event);
if ($databaseUser !== null && $databaseUser->isEnabled()) {
$databaseUser->setFailedLoginAttempts(null);
$databaseUser->setFailedLoginLastTimestamp(null);
$this->entityManager->persist($databaseUser);
$this->entityManager->flush();
}
} else {
// on all other recaptcha related errors throw exception
throw new CustomUserMessageAuthenticationException('recaptcha_error');
}
}
/**
* Retrieve user from database
*
* #param AuthenticationFailureEvent|AuthenticationEvent $event
*
* #return User|null
*/
private function searchUserinDatabase($event)
{
$token = $event->getAuthenticationToken();
$username = $token->getUsername();
$databaseUser = null;
if (!$token instanceof AnonymousToken) {
// get user from database
$databaseUser = $this->entityManager->getRepository($entity)->findOneBy(array(
"username" => $username
));
}
return $databaseUser;
}
}
Hope that helps ...