Laravel Send or Queue Mail depending on config setting - laravel

I am looking for a neat way to send or queue email depending on a config setting.
Right now I am having to do something like this everytime I send an email
$mailContent = new AccountNotification($account);
$mailObject = Mail::to($email);
if(config('app.queueemail')){
$mailObject->queue($mailContent);
} else {
$mailObject->send($mailContent);
}
There has to be a simpler way to do this so I don't have to repeat this code each time I want to send an email.

Extending #ceejayoz's comment, a simpler way could also be to use a global Helper function.
For example, you could have a send_email() global function that will send/queue email depending on your app's configuration.
if ( ! function_exists('send_email')) {
/**
* Sends or queues email
*
* #return mixed
*/
function send_email($mailer, $content)
{
return config('app.queueemail')
? $mailer->queue($content)
: $mailer->send($content);
}
}
To use it you would do:
send_email($mailer, $content);

Related

Laravel 8 Mail Notifications

I'm using Laravel 8, and my Client asks to be able to modify the mailables content.
I need to show the different notification templates, and let the users add text, action buttons, etc.
I'm thinking on building a DB structure to store the different fields with the corresponding order, but I'm not sure if it is possible to apply that on the toMail method.
For example: a NotificationTemplate Model that hasMany NotificationField (this can have type and content).
And then try to use it as a query builder:
/**
* Get the mail representation of the notification.
*
* #param mixed $notifiable
* #return \Illuminate\Notifications\Messages\MailMessage
*/
public function toMail($notifiable)
{
$fields = NotificationTemplate::where('name', 'example')->fields;
$mail = (new MailMessage);
foreach($fields as $field){
if($field->$type = 'line'){
$mail->line($field->content);
}
}
return $mail;
}
Is this possible? Or is there another way to allow the admins of a Laravel 8 app to modify the Mail notificiation message from the frontend?
Thanks, HernĂ¡n.
You can simply give the admin a textarea where he can customize the content of email.
I use this package armincms/option to stock the content and in your template email you can use option()->content

Deleting a File from Storage after Download

I'm working on a media asset management system. I want the user to be able to fill out a form with file width, height, extension and colorspace, then transform the image and serve it back as a download.
I can get that to work by responding to the Post-Request with the URL of the newly created file.
What I want is for that file to be deleted after download or after some time.
(Or, preferably, a way to use laravels download() Response, which I apparently can't use inside an Axios/Ajax post request).
Thanks for your help :)
There are two ways you can do this.
Let's assume you have a file in storage/app/download.zip:
1. Because Laravel uses Symfony's HttpFoundation internally, you can use the deleteFileAfterSend method:
public function download()
{
return response()
->download(storage_path('app/download.zip'))
->deleteFileAfterSend(true);
}
2. Create a Terminable Middleware that deletes the file after the download response was prepared.
class StorageDownload
{
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle($request, Closure $next)
{
return $next($request);
}
public function terminate($request, $response)
{
\Storage::delete('download.zip');
}
}
You'll need to register the middlware and assign it to your route for it to work.
As for triggering the download using JavaScript, something as trivial as setting the window location will work:
axios
.post('files/export/' + file.id, formData)
.then(function() {
window.location = 'files/download/' + file.id
});
Don't worry, this will not navigate away from your current page, it will just trigger the download.
for me , i like to handle this case using cron
you can read details here
add any column to check it's been downloaded or not (e.g status with value 0 or 1)
and you can create accessor on your model to count the date diff
public function getRangeDateAttribute(){
$today = Carbon::now()->startOfDay();
$downloadDate = $this->created_at;
$datediff = $today->diffInDays($downloadDate);
return $datediff
}
then sample code inside task scheduling :
if($media->status == 1 || $media->rangeDate > 7 ){ //checking the status & range date passed specific days you wanted
do whatever you wanted here
}

how to intercept graphql request in api-platform?

