314 lines
12 KiB
PHP
314 lines
12 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use CodeIgniter\Database\BaseConnection;
|
|
|
|
/**
|
|
* @deprecated Use EnrollmentRegistrationEmailService instead. This legacy sender is retained for reference only.
|
|
*/
|
|
class RegistrationOpeningEmailService
|
|
{
|
|
public const TEMPLATE_KEY = 'registration_opening';
|
|
|
|
private BaseConnection $db;
|
|
private EmailService $emailService;
|
|
|
|
public function __construct(?BaseConnection $db = null, ?EmailService $emailService = null)
|
|
{
|
|
$this->db = $db ?? \Config\Database::connect();
|
|
$this->emailService = $emailService ?? new EmailService();
|
|
}
|
|
|
|
public function sendForDate(\DateTimeInterface $date, bool $force = false, ?string $testEmail = null, bool $dryRun = false): array
|
|
{
|
|
$summary = [
|
|
'school_years' => 0,
|
|
'recipients' => 0,
|
|
'sent' => 0,
|
|
'failed' => 0,
|
|
'skipped' => 0,
|
|
'dry_run' => $dryRun,
|
|
'messages' => [],
|
|
];
|
|
|
|
foreach ($this->registrationYearsForDate($date, $force) as $schoolYear) {
|
|
$summary['school_years']++;
|
|
|
|
$recipients = $testEmail !== null
|
|
? [[
|
|
'user_id' => null,
|
|
'family_id' => null,
|
|
'email' => $testEmail,
|
|
'name' => 'Parent',
|
|
]]
|
|
: $this->parentEmailRecipients();
|
|
|
|
if ($recipients === []) {
|
|
$summary['messages'][] = sprintf('No parent emails found for %s.', $schoolYear['name']);
|
|
continue;
|
|
}
|
|
|
|
[$subject, $body] = $this->composeEmail($schoolYear);
|
|
if ($testEmail !== null) {
|
|
$subject = '[TEST] ' . $subject;
|
|
}
|
|
|
|
foreach ($recipients as $recipient) {
|
|
if ($testEmail === null && ! $dryRun && $this->alreadySentForRecipient($schoolYear, $recipient['email'])) {
|
|
$summary['skipped']++;
|
|
continue;
|
|
}
|
|
|
|
$summary['recipients']++;
|
|
$personalizedBody = $this->replaceTokens($body, $schoolYear, $recipient);
|
|
$personalizedSubject = $this->replaceTokens($subject, $schoolYear, $recipient);
|
|
|
|
$sent = $dryRun || $this->emailService->send($recipient['email'], $personalizedSubject, $personalizedBody, 'general');
|
|
$sent ? $summary['sent']++ : $summary['failed']++;
|
|
|
|
if (! $dryRun) {
|
|
$this->logAttempt($schoolYear, $recipient, $personalizedSubject, $personalizedBody, $sent);
|
|
}
|
|
}
|
|
}
|
|
|
|
return $summary;
|
|
}
|
|
|
|
public function registrationYearsForDate(\DateTimeInterface $date, bool $force = false): array
|
|
{
|
|
$builder = $this->db->table('school_years')
|
|
->where('registration_starts_on IS NOT NULL');
|
|
|
|
if (! $force) {
|
|
$builder->where('registration_starts_on', $date->format('Y-m-d'));
|
|
}
|
|
|
|
return $builder->orderBy('registration_starts_on', 'ASC')
|
|
->get()
|
|
->getResultArray();
|
|
}
|
|
|
|
public function parentEmailRecipients(): array
|
|
{
|
|
$recipients = [];
|
|
|
|
$hasFamilyGuardians = $this->db->tableExists('family_guardians');
|
|
|
|
if ($this->db->tableExists('roles') && $this->db->tableExists('user_roles')) {
|
|
$builder = $this->db->table('users u')
|
|
->select($hasFamilyGuardians ? 'u.id AS user_id, fg.family_id, u.firstname, u.lastname, u.email' : 'u.id AS user_id, NULL AS family_id, u.firstname, u.lastname, u.email')
|
|
->join('user_roles ur', 'ur.user_id = u.id', 'inner')
|
|
->join('roles r', 'r.id = ur.role_id', 'inner')
|
|
->where('LOWER(r.name)', 'parent')
|
|
->where('u.email IS NOT NULL')
|
|
->where('u.email !=', '');
|
|
|
|
if ($hasFamilyGuardians) {
|
|
$builder->join('family_guardians fg', 'fg.user_id = u.id', 'left')
|
|
->groupStart()
|
|
->where('fg.id IS NULL')
|
|
->orWhere('fg.receive_emails', 1)
|
|
->groupEnd();
|
|
}
|
|
|
|
if ($this->hasField('user_roles', 'deleted_at')) {
|
|
$builder->where('ur.deleted_at', null);
|
|
}
|
|
|
|
foreach ($builder->get()->getResultArray() as $row) {
|
|
$this->addRecipient($recipients, $row);
|
|
}
|
|
}
|
|
|
|
if ($hasFamilyGuardians) {
|
|
$guardianBuilder = $this->db->table('family_guardians fg')
|
|
->select('u.id AS user_id, fg.family_id, u.firstname, u.lastname, u.email')
|
|
->join('users u', 'u.id = fg.user_id', 'inner')
|
|
->where('fg.receive_emails', 1)
|
|
->where('u.email IS NOT NULL')
|
|
->where('u.email !=', '');
|
|
|
|
foreach ($guardianBuilder->get()->getResultArray() as $row) {
|
|
$this->addRecipient($recipients, $row);
|
|
}
|
|
}
|
|
|
|
uasort($recipients, static fn(array $a, array $b): int => strcasecmp($a['email'], $b['email']));
|
|
|
|
return array_values($recipients);
|
|
}
|
|
|
|
public function alreadySentForSchoolYear(array $schoolYear): bool
|
|
{
|
|
$schoolYearId = (int) ($schoolYear['id'] ?? 0);
|
|
$schoolYearName = (string) ($schoolYear['name'] ?? '');
|
|
|
|
$builder = $this->db->table('communication_logs')
|
|
->where('template_key', self::TEMPLATE_KEY)
|
|
->where('status', 'sent')
|
|
->groupStart();
|
|
|
|
if ($schoolYearId > 0) {
|
|
$builder->like('metadata', '"school_year_id":' . $schoolYearId);
|
|
}
|
|
|
|
if ($schoolYearName !== '') {
|
|
if ($schoolYearId > 0) {
|
|
$builder->orLike('metadata', '"school_year":"' . $this->escapeJsonLikeValue($schoolYearName) . '"');
|
|
} else {
|
|
$builder->like('metadata', '"school_year":"' . $this->escapeJsonLikeValue($schoolYearName) . '"');
|
|
}
|
|
}
|
|
|
|
$builder->groupEnd();
|
|
|
|
return $builder->countAllResults() > 0;
|
|
}
|
|
|
|
public function alreadySentForRecipient(array $schoolYear, string $email): bool
|
|
{
|
|
$schoolYearId = (int) ($schoolYear['id'] ?? 0);
|
|
$schoolYearName = (string) ($schoolYear['name'] ?? '');
|
|
|
|
$builder = $this->db->table('communication_logs')
|
|
->where('template_key', self::TEMPLATE_KEY)
|
|
->where('status', 'sent')
|
|
->like('recipients', '"' . strtolower(trim($email)) . '"')
|
|
->groupStart();
|
|
|
|
if ($schoolYearId > 0) {
|
|
$builder->like('metadata', '"school_year_id":' . $schoolYearId);
|
|
}
|
|
|
|
if ($schoolYearName !== '') {
|
|
if ($schoolYearId > 0) {
|
|
$builder->orLike('metadata', '"school_year":"' . $this->escapeJsonLikeValue($schoolYearName) . '"');
|
|
} else {
|
|
$builder->like('metadata', '"school_year":"' . $this->escapeJsonLikeValue($schoolYearName) . '"');
|
|
}
|
|
}
|
|
|
|
$builder->groupEnd();
|
|
|
|
return $builder->countAllResults() > 0;
|
|
}
|
|
|
|
private function addRecipient(array &$recipients, array $row): void
|
|
{
|
|
$email = strtolower(trim((string) ($row['email'] ?? '')));
|
|
if (! filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
|
return;
|
|
}
|
|
|
|
$name = trim((string) ($row['firstname'] ?? '') . ' ' . (string) ($row['lastname'] ?? ''));
|
|
$recipients[$email] = [
|
|
'user_id' => isset($row['user_id']) ? (int) $row['user_id'] : null,
|
|
'family_id' => isset($row['family_id']) ? (int) $row['family_id'] : null,
|
|
'email' => $email,
|
|
'name' => $name !== '' ? $name : 'Parent',
|
|
];
|
|
}
|
|
|
|
private function composeEmail(array $schoolYear): array
|
|
{
|
|
$template = $this->loadTemplate();
|
|
|
|
$subject = $template['subject'] ?? 'Registration is now open for {{school_year}}';
|
|
$bodyHtml = $template['body'] ?? $this->defaultBody();
|
|
$bodyHtml = $this->replaceTokens($bodyHtml, $schoolYear, ['name' => 'Parent', 'email' => '']);
|
|
|
|
$body = view('emails/_wrap_layout', [
|
|
'title' => $this->replaceTokens($subject, $schoolYear, ['name' => 'Parent', 'email' => '']),
|
|
'body_html' => $bodyHtml,
|
|
], ['saveData' => true]);
|
|
|
|
return [$subject, $body];
|
|
}
|
|
|
|
private function loadTemplate(): ?array
|
|
{
|
|
if (! $this->db->tableExists('email_templates')) {
|
|
return null;
|
|
}
|
|
|
|
$fields = $this->db->getFieldNames('email_templates');
|
|
$keyField = in_array('code', $fields, true) ? 'code' : 'template_key';
|
|
$bodyField = in_array('body_html', $fields, true) ? 'body_html' : 'body';
|
|
|
|
$row = $this->db->table('email_templates')
|
|
->select('subject, ' . $bodyField . ' AS body')
|
|
->where($keyField, self::TEMPLATE_KEY)
|
|
->where('is_active', 1)
|
|
->get()
|
|
->getRowArray();
|
|
|
|
return $row ?: null;
|
|
}
|
|
|
|
private function replaceTokens(string $text, array $schoolYear, array $recipient): string
|
|
{
|
|
$registrationEnds = trim((string) ($schoolYear['registration_ends_on'] ?? ''));
|
|
$deadline = $registrationEnds !== '' ? $registrationEnds : 'the posted deadline';
|
|
|
|
return strtr($text, [
|
|
'{{name}}' => (string) ($recipient['name'] ?? 'Parent'),
|
|
'{{parent_name}}' => (string) ($recipient['name'] ?? 'Parent'),
|
|
'{{school_year}}' => (string) ($schoolYear['name'] ?? ''),
|
|
'{{registration_starts_on}}' => (string) ($schoolYear['registration_starts_on'] ?? ''),
|
|
'{{registration_ends_on}}' => $registrationEnds,
|
|
'{{registration_deadline}}' => $deadline,
|
|
'{{registration_url}}' => site_url('user'),
|
|
'{{login_url}}' => site_url('login'),
|
|
'{{school_name}}' => 'Al Rahma Sunday School',
|
|
]);
|
|
}
|
|
|
|
private function defaultBody(): string
|
|
{
|
|
return <<<'HTML'
|
|
<p>Dear {{name}},</p>
|
|
<p>Registration is now open for the {{school_year}} school year.</p>
|
|
<p>Please complete your registration through the school portal. The registration deadline is {{registration_deadline}}.</p>
|
|
<p><a href="{{registration_url}}">Start registration</a></p>
|
|
<p>If you already have an account, you can also log in here: <a href="{{login_url}}">{{login_url}}</a></p>
|
|
<p>Regards,<br>{{school_name}}</p>
|
|
HTML;
|
|
}
|
|
|
|
private function logAttempt(array $schoolYear, array $recipient, string $subject, string $body, bool $sent): void
|
|
{
|
|
$this->db->table('communication_logs')->insert([
|
|
'student_id' => 0,
|
|
'family_id' => $recipient['family_id'] ?: null,
|
|
'student_name' => 'All Families',
|
|
'template_key' => self::TEMPLATE_KEY,
|
|
'subject' => $subject,
|
|
'body' => $body,
|
|
'recipients' => json_encode([$recipient['email']]),
|
|
'status' => $sent ? 'sent' : 'failed',
|
|
'error_message' => $sent ? null : 'Email send failed',
|
|
'sent_by' => null,
|
|
'metadata' => json_encode([
|
|
'campaign' => self::TEMPLATE_KEY,
|
|
'school_year_id' => (int) ($schoolYear['id'] ?? 0),
|
|
'school_year' => (string) ($schoolYear['name'] ?? ''),
|
|
'registration_starts_on' => $schoolYear['registration_starts_on'] ?? null,
|
|
'registration_ends_on' => $schoolYear['registration_ends_on'] ?? null,
|
|
'recipient_user_id' => $recipient['user_id'],
|
|
]),
|
|
]);
|
|
}
|
|
|
|
private function hasField(string $table, string $field): bool
|
|
{
|
|
return in_array($field, $this->db->getFieldNames($table), true);
|
|
}
|
|
|
|
private function escapeJsonLikeValue(string $value): string
|
|
{
|
|
return trim(json_encode($value), '"');
|
|
}
|
|
}
|