email support
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use Illuminate\Support\Facades\File;
|
||||
|
||||
class EmailImageInliner
|
||||
{
|
||||
/**
|
||||
* Replace <img src="/storage/..."> with base64 data URIs using files from storage/app/public.
|
||||
* Only affects local public storage images; external URLs and existing data URIs are left intact.
|
||||
*/
|
||||
public function inline(string $html): string
|
||||
{
|
||||
if ($html === '' || stripos($html, '<img') === false) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
return preg_replace_callback('#<img([^>]+)src=[\"\']([^\"\']+)[\"\']([^>]*)>#i', function (array $m): string {
|
||||
$before = $m[1] ?? '';
|
||||
$src = $m[2] ?? '';
|
||||
$after = $m[3] ?? '';
|
||||
|
||||
// Skip if already data URI or external
|
||||
if (stripos($src, 'data:') === 0) {
|
||||
return $m[0];
|
||||
}
|
||||
|
||||
// Accept either relative (/storage/...) OR absolute URLs whose path begins with /storage/
|
||||
$path = $src;
|
||||
if (preg_match('#^https?://#i', $src)) {
|
||||
$parts = parse_url($src);
|
||||
$path = $parts['path'] ?? '';
|
||||
}
|
||||
if (! preg_match('#^/?storage/#i', (string) $path)) {
|
||||
return $m[0];
|
||||
}
|
||||
|
||||
$rel = ltrim(preg_replace('#^/?storage/#i', '', (string) $path), '/');
|
||||
$full = storage_path('app/public/'.$rel);
|
||||
if (! File::exists($full)) {
|
||||
return $m[0];
|
||||
}
|
||||
|
||||
// Determine mime type
|
||||
$mime = null;
|
||||
try {
|
||||
$mime = File::mimeType($full);
|
||||
} catch (\Throwable) {
|
||||
$mime = null;
|
||||
}
|
||||
if ($mime === null) {
|
||||
$ext = strtolower(pathinfo($full, PATHINFO_EXTENSION));
|
||||
$map = [
|
||||
'jpg' => 'image/jpeg',
|
||||
'jpeg' => 'image/jpeg',
|
||||
'png' => 'image/png',
|
||||
'gif' => 'image/gif',
|
||||
'svg' => 'image/svg+xml',
|
||||
'webp' => 'image/webp',
|
||||
];
|
||||
$mime = $map[$ext] ?? 'application/octet-stream';
|
||||
}
|
||||
|
||||
// Cap size to avoid huge emails (e.g., 5 MB)
|
||||
$max = 5 * 1024 * 1024;
|
||||
try {
|
||||
if (File::size($full) > $max) {
|
||||
return $m[0];
|
||||
}
|
||||
} catch (\Throwable) {
|
||||
// ignore size errors
|
||||
}
|
||||
|
||||
try {
|
||||
$data = base64_encode(File::get($full));
|
||||
} catch (\Throwable) {
|
||||
return $m[0];
|
||||
}
|
||||
|
||||
$dataUri = 'data:'.$mime.';base64,'.$data;
|
||||
|
||||
return '<img'.$before.'src="'.$dataUri.'"'.$after.'>';
|
||||
}, $html);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientCase;
|
||||
use App\Models\Contract;
|
||||
use App\Models\Person\Person;
|
||||
|
||||
class EmailTemplateRenderer
|
||||
{
|
||||
/**
|
||||
* Render subject and bodies using a simple {{ key }} replacement.
|
||||
* Supported entities: client, person, client_case, contract
|
||||
*
|
||||
* @param array{subject:string, html?:string|null, text?:string|null} $template
|
||||
* @param array{client?:Client, person?:Person, client_case?:ClientCase, contract?:Contract, extra?:array} $ctx
|
||||
* @return array{subject:string, html?:string, text?:string}
|
||||
*/
|
||||
public function render(array $template, array $ctx): array
|
||||
{
|
||||
$map = $this->buildMap($ctx);
|
||||
$replacer = static function (?string $input) use ($map): ?string {
|
||||
if ($input === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return preg_replace_callback('/{{\s*([a-zA-Z0-9_.]+)\s*}}/', function ($m) use ($map) {
|
||||
$key = $m[1];
|
||||
|
||||
return (string) data_get($map, $key, '');
|
||||
}, $input);
|
||||
};
|
||||
|
||||
return [
|
||||
'subject' => $replacer($template['subject']) ?? '',
|
||||
'html' => $replacer($template['html'] ?? null) ?? null,
|
||||
'text' => $replacer($template['text'] ?? null) ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array{client?:Client, person?:Person, client_case?:ClientCase, contract?:Contract, extra?:array} $ctx
|
||||
*/
|
||||
protected function buildMap(array $ctx): array
|
||||
{
|
||||
$out = [];
|
||||
if (isset($ctx['client'])) {
|
||||
$c = $ctx['client'];
|
||||
$out['client'] = [
|
||||
'id' => data_get($c, 'id'),
|
||||
'uuid' => data_get($c, 'uuid'),
|
||||
];
|
||||
}
|
||||
if (isset($ctx['person'])) {
|
||||
$p = $ctx['person'];
|
||||
$out['person'] = [
|
||||
'first_name' => data_get($p, 'first_name'),
|
||||
'last_name' => data_get($p, 'last_name'),
|
||||
'full_name' => trim((data_get($p, 'first_name', '')).' '.(data_get($p, 'last_name', ''))),
|
||||
'email' => data_get($p, 'email'),
|
||||
'phone' => data_get($p, 'phone'),
|
||||
];
|
||||
}
|
||||
if (isset($ctx['client_case'])) {
|
||||
$c = $ctx['client_case'];
|
||||
$out['case'] = [
|
||||
'id' => data_get($c, 'id'),
|
||||
'uuid' => data_get($c, 'uuid'),
|
||||
'reference' => data_get($c, 'reference'),
|
||||
];
|
||||
}
|
||||
if (isset($ctx['contract'])) {
|
||||
$co = $ctx['contract'];
|
||||
$out['contract'] = [
|
||||
'id' => data_get($co, 'id'),
|
||||
'uuid' => data_get($co, 'uuid'),
|
||||
'reference' => data_get($co, 'reference'),
|
||||
'amount' => data_get($co, 'amount'),
|
||||
];
|
||||
$meta = data_get($co, 'meta');
|
||||
if (is_array($meta)) {
|
||||
$out['contract']['meta'] = $meta;
|
||||
}
|
||||
}
|
||||
if (! empty($ctx['extra']) && is_array($ctx['extra'])) {
|
||||
$out['extra'] = $ctx['extra'];
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user