add more controllers and fix tests
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Email;
|
||||
|
||||
use Illuminate\Http\UploadedFile;
|
||||
|
||||
class EmailAttachmentService
|
||||
{
|
||||
public function normalize($files): array
|
||||
{
|
||||
if ($files instanceof UploadedFile) {
|
||||
$files = [$files];
|
||||
}
|
||||
if (!is_array($files)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$out = [];
|
||||
foreach ($files as $file) {
|
||||
if (!$file instanceof UploadedFile || !$file->isValid()) {
|
||||
continue;
|
||||
}
|
||||
$path = $file->getRealPath();
|
||||
if (!$path || !is_file($path)) {
|
||||
continue;
|
||||
}
|
||||
$out[] = [
|
||||
'path' => $path,
|
||||
'name' => $file->getClientOriginalName(),
|
||||
];
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Email;
|
||||
|
||||
use PHPMailer\PHPMailer\Exception as MailerException;
|
||||
use PHPMailer\PHPMailer\PHPMailer;
|
||||
|
||||
class EmailDispatchService
|
||||
{
|
||||
public function __construct(private EmailProfileService $profiles)
|
||||
{
|
||||
}
|
||||
|
||||
public function send(
|
||||
string $recipient,
|
||||
string $subject,
|
||||
string $htmlMessage,
|
||||
?string $profile = null,
|
||||
?string $replyToEmail = null,
|
||||
?string $replyToName = null,
|
||||
array $attachments = []
|
||||
): bool {
|
||||
$profile = $this->profiles->resolveProfile($profile);
|
||||
$cfg = $this->profiles->getProfileConfig($profile);
|
||||
|
||||
if (empty($cfg['host']) || empty($cfg['user']) || $cfg['pass'] === '') {
|
||||
logger()->error("[mail:$profile] Missing SMTP config (host/user/pass). Check MAIL_{$this->profiles->envKeyFromProfile($profile)}_* or MAIL_DEFAULT_*/MAIL_*.");
|
||||
return false;
|
||||
}
|
||||
|
||||
$debugEnabled = (bool) env('MAIL_DEBUG', false);
|
||||
$timeout = (int) env('MAIL_TIMEOUT', 15);
|
||||
$keepAlive = (bool) env('MAIL_KEEPALIVE', false);
|
||||
$verifyPeer = filter_var(env('MAIL_VERIFY_PEER', 'true'), FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
$mail = new PHPMailer(true);
|
||||
|
||||
try {
|
||||
if ($debugEnabled) {
|
||||
ob_start();
|
||||
}
|
||||
|
||||
$mail->isSMTP();
|
||||
$mail->Host = $cfg['host'];
|
||||
$mail->Port = $cfg['port'];
|
||||
$mail->SMTPAuth = true;
|
||||
$mail->Username = $cfg['user'];
|
||||
$mail->Password = $cfg['pass'];
|
||||
$mail->CharSet = 'UTF-8';
|
||||
$mail->Timeout = $timeout;
|
||||
$mail->SMTPKeepAlive = $keepAlive;
|
||||
$mail->SMTPAutoTLS = true;
|
||||
|
||||
if ($cfg['encryption'] === 'ssl') {
|
||||
$mail->SMTPSecure = PHPMailer::ENCRYPTION_SMTPS;
|
||||
} else {
|
||||
$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
|
||||
}
|
||||
|
||||
$mail->SMTPOptions = [
|
||||
'ssl' => [
|
||||
'verify_peer' => $verifyPeer,
|
||||
'verify_peer_name' => $verifyPeer,
|
||||
'allow_self_signed' => !$verifyPeer,
|
||||
],
|
||||
];
|
||||
|
||||
$mail->SMTPDebug = $debugEnabled ? 3 : 0;
|
||||
$mail->Debugoutput = 'error_log';
|
||||
|
||||
$mail->setFrom($cfg['fromEmail'], $cfg['fromName']);
|
||||
if (!empty($cfg['returnPath'])) {
|
||||
$mail->Sender = $cfg['returnPath'];
|
||||
}
|
||||
|
||||
$mail->clearReplyTos();
|
||||
$rtEmail = env('MAIL_DEFAULT_REPLY_TO');
|
||||
$rtName = env('MAIL_DEFAULT_REPLY_TO_NAME');
|
||||
if (!$rtEmail || !filter_var($rtEmail, FILTER_VALIDATE_EMAIL)) {
|
||||
$rtEmail = $replyToEmail ?: ($cfg['replyTo'] ?: $cfg['fromEmail']);
|
||||
}
|
||||
if (!$rtName) {
|
||||
$rtName = $replyToName ?: ($cfg['replyToName'] ?: $cfg['fromName']);
|
||||
}
|
||||
$rtName = $this->profiles->sanitizeReplyToName($rtName, $cfg['fromName']);
|
||||
if ($rtEmail) {
|
||||
$mail->addReplyTo($rtEmail, $rtName);
|
||||
}
|
||||
|
||||
if (!empty($cfg['dkim']['domain']) && !empty($cfg['dkim']['private']) && !empty($cfg['dkim']['selector'])) {
|
||||
$mail->DKIM_domain = $cfg['dkim']['domain'];
|
||||
$mail->DKIM_private = $cfg['dkim']['private'];
|
||||
$mail->DKIM_selector = $cfg['dkim']['selector'];
|
||||
$mail->DKIM_identity = $cfg['fromEmail'];
|
||||
}
|
||||
|
||||
$mail->addAddress($recipient);
|
||||
|
||||
foreach ($attachments as $att) {
|
||||
if (!empty($att['path']) && is_file($att['path'])) {
|
||||
$mail->addAttachment($att['path'], $att['name'] ?? '');
|
||||
}
|
||||
}
|
||||
|
||||
$mail->isHTML(true);
|
||||
$mail->Subject = $subject;
|
||||
$mail->Body = $htmlMessage;
|
||||
|
||||
$ok = $mail->send();
|
||||
$dbg = $debugEnabled ? (ob_get_clean() ?: '') : '';
|
||||
|
||||
if ($ok) {
|
||||
logger()->info("[mail:$profile] Sent to {$recipient}, subj='{$subject}' via {$cfg['host']}:{$cfg['port']}/{$cfg['encryption']}");
|
||||
return true;
|
||||
}
|
||||
|
||||
logger()->error("[mail:$profile] Failed: {$mail->ErrorInfo}. Debug: {$dbg}");
|
||||
return false;
|
||||
} catch (MailerException $e) {
|
||||
$dbg = $debugEnabled ? (ob_get_clean() ?: '') : '';
|
||||
logger()->error("[mail:$profile] Exception: {$e->getMessage()} | PHPMailer: {$mail->ErrorInfo} | Debug: {$dbg}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Email;
|
||||
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class EmailExtractorService
|
||||
{
|
||||
public function listEmails(): array
|
||||
{
|
||||
$users = [];
|
||||
$parents = [];
|
||||
|
||||
$userRows = DB::table('users')->select('email')->get();
|
||||
foreach ($userRows as $row) {
|
||||
$email = strtolower(trim((string) ($row->email ?? '')));
|
||||
if ($email !== '' && filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
$users[] = $email;
|
||||
}
|
||||
}
|
||||
$users = array_values(array_unique($users));
|
||||
|
||||
$parentRows = DB::table('parents')->select('secondparent_email')->get();
|
||||
foreach ($parentRows as $row) {
|
||||
$email = strtolower(trim((string) ($row->secondparent_email ?? '')));
|
||||
if ($email !== '' && filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
$parents[] = $email;
|
||||
}
|
||||
}
|
||||
$parents = array_values(array_unique($parents));
|
||||
|
||||
return [
|
||||
'users' => $users,
|
||||
'parents' => $parents,
|
||||
];
|
||||
}
|
||||
|
||||
public function compare(array $csvEmails): array
|
||||
{
|
||||
$emails = $this->listEmails();
|
||||
$users = $emails['users'];
|
||||
$parents = $emails['parents'];
|
||||
|
||||
$csvEmails = $this->normalizeEmailArray($csvEmails);
|
||||
|
||||
$csvSet = array_flip(array_values(array_unique($csvEmails)));
|
||||
$dbUnion = array_values(array_unique(array_merge($users, $parents)));
|
||||
$dbSet = array_flip($dbUnion);
|
||||
|
||||
$existed = [];
|
||||
foreach ($csvSet as $email => $_) {
|
||||
if (isset($dbSet[$email])) {
|
||||
$existed[] = $email;
|
||||
}
|
||||
}
|
||||
sort($existed);
|
||||
|
||||
$needToAdd = [];
|
||||
foreach ($dbSet as $email => $_) {
|
||||
if (!isset($csvSet[$email])) {
|
||||
$needToAdd[] = $email;
|
||||
}
|
||||
}
|
||||
sort($needToAdd);
|
||||
|
||||
return [
|
||||
'existed' => $existed,
|
||||
'needToAdd' => $needToAdd,
|
||||
'counts' => [
|
||||
'csv' => count($csvEmails),
|
||||
'db' => count($dbUnion),
|
||||
'users' => count($users),
|
||||
'parents' => count($parents),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function normalizeEmailArray(array $arr): array
|
||||
{
|
||||
$out = [];
|
||||
foreach ($arr as $e) {
|
||||
$email = strtolower(trim((string) $e));
|
||||
if ($email !== '' && filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||||
$out[] = $email;
|
||||
}
|
||||
}
|
||||
return array_values(array_unique($out));
|
||||
}
|
||||
|
||||
public function extractEmailsFromText(string $text): array
|
||||
{
|
||||
$pattern = '/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/i';
|
||||
preg_match_all($pattern, $text, $matches);
|
||||
$emails = $matches[0] ?? [];
|
||||
return $this->normalizeEmailArray($emails);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Email;
|
||||
|
||||
class EmailProfileService
|
||||
{
|
||||
public function resolveProfile(?string $profile): string
|
||||
{
|
||||
$p = strtolower(trim((string) $profile));
|
||||
if ($p === '' || $p === 'auto') {
|
||||
$p = strtolower((string) env('MAIL_PROFILE_DEFAULT', 'default')) ?: 'default';
|
||||
}
|
||||
|
||||
return match ($p) {
|
||||
'comm', 'comms' => 'communication',
|
||||
'sys', 'system' => 'default',
|
||||
default => $p,
|
||||
};
|
||||
}
|
||||
|
||||
public function envKeyFromProfile(string $profile): string
|
||||
{
|
||||
return strtoupper(preg_replace('/[^A-Z0-9]+/i', '_', $profile));
|
||||
}
|
||||
|
||||
public function getProfileConfig(string $profile): array
|
||||
{
|
||||
$key = $this->envKeyFromProfile($profile);
|
||||
|
||||
$envFirst = function (array $keys, $default = null) {
|
||||
foreach ($keys as $k) {
|
||||
$v = env($k);
|
||||
if (is_string($v)) {
|
||||
$v = trim($v);
|
||||
}
|
||||
if ($v !== null && $v !== '') {
|
||||
return $v;
|
||||
}
|
||||
}
|
||||
return $default;
|
||||
};
|
||||
|
||||
$host = $envFirst(["MAIL_{$key}_HOST", 'MAIL_DEFAULT_HOST', 'SMTP_HOST', 'MAIL_HOST'], '');
|
||||
$user = $envFirst(["MAIL_{$key}_USER", 'MAIL_DEFAULT_USER', 'SMTP_USER', 'MAIL_USERNAME'], '');
|
||||
$pass = $envFirst(["MAIL_{$key}_PASS", 'MAIL_DEFAULT_PASS', 'SMTP_PASS', 'MAIL_PASSWORD'], '');
|
||||
$portRaw = $envFirst(["MAIL_{$key}_PORT", 'MAIL_DEFAULT_PORT', 'SMTP_PORT', 'MAIL_PORT'], 587);
|
||||
$encRaw = $envFirst(["MAIL_{$key}_ENCRYPTION", 'MAIL_DEFAULT_ENCRYPTION', 'SMTP_ENCRYPTION', 'MAIL_ENCRYPTION'], 'tls');
|
||||
|
||||
$fromEmail = $envFirst(["MAIL_{$key}_FROM_EMAIL", 'MAIL_DEFAULT_FROM_EMAIL', 'MAIL_FROM_ADDRESS'], $user ?: 'no-reply@alrahmaisgl.org');
|
||||
$fromName = $envFirst(["MAIL_{$key}_FROM_NAME", 'MAIL_DEFAULT_FROM_NAME', 'MAIL_FROM_NAME'], 'Al Rahma Sunday School');
|
||||
$replyTo = $envFirst(["MAIL_{$key}_REPLY_TO", 'MAIL_DEFAULT_REPLY_TO'], '');
|
||||
$replyToName = $envFirst(["MAIL_{$key}_REPLY_TO_NAME", 'MAIL_DEFAULT_REPLY_TO_NAME'], '');
|
||||
$returnPath = $envFirst(["MAIL_{$key}_RETURN_PATH", 'MAIL_DEFAULT_RETURN_PATH'], '');
|
||||
|
||||
$dkim = [
|
||||
'domain' => $envFirst(["MAIL_{$key}_DKIM_DOMAIN", 'MAIL_DEFAULT_DKIM_DOMAIN'], ''),
|
||||
'selector' => $envFirst(["MAIL_{$key}_DKIM_SELECTOR", 'MAIL_DEFAULT_DKIM_SELECTOR'], ''),
|
||||
'private' => $envFirst(["MAIL_{$key}_DKIM_PRIVATE", 'MAIL_DEFAULT_DKIM_PRIVATE'], ''),
|
||||
];
|
||||
|
||||
$port = (int) $portRaw ?: 587;
|
||||
$encryption = strtolower((string) $encRaw);
|
||||
$encryption = in_array($encryption, ['tls', 'ssl'], true) ? $encryption : 'tls';
|
||||
|
||||
if ($port === 587 && $encryption === 'ssl') {
|
||||
$encryption = 'tls';
|
||||
}
|
||||
if ($port === 465 && $encryption === 'tls') {
|
||||
$encryption = 'ssl';
|
||||
}
|
||||
|
||||
return [
|
||||
'host' => (string) $host,
|
||||
'user' => (string) $user,
|
||||
'pass' => (string) $pass,
|
||||
'port' => $port,
|
||||
'encryption' => $encryption,
|
||||
'fromEmail' => (string) $fromEmail,
|
||||
'fromName' => (string) $fromName,
|
||||
'replyTo' => (string) $replyTo,
|
||||
'replyToName' => (string) $this->sanitizeReplyToName($replyToName, (string) $fromName),
|
||||
'returnPath' => (string) $returnPath,
|
||||
'dkim' => $dkim,
|
||||
];
|
||||
}
|
||||
|
||||
public function sanitizeReplyToName(?string $name, string $fallback): string
|
||||
{
|
||||
$trimmed = trim((string) $name);
|
||||
if ($trimmed === '' || preg_match('/^no[- ]?repl(?:y|ay)$/i', $trimmed)) {
|
||||
return $fallback;
|
||||
}
|
||||
return $trimmed;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Email;
|
||||
|
||||
class EmailSenderOptionsService
|
||||
{
|
||||
public function listSenders(): array
|
||||
{
|
||||
$json = env('MAIL_SENDERS', '{}');
|
||||
$arr = json_decode($json, true);
|
||||
|
||||
if (!is_array($arr) || empty($arr)) {
|
||||
$smtpUser = getenv('SMTP_USER') ?: '';
|
||||
$name = 'Al Rahma Sunday School';
|
||||
|
||||
return [[
|
||||
'key' => 'general',
|
||||
'name' => $name,
|
||||
'email' => $smtpUser,
|
||||
'label' => trim($name . ($smtpUser ? " <{$smtpUser}>" : '')),
|
||||
]];
|
||||
}
|
||||
|
||||
$out = [];
|
||||
foreach ($arr as $key => $info) {
|
||||
$name = $info['name'] ?? 'Sender';
|
||||
$email = $info['email'] ?? '';
|
||||
$out[] = [
|
||||
'key' => (string) $key,
|
||||
'name' => (string) $name,
|
||||
'email' => (string) $email,
|
||||
'label' => trim($name . ($email ? " <{$email}>" : '')),
|
||||
];
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user