add projet

This commit is contained in:
root
2026-03-05 12:29:37 -05:00
parent 8d1eef8ba8
commit 23b7db1107
9109 changed files with 1106501 additions and 73 deletions
+244
View File
@@ -0,0 +1,244 @@
<?php
namespace App\Http\Controllers\Api;
use App\Models\User;
use App\Services\EmailService;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\Log;
use Symfony\Component\HttpFoundation\Response;
class BroadcastEmailController extends BaseApiController
{
protected User $user;
protected EmailService $mailer;
public function __construct()
{
parent::__construct();
$this->user = model(User::class);
$this->mailer = app(EmailService::class);
}
public function metadata(): JsonResponse
{
$parents = array_values(array_filter(
$this->user->getParents(),
static fn ($parent) => !empty($parent['email'])
));
return $this->success([
'parents' => $parents,
'fromOptions' => $this->senderOptionsFromEnv(),
], 'Broadcast email metadata retrieved');
}
public function send(): JsonResponse
{
$data = $this->payloadData();
$rules = [
'subject' => 'required|string',
'body_html' => 'required|string',
'mode' => 'permit_empty|string',
'from_key' => 'permit_empty|string',
'wrap_layout'=> 'permit_empty',
'preheader' => 'permit_empty|string',
'cta_text' => 'permit_empty|string',
'cta_url' => 'permit_empty|string',
];
$errors = $this->validateRequest($data, $rules);
if (!empty($errors)) {
return $this->respondValidationError($errors);
}
$isTest = !empty($data['send_test_only']);
$mode = (string) ($data['mode'] ?? 'standard');
$subject = trim((string) $data['subject']);
$fromKey = trim((string) ($data['from_key'] ?? 'general'));
$wrapLayout = !empty($data['wrap_layout']);
$preheader = (string) ($data['preheader'] ?? '');
$ctaText = (string) ($data['cta_text'] ?? '');
$ctaUrl = (string) ($data['cta_url'] ?? '');
$body = $this->sanitizeEmailHtml((string) $data['body_html']);
if ($subject === '' || $body === '') {
return $this->respondError('Subject and body are required.', Response::HTTP_BAD_REQUEST);
}
if ($isTest) {
$testEmail = trim((string) ($data['test_email'] ?? ''));
if ($testEmail === '' || !filter_var($testEmail, FILTER_VALIDATE_EMAIL)) {
return $this->respondError('Provide a valid test email address.', Response::HTTP_BAD_REQUEST);
}
$html = $this->composeEmailHtml(
$wrapLayout,
$subject,
$body,
'Parent',
$preheader,
$ctaText,
$ctaUrl,
$mode === 'personalized'
);
$ok = $this->mailer->send($testEmail, '[TEST] ' . $subject, $html, $fromKey);
return $ok
? $this->success(['message' => "Test email sent to {$testEmail}."], 'Test email sent')
: $this->respondError('Test email failed to send. Check logs.', Response::HTTP_INTERNAL_SERVER_ERROR);
}
$parentIds = $this->parseParentIds($data['parent_ids'] ?? []);
if (empty($parentIds)) {
return $this->respondError('Please select at least one parent.', Response::HTTP_BAD_REQUEST);
}
$rows = $this->user->newQuery()
->select('users.id', 'users.email', 'users.firstname', 'users.lastname')
->whereIn('users.id', $parentIds)
->whereNotNull('users.email')
->get();
if ($rows->isEmpty()) {
return $this->respondError('No valid parent emails found.', Response::HTTP_BAD_REQUEST);
}
$stats = ['attempted' => 0, 'sent' => 0, 'failed' => 0, 'mode' => $mode];
$personalized = ($mode === 'personalized');
foreach ($rows as $row) {
$stats['attempted']++;
$recipientName = trim(($row->firstname ?? '') . ' ' . ($row->lastname ?? '')) ?: 'Parent';
$html = $this->composeEmailHtml(
$wrapLayout,
$subject,
$body,
$recipientName,
$preheader,
$ctaText,
$ctaUrl,
$personalized
);
$ok = $this->mailer->send($row->email, $subject, $html, $fromKey);
$ok ? $stats['sent']++ : $stats['failed']++;
}
$message = "Broadcast finished. Mode: {$stats['mode']}. Sent: {$stats['sent']}/{$stats['attempted']}. Failures: {$stats['failed']}.";
$status = $stats['failed'] > 0 ? Response::HTTP_OK : Response::HTTP_OK;
return $this->success(array_merge($stats, ['message' => $message]), 'Broadcast completed', $status);
}
public function uploadImage(): JsonResponse
{
if (!$this->request->is('post')) {
return $this->respondError('Method not allowed', Response::HTTP_METHOD_NOT_ALLOWED);
}
$file = request()->file('image');
if (!$file || !$file->isValid()) {
return $this->respondError('No image uploaded.', Response::HTTP_BAD_REQUEST);
}
$mime = strtolower((string) $file->getMimeType());
$allowed = ['image/jpeg' => 'jpg', 'image/png' => 'png', 'image/gif' => 'gif', 'image/webp' => 'webp'];
if (!isset($allowed[$mime])) {
return $this->respondError('Unsupported image type.', Response::HTTP_UNSUPPORTED_MEDIA_TYPE);
}
if ($file->getSize() > 5 * 1024 * 1024) {
return $this->respondError('Image too large (max 5MB).', Response::HTTP_REQUEST_ENTITY_TOO_LARGE);
}
$targetDir = public_path('uploads/email');
if (!is_dir($targetDir)) {
@mkdir($targetDir, 0755, true);
}
$newName = uniqid('em_', true) . '.' . $allowed[$mime];
$file->move($targetDir, $newName);
$url = url('uploads/email/' . $newName);
return $this->success([
'success' => true,
'url' => $url,
], 'Image uploaded successfully');
}
private function composeEmailHtml(
bool $wrap,
string $subject,
string $body,
string $recipientName,
string $preheader,
string $ctaText,
string $ctaUrl,
bool $doPersonalize
): string {
$content = $doPersonalize ? str_replace('{{name}}', $recipientName, $body) : $body;
if (!$wrap) {
return $content;
}
try {
return view('emails/broadcast_wrapper', [
'subject' => $subject,
'content' => $content,
'preheader' => $preheader,
'cta_text' => $ctaText,
'cta_url' => $ctaUrl,
], ['saveData' => false]);
} catch (\Throwable $e) {
Log::warning('broadcast_wrapper view missing: ' . $e->getMessage());
return $content;
}
}
private function sanitizeEmailHtml(string $html): string
{
$html = preg_replace('#<(script|iframe)[^>]*>.*?</\\1>#is', '', $html);
$html = preg_replace('/\son\w+="[^"]*"/i', '', $html);
$html = preg_replace("/\son\w+='[^']*'/i", '', $html);
return $html;
}
private function senderOptionsFromEnv(): array
{
$json = env('MAIL_SENDERS', '{}');
$arr = json_decode($json, true);
if (!is_array($arr) || empty($arr)) {
$smtpUser = env('SMTP_USER', '');
$name = 'Al Rahma Sunday School';
return [[
'key' => 'general',
'label' => trim($name . ($smtpUser ? " <{$smtpUser}>" : '')),
]];
}
$out = [];
foreach ($arr as $key => $info) {
$nm = $info['name'] ?? 'Sender';
$em = $info['email'] ?? '';
$out[] = ['key' => (string) $key, 'label' => trim($nm . ($em ? " <{$em}>" : ''))];
}
return $out;
}
private function parseParentIds($raw): array
{
$raw = is_array($raw) ? $raw : [$raw];
$ids = [];
foreach ($raw as $value) {
if (is_string($value) && str_contains($value, ',')) {
$ids = array_merge($ids, array_map('intval', explode(',', $value)));
} else {
$ids[] = (int) $value;
}
}
return array_values(array_unique(array_filter($ids)));
}
}