i'm using api-platform 2.3.5 and i can't find a way to intercept a graphQl request.
I mean that let's say i'm making a mutation (update) and want to also log the data or send an email. How do i do that ?
I did read the api-platform documentation, but there's very little about their implementation of graphQl. It does quite a lot automagically.
Events are not yet implemented (https://github.com/api-platform/core/pull/2329)
I also found this - https://github.com/api-platform/core/blob/master/src/GraphQl/Resolver/Factory/ItemMutationResolverFactory.php#L101
but i'd rather not touch it. Is there a simpler way ?
I know this is a bit old but if only for reference.
The recommended way with API-Platform's graphql is to use stage. See this documentation.
In the case of the sending an e-mail you can use the serialize stage. See example below for sending email to notify user he has received a message.
<?php
namespace App\Stage;
use ApiPlatform\Core\GraphQl\Resolver\Stage\SerializeStageInterface;
use App\Email\Mailer;
final class SerializeStage implements SerializeStageInterface
{
/**
* #var Mailer
*/
private $mailer;
private $serializeStage;
public function __construct(
SerializeStageInterface $serializeStage,
Mailer $mailer)
{
$this->serializeStage = $serializeStage;
$this->mailer = $mailer;
}
/**
* #param object|iterable|null $itemOrCollection
*/
public function __invoke($itemOrCollection, string $resourceClass, string $operationName, array $context): ?array
{
// Call the decorated serialized stage (this syntax calls the __invoke method).
$serializedObject = ($this->serializeStage)($itemOrCollection, $resourceClass, $operationName, $context);
// send notification e-mail if creating message
if ($resourceClass === 'App\Entity\Message' && $operationName === 'create') {
$this->mailer->sendMessageNotificationEmail($itemOrCollection->getReceiver());
}
return $serializedObject;
}
}
Then we need to decorate the native stage:
/config/services.yaml
App\Stage\SerializeStage:
decorates: api_platform.graphql.resolver.stage.serialize

How can I change SMTP details globally at runtime?

I'm using Laravel 5.5. The nature of the website is a 'multisite' architecture where multiple websites/domains are run from the same codebase.
I've come across an issue when sending email. I need to change the from name and address as well as the transport (SMTP, etc) options depending on which website is being viewed. I have these details stored in a config file.
The easiest way is to just pull those details in the Controller before I call Mail::send/Mail::queue and to update them. However, this brings back 2 issues:
There is a heavy reliance on remembering to actually do that every time I send any email in the code. In short, it's not abiding by DRY.
I'd be forced to use Mail::send instead of Mail::queue, because the queue wouldn't have any idea of the config update from the time it was queued only from when it is processed .
How can I achieve what I am looking to do here in a clean way?
I thought about extending all of my 'Mailable' classes with a custom class that updates the SMTP details, but it doesn't look like you can update the SMTP/Transport information after the class is initiated; you can only update the from name and address.
I managed to find a way to do this.
I had my mailable class (ContactFormMailable) extend a custom class, as follows:
<?php
namespace CustomGlobal\Mail;
use CustomGlobal\Mail\CustomMailable;
use CustomGlobal\ContactForm;
class ContactFormMailable extends CustomMailable
{
public $contact_form;
/**
* Create a new message instance.
*
* #return void
*/
public function __construct(ContactForm $contact_form)
{
$this->contact_form = $contact_form;
}
/**
* Build the message.
*
* #return $this
*/
public function build()
{
$view = $this->get_custom_mail_view('contact_form', $this->contact_form);
return $this->subject('Contact Form Enquiry')
->view($view);
}
}
You'll notice I'm calling get_custom_mail_view. This is in my extended class and used to calculate the view and template I need to use for my mail, depending on the website being viewed. In here I also set the location of my config folder.
<?php
namespace CustomGlobal\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Contracts\Mail\Mailer;
use Illuminate\Queue\SerializesModels;
use Illuminate\Contracts\Queue\ShouldQueue;
use Swift_Mailer;
use Swift_SmtpTransport;
use CustomGlobal\Website;
use CustomGlobal\Territory;
class CustomMailable extends Mailable
{
use Queueable, SerializesModels;
public $layout_view_to_serve;
public $host_folder;
/**
* Override Mailable functionality to support per-user mail settings
*
* #param \Illuminate\Contracts\Mail\Mailer $mailer
* #return void
*/
public function send(Mailer $mailer)
{
app()->call([$this, 'build']);
$config = config($this->host_folder .'.mail');
// Set SMTP details for this host
$host = $config['host'];
$port = $config['port'];
$encryption = $config['encryption'];
$transport = new Swift_SmtpTransport( $host, $port, $encryption );
$transport->setUsername($config['username']);
$transport->setPassword($config['password']);
$mailer->setSwiftMailer(new Swift_Mailer($transport));
$mailer->send($this->buildView(), $this->buildViewData(), function ($message) use($config) {
$message->from([$config['from']['address'] => $config['from']['name']]);
$this->buildFrom($message)
->buildRecipients($message)
->buildSubject($message)
->buildAttachments($message)
->runCallbacks($message);
});
}
/**
* Calculate the template we need to serve.
* $entity can be any object but it must contain a
* $website_id and $territory_id, as that is used
* to calculate the path.
*/
public function get_custom_mail_view($view_filename, $entity)
{
if(empty($view_filename)) {
throw new Exception('The get_custom_mail_view method requires a view to be passed as parameter 1.');
}
if(empty($entity->website_id) || empty($entity->territory_id)) {
throw new Exception('The get_custom_mail_view method must be passed an object containing a website_id and territory_id value.');
}
// Get the website and territory
$website = Website::findOrFail($entity->website_id);
$territory = Territory::findOrFail($entity->territory_id);
$view_to_serve = false;
$layout_view_to_serve = false;
// Be sure to replace . with _, as Laravel doesn't play nice with dots in folder names
$host_folder = str_replace('.', '_', $website->website_domain);
$this->host_folder = $host_folder; // Used for mail config later
/***
Truncated for readability. What's in this area isn't really important to this answer.
***/
$this->layout_view_to_serve = $layout_view_to_serve;
return $view_to_serve;
}
}
It's important to remember that mail can be queued. If you do this is another way, such as setting a config at runtime, then you'll find that the process that runs the queue has no visibility/scope of your runtime config changes, and you'll end up firing out email from your default values.
I found a few answers similar to this one, which helped me out, but none of them worked completely, and some are out-dated (Swift_SmtpTransport is changed considerably since those answers).
Hopefully this helps someone else out.

How do I access non id property of request inside form requests?

I want to access route request parameters inside laravel form requests authorize. I cant find an example describing this.
// Works fine when you want id
dd($this->route('myResourceName'));
// How to do when I want something else???
dd($this->route('anotherAttribute'));
// Above give null probably because it is a resourceful controller
On a side note, I dont understand this design, whats the point?
$this->route('anyAttribute') would be the easiest, right?
Edit: more extensive example
class UpdateSlotAPIRequest extends APIRequest
{
public function __construct(){
parent::__construct();
$this->slot = Slot::find($this->route('slot'));
$this->access_token = $this->route('access_token'); // this is not working!
}
/**
* Determine if the user is authorized to make this request.
*
* #return bool
*/
public function authorize()
{
// If administrator is logged in all is good.
// If slot is free its ok.
// If its not free but you provide good access_token its also fine.
return Auth::check() || $this->slot->isAvailable() || $this->slot->isValidAccessToken($this->access_token);
}
...
```
$access_token = request()->input('access_token');
Found it in https://laravel.com/docs/5.4/helpers

Resources