email support

This commit is contained in:
Simon Pocrnjič
2025-10-11 17:20:05 +02:00
parent 7c7defb6c5
commit 1b615163be
23 changed files with 3183 additions and 28 deletions
+86
View File
@@ -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);
}
}
+92
View File
@@ -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;
}
}