communication = model(Communication::class); $this->student = model(Student::class); $this->familyStudent = model(FamilyStudent::class); $this->template = model(EmailTemplate::class); $this->log = model(CommunicationLog::class); } public function index() { $page = max(1, (int) ($this->request->getGet('page') ?? 1)); $perPage = min(100, max(1, (int) ($this->request->getGet('per_page') ?? 20))); $senderId = $this->request->getGet('sender_id'); $receiverId = $this->request->getGet('receiver_id'); $query = $this->communication->newQuery(); if ($senderId) { $query->where('sender_id', $senderId); } if ($receiverId) { $query->where('receiver_id', $receiverId); } $query->orderBy('created_at', 'DESC'); $result = $this->paginate($query, $page, $perPage); return $this->success($result, 'Communications retrieved successfully'); } public function send() { $data = $this->payloadData(); $rules = [ 'sender_id' => 'required|integer', 'receiver_id' => 'required|integer', 'subject' => 'required|string|max_length[255]', 'message' => 'required|string', ]; $errors = $this->validateRequest($data, $rules); if (!empty($errors)) { return $this->respondValidationError($errors); } $communication = $this->communication->create([ 'sender_id' => $data['sender_id'], 'receiver_id' => $data['receiver_id'], 'subject' => $data['subject'], 'message' => $data['message'], ]); return $this->respondCreated([ 'id' => $communication->id, 'data' => $communication, ], 'Message sent successfully'); } public function conversation() { $senderId = $this->request->getGet('sender_id'); $receiverId = $this->request->getGet('receiver_id'); if (!$senderId || !$receiverId) { return $this->respondValidationError([ 'sender_id' => 'sender_id is required', 'receiver_id' => 'receiver_id is required', ]); } $conversation = $this->communication->newQuery() ->where(function ($query) use ($senderId, $receiverId) { $query->where('sender_id', $senderId) ->where('receiver_id', $receiverId); }) ->orWhere(function ($query) use ($senderId, $receiverId) { $query->where('sender_id', $receiverId) ->where('receiver_id', $senderId); }) ->orderBy('created_at', 'ASC') ->get() ->toArray(); return $this->success($conversation, 'Conversation retrieved successfully'); } public function delete($id = null) { $message = $this->communication->find($id); if (!$message) { return $this->respondError('Message not found', Response::HTTP_NOT_FOUND); } $message->delete(); return $this->respondDeleted(null, 'Message deleted successfully'); } public function metadata() { $students = $this->student->newQuery() ->selectRaw('id, firstname, lastname, COALESCE(registration_grade, \'\') AS grade') ->orderBy('lastname', 'ASC') ->orderBy('firstname', 'ASC') ->get() ->toArray(); $templates = $this->template->getActiveTemplates(); return $this->success([ 'students' => $students, 'templates' => $templates, ], 'Communication metadata retrieved'); } public function families(int $studentId) { $families = $this->familyStudent->getFamiliesForStudent($studentId); return $this->success(['data' => $families], 'Families retrieved'); } public function guardians(int $familyId) { $rows = DB::table('family_guardians as fg') ->select('u.id AS user_id', 'u.firstname', 'u.lastname', 'u.email', 'fg.relation', 'fg.is_primary', 'fg.receive_emails', 'fg.receive_sms') ->join('users as u', 'u.id', '=', 'fg.user_id') ->where('fg.family_id', $familyId) ->get() ->map(fn ($row) => (array) $row) ->toArray(); return $this->success(['data' => $rows], 'Guardians retrieved'); } public function preview() { $data = $this->payloadData(); $rules = [ 'template_key' => 'required|string', 'student_id' => 'required|integer', 'family_id' => 'required|integer', 'vars' => 'permit_empty', ]; $errors = $this->validateRequest($data, $rules); if (!empty($errors)) { return $this->respondValidationError($errors); } $template = $this->template->findByKey((string) ($data['template_key'] ?? '')); if (!$template) { return $this->respondError('Template not found', Response::HTTP_NOT_FOUND); } $student = $this->student->find($data['student_id']); if (!$student) { return $this->respondError('Student not found', Response::HTTP_NOT_FOUND); } $guardianRows = DB::table('family_guardians as fg') ->select('u.firstname', 'u.lastname') ->join('users as u', 'u.id', '=', 'fg.user_id') ->where('fg.family_id', $data['family_id']) ->where('fg.receive_emails', 1) ->orderByDesc('fg.is_primary') ->orderBy('u.lastname') ->orderBy('u.firstname') ->get() ->map(fn ($row) => (array) $row) ->toArray(); $salutation = 'Parent/Guardian'; if (!empty($guardianRows)) { $names = array_map(fn ($row) => trim(($row['firstname'] ?? '') . ' ' . ($row['lastname'] ?? '')), $guardianRows); $names = array_filter($names, fn ($name) => $name !== ''); if (!empty($names)) { $salutation = implode(' & ', $names); } } $varsPost = $data['vars'] ?? null; $vars = $this->normalizeVars($varsPost); $autoVars = [ 'student_fullname' => trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? '')), 'student_grade' => $student['grade'] ?? $student['registration_grade'] ?? '', 'parent_salutation'=> $salutation, 'date' => local_date(utc_now(), 'Y-m-d'), 'school_name' => 'Al Rahma Sunday School', 'teacher_name' => (string) (session('display_name') ?? 'Teacher'), ]; $context = array_merge($autoVars, $vars); $subject = $this->renderTwig($template['subject'] ?? '', $context); $body = nl2br($this->renderTwig($template['body'] ?? '', $context)); return $this->success([ 'subject' => $subject, 'html' => $body, ], 'Template preview generated'); } public function sendEmail() { $data = $this->payloadData(); $rules = [ 'student_id' => 'required|integer', 'family_id' => 'required|integer', 'template_key' => 'required|string', 'subject' => 'required|string', 'body' => 'required|string', 'recipients' => 'required', ]; $errors = $this->validateRequest($data, $rules); if (!empty($errors)) { return $this->respondValidationError($errors); } $student = $this->student->find($data['student_id']); if (!$student) { return $this->respondError('Student not found', Response::HTTP_NOT_FOUND); } $recipients = $this->normalizeRecipients($data['recipients']); $cc = $this->normalizeRecipients($data['cc'] ?? null); $bcc = $this->normalizeRecipients($data['bcc'] ?? null); if (empty($recipients)) { return $this->respondError('At least one recipient is required'); } $bodyHtml = (string) ($data['body'] ?? ''); $subject = (string) ($data['subject'] ?? ''); $fromAddress = config('mail.from.address', 'no-reply@alrahmaisgl.org'); $fromName = config('mail.from.name', config('app.name', 'Al Rahma')); $status = 'failed'; $error = null; try { Mail::send([], [], function (Message $message) use ($fromAddress, $fromName, $subject, $bodyHtml, $recipients, $cc, $bcc) { $message->from($fromAddress, $fromName); $message->to($recipients); if (!empty($cc)) { $message->cc($cc); } if (!empty($bcc)) { $message->bcc($bcc); } $message->subject($subject); $message->setBody($bodyHtml, 'text/html'); $message->addPart(strip_tags($bodyHtml), 'text/plain'); }); $status = 'sent'; } catch (\Throwable $t) { $error = $t->getMessage(); } $studentName = trim(($student['firstname'] ?? '') . ' ' . ($student['lastname'] ?? '')); $this->log->insert([ 'student_id' => $data['student_id'], 'family_id' => $data['family_id'], 'student_name' => $studentName, 'template_key' => $data['template_key'], 'subject' => $subject, 'body' => $bodyHtml, 'recipients' => json_encode(array_values($recipients)), 'cc' => json_encode(array_values($cc)), 'bcc' => json_encode(array_values($bcc)), 'status' => $status, 'error_message' => $error, 'sent_by' => (int) (session('user_id') ?? 0), 'metadata' => null, ]); if ($status === 'sent') { return $this->success(['status' => 'sent'], 'Email sent successfully'); } return $this->error('Failed to send email: ' . ($error ?? 'Unknown error'), Response::HTTP_INTERNAL_SERVER_ERROR); } private function normalizeVars($value): array { if (is_string($value)) { $decoded = json_decode($value, true); if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) { return $decoded; } return []; } if (is_array($value)) { return $value; } return []; } private function normalizeRecipients($input): array { $values = []; if (is_string($input)) { $decoded = json_decode($input, true); if (json_last_error() === JSON_ERROR_NONE && is_array($decoded)) { $values = $decoded; } elseif ($input !== '') { $values = [$input]; } } elseif (is_array($input)) { $values = $input; } $sanitized = array_values(array_filter(array_map(fn ($item) => trim((string) $item), $values))); return $sanitized; } private function renderTwig(string $template, array $vars): string { return preg_replace_callback('/\{\{\s*([a-zA-Z0-9_\.]+)\s*\}\}/', function ($match) use ($vars) { $key = $match[1]; return htmlspecialchars((string) ($vars[$key] ?? ''), ENT_QUOTES, 'UTF-8'); }, $template); } }