Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 246
0.00% covered (danger)
0.00%
0 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
ResendEmails
0.00% covered (danger)
0.00%
0 / 246
0.00% covered (danger)
0.00%
0 / 4
3192
0.00% covered (danger)
0.00%
0 / 1
 handle
0.00% covered (danger)
0.00%
0 / 145
0.00% covered (danger)
0.00%
0 / 1
1332
 isEmailValid
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getEmailBody
0.00% covered (danger)
0.00%
0 / 78
0.00% covered (danger)
0.00%
0 / 1
132
 calculateEmailRequestSize
0.00% covered (danger)
0.00%
0 / 19
0.00% covered (danger)
0.00%
0 / 1
56
1<?php
2
3namespace App\Console\Commands;
4
5use App\Models\TblBlockedDomains;
6use App\Models\TblCcBcc;
7use App\Models\TblCompanyEmails;
8use App\Models\TblEmailConfiguration;
9use App\Models\TblEmailFiles;
10use App\Models\TblFiles;
11use App\Models\TblQuotations;
12use App\Models\TblResendEmails;
13use App\Models\TblSendgridWebhook;
14use Illuminate\Console\Command;
15use Illuminate\Support\Facades\DB;
16use Illuminate\Support\Facades\Log;
17use Illuminate\Support\Facades\Storage;
18use SendGrid\Mail\Mail;
19
20class ResendEmails extends Command
21{
22    /**
23     * The name and signature of the console command.
24     *
25     * @var string
26     */
27    protected $signature = 'command:resend-emails {id?} {type?}';
28
29    private ?\SendGrid\Mail\Mail $email = null;
30
31    /**
32     * The console command description.
33     *
34     * @var string
35     */
36    protected $description = 'Command description';
37
38    /**
39     * Execute the console command.
40     */
41    public function handle(): void
42    {
43
44        try {
45
46            $this->email = new \SendGrid\Mail\Mail;
47
48            $id = $this->argument('id') ?? null;
49            $typeId = (int) ($this->argument('type') ?? 0);
50
51            $type = 'followUps';
52
53            if($typeId == 1){
54                $type = "sendToClient";
55            }
56
57            if ($id !== null) {
58
59                $result = TblQuotations::where('id', $id)->first();
60
61                $emailTemplate = TblEmailConfiguration::where('type', $type)->where('is_default', 1)->get()->keyBy('company_id');
62
63                if ($result) {
64
65                    $budgetTypeId = $result->budget_type_id;
66                    $quoteId = $result->id;
67                    $commercialUser = $result->commercial;
68                    $companyId = $result->company_id;
69
70                    $emailContent = $this->getEmailBody(
71                        $emailTemplate[$companyId]->getAttribute('html'),
72                        $emailTemplate[$companyId]->getAttribute('subject'),
73                        $emailTemplate[$companyId]->getAttribute('email_template_id'),
74                        $result
75                    );
76
77                    $html = $emailContent[0];
78                    $subject = $emailContent[1];
79
80                    $blockedDomains = TblBlockedDomains::where('company_id', $result->company_id)->pluck('domain')->toArray();
81
82                    if(config('services.sendgrid.staging')){
83                        $toEmail = "christian@ibventur.es";
84                    }else{
85                        $toEmail = $result->email;  
86                    }
87
88                    if ($toEmail != null) {
89
90                        $toEmail = explode(",", (string) $toEmail);
91                        $toEmail = array_map(trim(...), $toEmail);
92
93                        $companyEmail = null;
94
95                        $queryUsers = "SELECT 
96                                            sender_email AS from_email, 
97                                            `name` AS from_name 
98                                        FROM tbl_users
99                                        WHERE 
100                                            sender_enabled = 1 
101                                        AND response_id IS NOT NULL 
102                                        AND verified = 1 
103                                        AND `name` = '{$commercialUser}'";
104
105                        $commercialEmail = DB::select($queryUsers);
106
107                        if (count($commercialEmail) > 0) {
108                            $companyEmail = $commercialEmail[0];
109                        } else {
110                            if ($emailTemplate[$companyId]->getAttribute('from_id') != null) {
111                                $companyEmail = TblCompanyEmails::where('id', $emailTemplate[$companyId]->getAttribute('from_id'))->first();
112                            } else {
113                                $companyEmail = TblCompanyEmails::where('is_active', 1)->where('verified', 1)->where('company_id', $result->company_id)->first();
114                            }
115                        }
116
117                        if (! $companyEmail) {
118                            $error = __('language.no_active_verified_sender');
119                            Log::channel('resend_emails')->error("ID:{$quoteId}{$error}");
120                        }
121
122                        $ccBcc = TblCcBcc::where('company_id', $result->company_id)->get();
123
124                        $this->email->setFrom($companyEmail->from_email, $companyEmail->from_name);
125                        $this->email->setSubject($subject);
126
127                        $s = 0;
128                        $addTo = [];
129                        foreach ($toEmail as $clientEmail) {
130                            $isValid = $this->isEmailValid($clientEmail);
131
132                            if ($isValid) {
133                                $domain = substr($clientEmail, strpos($clientEmail, '@') + 1);
134
135                                if (! in_array($domain, $blockedDomains)) {
136                                    $this->email->addTo($clientEmail);
137                                    array_push($addTo, $clientEmail);
138                                    $s++;
139                                }
140                            } else {
141                                Log::channel('resend_emails')->error("ID:{$quoteId}: Invalid email {$clientEmail}");
142                            }
143                        }
144
145                        if ($s == 0) {
146                            $toEmail = json_encode($toEmail);
147                            Log::channel('resend_emails')->error("[NO-VALID-EMAILS:{$s}] ID:{$quoteId}{$toEmail}}");
148                        }
149
150                        if (!config('services.sendgrid.staging')) {
151                            if(count($ccBcc) > 0){
152                                foreach ($ccBcc as $data) {
153                                    if (! in_array($data->email, $toEmail)) {
154                                        $this->email->addBcc($data->email);
155                                    }
156                                }
157                            }
158                        }
159
160                        $this->email->addContent('text/html', $html);
161
162                        $sendgrid = new \SendGrid(config('services.sendgrid.api_key'));
163
164                        $files = TblFiles::where('quotation_id', $result->id)->where('is_internal', null)->get();
165                        $requestSize = $this->calculateEmailRequestSize($this->email);
166
167                        foreach ($files as $key => $value) {
168                            if ($files[$key]->filename && Storage::disk('s3')->exists("uploads/" . $files[$key]->filename)) {
169                                                            
170                                $fileContent = Storage::disk('s3')->get("uploads/" . $files[$key]->filename);
171                                $fileSize = strlen((string) $fileContent) / 1048576;
172                                $fileSize = (int) ceil($fileSize);
173
174                                $originalName = $files[$key]->original_name ?: basename((string) $files[$key]->filename);
175                            }else if ($files[$key]->file) {
176                                $fileContent = $files[$key]->file;
177
178                                if (is_string($fileContent) && base64_decode($fileContent, true)) {
179                                    $fileContent = base64_decode($fileContent);
180                                }
181
182                                $fileSize = strlen((string) $fileContent) / 1048576;
183                                $fileSize = (int) ceil($fileSize);
184
185                                $originalName = $files[$key]->original_name ?: ($files[$key]->getAttribute('file_name') ?: 'file');
186                            } else {
187                                continue;
188                            }
189
190                            if ($fileSize > 0) {
191                                if ($requestSize + $fileSize < 25) {
192                                    $attachment = new \SendGrid\Mail\Attachment;
193                                    $attachment->setFilename($originalName);
194                                    $attachment->setDisposition('attachment');
195
196                                    $attachment->setContent(base64_encode((string) $fileContent));
197
198                                    if ($files[$key]->mime_type) {
199                                        $attachment->setType($files[$key]->mime_type);
200                                    }
201
202                                    $this->email->addAttachment($attachment);
203                                    $requestSize = $this->calculateEmailRequestSize($this->email);
204                                } else {
205                                    Log::channel('resend_emails')->error('File omitted due to size limit: '.$originalName);
206                                }
207                            }
208                        }
209
210                        $sentWithBackup = false;
211                        $response = $sendgrid->send($this->email);
212
213                        if ($response->statusCode() == 202) {
214
215                            $messageId = null;
216
217                            foreach ($response->headers() as $header) {
218                                if (str_starts_with(strtolower((string) $header), 'x-message-id:')) {
219                                    $messageId = trim(substr((string) $header, strpos((string) $header, ':') + 1));
220                                    break;
221                                }
222                            }
223
224                            $typeArray = [
225                                'y_message_id' => $messageId,
226                                'y_status' => 'Processing'
227                            ];
228
229                            if($type == "sendToClient"){
230                                $typeArray = [
231                                    'x_message_id' => $messageId,
232                                    'x_status' => 'Processing'
233                                ];
234                            }
235
236                            TblQuotations::where('id', $result->id)->update($typeArray);
237
238                            $jsonBody = [];
239
240                            foreach ($toEmail as $clientEmail) {
241                                $isValid = $this->isEmailValid($clientEmail);
242                                $eventStatus = 'processed';
243                                $eventResponse = '';
244                                if (! $isValid) {
245                                    $eventStatus = 'invalid';
246                                    $eventResponse = 'Invalid email address';
247                                }
248
249                                array_push(
250                                    $jsonBody,
251                                    [
252                                        'email' =>  $clientEmail,
253                                        'event' => $eventStatus,
254                                        'sg_message_id' => $messageId,
255                                        'smtp-id' => $messageId,
256                                        'timestamp' => strtotime(date('Y-m-d H:i:s')),
257                                        'response' => $eventResponse
258                                    ]
259                                );
260                            }
261
262                            TblSendgridWebhook::create(
263                                [
264                                    'quotation_id' => $result->id,
265                                    'type' => $type,
266                                    'json_body' => json_encode($jsonBody),
267                                    'x_message_id' => $messageId
268                                ]
269                            );
270
271                            TblResendEmails::where('quotation_id', $quoteId)->where('type', $typeId)->delete();
272
273                            $toEmail = json_encode($toEmail);
274                            Log::channel('resend_emails')->error("[SENT-OK] ID:{$quoteId}{$toEmail}}");
275                        } else {
276                            Log::channel('resend_emails')->error('[ERROR-SG] ID:'.$result->id.' - '.$response->body());
277                        }
278                    }
279                } else {
280                    Log::channel('resend_emails')->error('[ERROR-ID] ID does not exist');
281                }
282            } else {
283                Log::channel('resend_emails')->error('[ERROR-ID] Missing ID');
284            }
285
286        } catch (\Exception $e) {
287            Log::channel('resend_emails')->error($e);
288        }
289    }
290    
291    function isEmailValid($email): bool {
292        // Regular expression pattern for email validation
293        $pattern = '/^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$/';
294
295        // Check if the email matches the pattern
296        if (preg_match($pattern, (string) $email)) {
297            return true; // Valid email
298        } else {
299            return false; // Invalid email
300        }
301    }
302
303
304    function getEmailBody($body, $subject, $emailTemplateId, $result): array{
305
306        $availableParameters = [
307            'quote_id',
308            'company_id',
309            'client',
310            'client_type',
311            'phone_number',
312            'email',
313            'issue_date',
314            'request_date',
315            'duration',
316            'invoice_number',
317            'type',
318            'acceptance_date',
319            'status',
320            'source',
321            'amount',
322            'reason_for_not_following_up',
323            'last_follow_up_date',
324            'last_follow_up_comment',
325            'reason_for_rejection_id',
326            'reason_for_rejection',
327            'commercial',
328            'created_at',
329            'created_by',
330            'updated_at',
331            'updated_by',
332        ];
333
334        $dateParameters = [
335            'issue_date',
336            'request_date',
337            'acceptance_date',
338            'last_follow_up_date',
339            'created_at',
340            'updated_at',
341        ];
342
343        preg_match_all('/{{(.*?)}}/', (string) $body, $matches);
344
345        $parameters = $matches[1];
346
347        foreach ($parameters as $parameter) {
348
349            if(in_array($parameter, $dateParameters)){
350                if($result->{$parameter}){
351                    $result->{$parameter} = iconv('ISO-8859-2', 'UTF-8', strftime("%A, %B %d, %Y", strtotime((string) $result->{$parameter})));
352                }
353            }
354
355            if (in_array($parameter, $availableParameters)) {
356                $body = str_replace('{{'.$parameter.'}}', $result->{$parameter}, $body);
357            }
358        }
359
360        preg_match_all('/{{(.*?)}}/', (string) $subject, $matches);
361
362        $parameters = $matches[1];
363
364        foreach ($parameters as $parameter) {
365
366            if(in_array($parameter, $dateParameters)){
367                if($result->{$parameter}){
368                    $result->{$parameter} = iconv('ISO-8859-2', 'UTF-8', strftime("%A, %B %d, %Y", strtotime((string) $result->{$parameter})));
369                }
370            }
371
372            if (in_array($parameter, $availableParameters)) {
373                $subject = str_replace('{{'.$parameter.'}}', $result->{$parameter}, $subject);
374            }
375        }
376
377        $templateFiles = TblEmailFiles::where('email_template_id', $emailTemplateId)->orderBy('order', 'asc')->get();
378
379        foreach ($templateFiles as $item) {
380            $f = storage_path('app/public/uploads/'.$item->filename);
381
382            if (file_exists($f)) {
383                $imgpath = file_get_contents($f);
384                $base64 = 'data:image/png;base64,'.base64_encode($imgpath);
385                $mimeType = mime_content_type($f);
386
387                $this->email->addAttachment(
388                    $imgpath,
389                    $mimeType,
390                    str_replace(' ', '', $item->original_name),
391                    'inline',
392                    str_replace(' ', '', $item->original_name),
393                );
394
395                $body .= "<img src='cid:{$item->original_name}' style='height: 45px; padding-right: 6px' />";
396            } else {
397                Log::channel('resend_emails')->error("[{$result->id}] File not found: ".$f);
398            }
399        }
400
401        $html = '<!DOCTYPE html>';
402        $html .= '<html>';
403        $html .= '<head>';
404        $html .= '<meta charset="UTF-8">';
405        $html .= '<meta name="viewport" content="width=device-width, initial-scale=1.0">';
406        $html .= '</head>';
407        $html .= '<body>';
408        $html .= $body;
409        $html .= '</body>';
410        $html .= '</html>';
411
412        return [$html, $subject];
413    }
414
415    function calculateEmailRequestSize(Mail $email): int{
416
417        $size = 0;
418
419        // Add size of 'from', 'to', 'subject', 'content'
420        $from = $email->getFrom();
421        $size += strlen(json_encode([
422            'from' => $from->getEmail().' '.$from->getName(),
423            'subject' => $email->getSubject(),
424        ]));
425
426        // Add size of 'to' (recipients)
427        $personalizations = $email->getPersonalizations() ?? [];
428        foreach ($personalizations as $personalization) {
429            foreach ($personalization->getTos() as $to) {
430                $size += strlen($to->getEmail().' '.$to->getName());
431            }
432        }
433
434        // Add size of content
435        foreach ($email->getContents() as $content) {
436            $size += strlen((string) $content->getValue());
437        }
438
439        // Add size of attachments (if any)
440
441        if ($email->getAttachments() != null && $email->getAttachments() != '') {
442            foreach ($email->getAttachments() as $attachment) {
443                $size += strlen($attachment->getContent()); // Base64 encoded size
444                $size += strlen($attachment->getFilename());
445                $size += strlen($attachment->getType());
446            }
447        }
448
449        $sizeInMegabytes = $size / 1048576; // 1 MB = 1,048,576 bytes
450
451        return (int) ceil($sizeInMegabytes);
452    }
453}