diff --git a/app/Services/EmailSender.php b/app/Services/EmailSender.php index 7f00ba9..c34947f 100644 --- a/app/Services/EmailSender.php +++ b/app/Services/EmailSender.php @@ -180,40 +180,122 @@ public function sendFromLog(EmailLog $log): array foreach ($attachments as $att) { try { $disk = $att['disk'] ?? 'public'; - $path = $att['path'] ?? null; - if (! $path) { + $rawPath = $att['path'] ?? null; + if (! $rawPath) { continue; } - $name = $att['name'] ?? basename($path); + $name = $att['name'] ?? basename($rawPath); $mime = $att['mime'] ?? 'application/octet-stream'; - $attached = false; $storage = \Storage::disk($disk); - // Prefer local path when available (local driver) - try { - if (method_exists($storage, 'path')) { - $full = $storage->path($path); - if (is_string($full) && is_file($full)) { - $email->attachFromPath($full, $name, $mime); - $attached = true; - } + // Build candidate paths (normalize slashes and strip legacy prefixes) + $norm = ltrim(str_replace('\\', '/', (string) $rawPath), '/'); + $candidates = []; + $candidates[] = $norm; + if (str_starts_with($norm, 'public/')) { + $candidates[] = substr($norm, 7); + } + if (str_starts_with($norm, 'storage/')) { + $candidates[] = substr($norm, 8); + } + $candidates = array_values(array_unique(array_filter($candidates))); + + // Diagnostics (guarded by app.debug) + if (config('app.debug')) { + try { + \Log::debug('EmailSender: attachment diagnostics (Symfony mailer)', [ + 'log_id' => $log->id, + 'disk' => $disk, + 'disk_driver' => config('filesystems.disks.'.$disk.'.driver'), + 'raw_path' => $rawPath, + 'normalized' => $norm, + 'candidates' => $candidates, + ]); + } catch (\Throwable $e) { + // ignore logging failures } - } catch (\Throwable $e) { - // fall through to bytes } - if (! $attached) { - // Fallback for non-local disks (e.g., S3): read bytes and attach directly + $attached = false; + // Try local filesystem path first + foreach ($candidates as $cand) { try { - if ($storage->exists($path)) { - $bytes = $storage->get($path); - if (! is_null($bytes) && $bytes !== false) { - $email->attach($bytes, $name, $mime); + if (method_exists($storage, 'path')) { + $full = $storage->path($cand); + if (is_string($full) && is_file($full)) { + if (config('app.debug')) { + try { + \Log::debug('EmailSender: attaching from local path', [ + 'log_id' => $log->id, + 'disk' => $disk, + 'candidate' => $cand, + 'full_path' => $full, + 'filesize' => @filesize($full) ?: null, + 'mime' => $mime, + 'name' => $name, + ]); + } catch (\Throwable $e) { + } + } + $email->attachFromPath($full, $name, $mime); $attached = true; + break; } } } catch (\Throwable $e) { - // ignore, continue to next attachment + } + } + + if (! $attached) { + // Fallback for non-local disks (S3, etc.): read bytes and attach + foreach ($candidates as $cand) { + try { + $exists = $storage->exists($cand); + if (config('app.debug')) { + try { + \Log::debug('EmailSender: storage exists check', [ + 'log_id' => $log->id, + 'disk' => $disk, + 'candidate' => $cand, + 'exists' => $exists, + ]); + } catch (\Throwable $e) { + } + } + if ($exists) { + $bytes = $storage->get($cand); + if (! is_null($bytes) && $bytes !== false) { + if (config('app.debug')) { + try { + \Log::debug('EmailSender: attaching from bytes (Symfony)', [ + 'log_id' => $log->id, + 'disk' => $disk, + 'candidate' => $cand, + 'byte_length' => is_string($bytes) ? strlen($bytes) : null, + 'mime' => $mime, + 'name' => $name, + ]); + } catch (\Throwable $e) { + } + } + $email->attach($bytes, $name, $mime); + $attached = true; + break; + } + } + } catch (\Throwable $e) { + } + } + } + + if (config('app.debug')) { + try { + \Log::debug('EmailSender: attachment result (Symfony mailer)', [ + 'log_id' => $log->id, + 'name' => $name, + 'attached' => $attached, + ]); + } catch (\Throwable $e) { } } } catch (\Throwable $e) { @@ -265,6 +347,105 @@ public function sendFromLog(EmailLog $log): array // Provide a plain text alternative when available $message->text($text); } + + // Attachments via Laravel Mailer (supports storage disks) + $attachments = (array) ($log->attachments ?? []); + foreach ($attachments as $att) { + try { + $disk = $att['disk'] ?? 'public'; + $rawPath = $att['path'] ?? null; + if (! $rawPath) { + continue; + } + $name = $att['name'] ?? basename($rawPath); + $mime = $att['mime'] ?? 'application/octet-stream'; + + $norm = ltrim(str_replace('\\', '/', (string) $rawPath), '/'); + $candidates = [$norm]; + if (str_starts_with($norm, 'public/')) { + $candidates[] = substr($norm, 7); + } + if (str_starts_with($norm, 'storage/')) { + $candidates[] = substr($norm, 8); + } + if (config('app.debug')) { + try { + \Log::debug('EmailSender: attachment diagnostics (Laravel html)', [ + 'log_id' => $log->id, + 'disk' => $disk, + 'disk_driver' => config('filesystems.disks.'.$disk.'.driver'), + 'raw_path' => $rawPath, + 'normalized' => $norm, + 'candidates' => array_values(array_unique($candidates)), + ]); + } catch (\Throwable $e) { + } + } + $attached = false; + foreach (array_values(array_unique($candidates)) as $cand) { + try { + $message->attachFromStorageDisk($disk, $cand, $name, ['mime' => $mime]); + if (config('app.debug')) { + try { + \Log::debug('EmailSender: attached via attachFromStorageDisk (Laravel html)', [ + 'log_id' => $log->id, + 'disk' => $disk, + 'candidate' => $cand, + 'mime' => $mime, + 'name' => $name, + ]); + } catch (\Throwable $e) { + } + } + $attached = true; + break; + } catch (\Throwable $e) { + // fall back to bytes + } + } + if (! $attached) { + $storage = \Storage::disk($disk); + foreach (array_values(array_unique($candidates)) as $cand) { + try { + $exists = $storage->exists($cand); + if (config('app.debug')) { + try { + \Log::debug('EmailSender: storage exists check (Laravel html)', [ + 'log_id' => $log->id, + 'disk' => $disk, + 'candidate' => $cand, + 'exists' => $exists, + ]); + } catch (\Throwable $e) { + } + } + if ($exists) { + $bytes = $storage->get($cand); + if (! is_null($bytes) && $bytes !== false) { + if (config('app.debug')) { + try { + \Log::debug('EmailSender: attaching from bytes (Laravel html)', [ + 'log_id' => $log->id, + 'disk' => $disk, + 'candidate' => $cand, + 'byte_length' => is_string($bytes) ? strlen($bytes) : null, + 'mime' => $mime, + 'name' => $name, + ]); + } catch (\Throwable $e) { + } + } + $message->attachData($bytes, $name, ['mime' => $mime]); + break; + } + } + } catch (\Throwable $e) { + } + } + } + } catch (\Throwable $e) { + } + } }); } else { \Mail::raw($text ?: '', function ($message) use ($log, $subject) { @@ -296,6 +477,104 @@ public function sendFromLog(EmailLog $log): array if (! empty($log->reply_to)) { $message->replyTo($log->reply_to); } + + // Attachments for raw mail + $attachments = (array) ($log->attachments ?? []); + foreach ($attachments as $att) { + try { + $disk = $att['disk'] ?? 'public'; + $rawPath = $att['path'] ?? null; + if (! $rawPath) { + continue; + } + $name = $att['name'] ?? basename($rawPath); + $mime = $att['mime'] ?? 'application/octet-stream'; + + $norm = ltrim(str_replace('\\', '/', (string) $rawPath), '/'); + $candidates = [$norm]; + if (str_starts_with($norm, 'public/')) { + $candidates[] = substr($norm, 7); + } + if (str_starts_with($norm, 'storage/')) { + $candidates[] = substr($norm, 8); + } + if (config('app.debug')) { + try { + \Log::debug('EmailSender: attachment diagnostics (Laravel raw)', [ + 'log_id' => $log->id, + 'disk' => $disk, + 'disk_driver' => config('filesystems.disks.'.$disk.'.driver'), + 'raw_path' => $rawPath, + 'normalized' => $norm, + 'candidates' => array_values(array_unique($candidates)), + ]); + } catch (\Throwable $e) { + } + } + $attached = false; + foreach (array_values(array_unique($candidates)) as $cand) { + try { + $message->attachFromStorageDisk($disk, $cand, $name, ['mime' => $mime]); + if (config('app.debug')) { + try { + \Log::debug('EmailSender: attached via attachFromStorageDisk (Laravel raw)', [ + 'log_id' => $log->id, + 'disk' => $disk, + 'candidate' => $cand, + 'mime' => $mime, + 'name' => $name, + ]); + } catch (\Throwable $e) { + } + } + $attached = true; + break; + } catch (\Throwable $e) { + } + } + if (! $attached) { + $storage = \Storage::disk($disk); + foreach (array_values(array_unique($candidates)) as $cand) { + try { + $exists = $storage->exists($cand); + if (config('app.debug')) { + try { + \Log::debug('EmailSender: storage exists check (Laravel raw)', [ + 'log_id' => $log->id, + 'disk' => $disk, + 'candidate' => $cand, + 'exists' => $exists, + ]); + } catch (\Throwable $e) { + } + } + if ($exists) { + $bytes = $storage->get($cand); + if (! is_null($bytes) && $bytes !== false) { + if (config('app.debug')) { + try { + \Log::debug('EmailSender: attaching from bytes (Laravel raw)', [ + 'log_id' => $log->id, + 'disk' => $disk, + 'candidate' => $cand, + 'byte_length' => is_string($bytes) ? strlen($bytes) : null, + 'mime' => $mime, + 'name' => $name, + ]); + } catch (\Throwable $e) { + } + } + $message->attachData($bytes, $name, ['mime' => $mime]); + break; + } + } + } catch (\Throwable $e) { + } + } + } + } catch (\Throwable $e) { + } + } }); } }