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, activity?:Activity, extra?:array} $ctx */ protected function buildMap(array $ctx): array { $formatDateEu = static function ($value): string { if ($value === null || $value === '') { return ''; } try { if ($value instanceof \DateTimeInterface) { return Carbon::instance($value)->format('d.m.Y'); } // Accept common formats (Y-m-d, Y-m-d H:i:s, etc.) return Carbon::parse((string) $value)->format('d.m.Y'); } catch (\Throwable $e) { return (string) $value; } }; $formatMoneyEu = static function ($value): string { if ($value === null || $value === '') { return ''; } $num = null; if (is_numeric($value)) { $num = (float) $value; } elseif (is_string($value)) { // Try to normalize string numbers like "1,234.56" or "1.234,56" $normalized = str_replace([' ', '\u{00A0}'], '', $value); $normalized = str_replace(['.', ','], ['.', '.'], $normalized); $num = is_numeric($normalized) ? (float) $normalized : null; } if ($num === null) { return (string) $value; } return number_format($num, 2, ',', '.').' €'; }; $out = []; if (isset($ctx['client'])) { $c = $ctx['client']; $out['client'] = [ 'id' => data_get($c, 'id'), 'uuid' => data_get($c, 'uuid'), // Expose nested person for {{ client.person.full_name }} etc. 'person' => [ 'first_name' => data_get($c, 'person.first_name'), 'last_name' => data_get($c, 'person.last_name'), 'full_name' => (function ($c) { $fn = (string) data_get($c, 'person.first_name', ''); $ln = (string) data_get($c, 'person.last_name', ''); $stored = data_get($c, 'person.full_name'); return (string) ($stored ?: trim(trim($fn.' '.$ln))); })($c), 'email' => data_get($c, 'person.email'), 'phone' => data_get($c, 'person.phone'), ], ]; } 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'), // Expose nested person for {{ case.person.full_name }}; prefer direct relation, fallback to client.person 'person' => [ 'first_name' => data_get($c, 'person.first_name') ?? data_get($c, 'client.person.first_name'), 'last_name' => data_get($c, 'person.last_name') ?? data_get($c, 'client.person.last_name'), 'full_name' => (function ($c) { $stored = data_get($c, 'person.full_name') ?? data_get($c, 'client.person.full_name'); if ($stored) { return (string) $stored; } $fn = (string) (data_get($c, 'person.first_name') ?? data_get($c, 'client.person.first_name') ?? ''); $ln = (string) (data_get($c, 'person.last_name') ?? data_get($c, 'client.person.last_name') ?? ''); return trim(trim($fn.' '.$ln)); })($c), 'email' => data_get($c, 'person.email') ?? data_get($c, 'client.person.email'), 'phone' => data_get($c, 'person.phone') ?? data_get($c, 'client.person.phone'), ], ]; } if (isset($ctx['contract'])) { $co = $ctx['contract']; $out['contract'] = [ 'id' => data_get($co, 'id'), 'uuid' => data_get($co, 'uuid'), 'reference' => data_get($co, 'reference'), // Format amounts in EU style for emails 'amount' => $formatMoneyEu(data_get($co, 'amount')), ]; $meta = data_get($co, 'meta'); if (is_array($meta)) { $out['contract']['meta'] = $meta; } } if (isset($ctx['activity'])) { $a = $ctx['activity']; $out['activity'] = [ 'id' => data_get($a, 'id'), 'note' => data_get($a, 'note'), // EU formatted date and amount by default in emails 'due_date' => $formatDateEu(data_get($a, 'due_date')), 'amount' => $formatMoneyEu(data_get($a, 'amount')), 'action' => [ 'name' => data_get($a, 'action.name'), ], 'decision' => [ 'name' => data_get($a, 'decision.name'), ], ]; } if (! empty($ctx['extra']) && is_array($ctx['extra'])) { $out['extra'] = $ctx['extra']; } return $out; } }