userModel = new UserModel(); $this->mailer = new EmailService(); // use your existing mailer unmodified } public function index() { helper(['form']); // Parents via your role-based function $parents = $this->userModel->getParents(); $parents = array_values(array_filter($parents, static fn($p) => !empty($p['email']))); // Sender list from MAIL_SENDERS directly (no change to EmailService) $fromOptions = $this->senderOptionsFromEnv(); return view('administrator/broadcast_email', [ 'parents' => $parents, 'fromOptions' => $fromOptions, // [['key'=>'general','label'=>'Al Rahma Office '], ...] ]); } public function send() { helper(['form']); if (strtolower($this->request->getMethod()) !== 'post') { return redirect()->to(site_url('admin/broadcast-email')); } $isTestOnly = $this->request->getPost('send_test_only') !== null; $mode = (string) $this->request->getPost('mode'); // 'personalized' | 'standard' $subject = trim((string) $this->request->getPost('subject')); $fromKey = trim((string) $this->request->getPost('from_key') ?: 'general'); $body = (string) $this->request->getPost('body_html'); $body = $this->sanitizeEmailHtml($body); // Layout options $wrapLayout = (bool) $this->request->getPost('wrap_layout'); $preheader = (string) ($this->request->getPost('preheader') ?? ''); $ctaText = (string) ($this->request->getPost('cta_text') ?? ''); $ctaUrl = (string) ($this->request->getPost('cta_url') ?? ''); $testEmail = trim((string) $this->request->getPost('test_email')); if ($subject === '' || $body === '') { return redirect()->back()->withInput()->with('error', 'Subject and Body are required.'); } $isPersonalized = ($mode === 'personalized'); // --- TEST ONLY --- if ($isTestOnly) { if ($testEmail === '') { return redirect()->back()->withInput()->with('error', 'Provide a test email address.'); } $recipientName = 'Parent'; $html = $this->composeEmailHtml( $wrapLayout, $subject, $body, $recipientName, $preheader, $ctaText, $ctaUrl, $isPersonalized ); $ok = $this->mailer->send($testEmail, '[TEST] ' . $subject, $html, $fromKey); return redirect()->back()->with( $ok ? 'message' : 'error', $ok ? "Test email sent to {$testEmail}." : "Test email failed (mailer->send() returned false). Check logs." ); } // --- BROADCAST --- $rawIds = (array) ($this->request->getPost('parent_ids') ?? []); $ids = []; foreach ($rawIds as $v) { if (is_string($v) && strpos($v, ',') !== false) { $ids = array_merge($ids, array_map('intval', explode(',', $v))); } else { $ids[] = (int) $v; } } $ids = array_values(array_unique(array_filter($ids))); if (empty($ids)) { return redirect()->back()->withInput()->with('error', 'Please select at least one parent.'); } $rows = model(\App\Models\UserModel::class) ->select('users.id, users.email, CONCAT(users.firstname, " ", users.lastname) AS name') ->whereIn('users.id', $ids) ->where('users.email IS NOT NULL AND users.email != ""') ->findAll(); if (empty($rows)) { return redirect()->back()->withInput()->with('error', 'No valid parent emails found.'); } $stats = ['attempted' => 0, 'sent' => 0, 'failed' => 0, 'mode' => $mode]; foreach ($rows as $r) { $stats['attempted']++; $recipientName = $r['name'] ?: 'Parent'; $html = $this->composeEmailHtml( $wrapLayout, $subject, $body, $recipientName, $preheader, $ctaText, $ctaUrl, $isPersonalized ); $ok = $this->mailer->send($r['email'], $subject, $html, $fromKey); $ok ? $stats['sent']++ : $stats['failed']++; } $msg = "Broadcast finished. Mode: {$stats['mode']}. Sent: {$stats['sent']}/{$stats['attempted']}. Failures: {$stats['failed']}."; return redirect()->to(site_url('admin/broadcast-email')) ->with($stats['failed'] > 0 ? 'error' : 'message', $msg); } /** * Build HTML: optionally wrap $body inside your view('layout/email_layout', ...). */ private function composeEmailHtml( bool $wrap, string $subject, string $body, string $recipientName, string $preheader = '', string $ctaText = '', string $ctaUrl = '', bool $doPersonalize = true ): string { $content = $doPersonalize ? str_replace('{{name}}', $recipientName, $body) : $body; if (!$wrap) { return $content; // raw body } // Render a CHILD view that defines the section your layout expects return view('emails/broadcast_wrapper', [ 'subject' => $subject, 'content' => $content, // pass more if you later wire them in your layout 'preheader' => $preheader, 'cta_text' => $ctaText, 'cta_url' => $ctaUrl, ]); } private function sanitizeEmailHtml(string $html): string { // Remove