388 lines
12 KiB
PHP
388 lines
12 KiB
PHP
<?php
|
|
|
|
namespace Config;
|
|
|
|
use CodeIgniter\Config\BaseConfig;
|
|
use Config\Database;
|
|
|
|
class PtRenewalReminderConfig extends BaseConfig
|
|
{
|
|
public bool $enabled = false;
|
|
|
|
public bool $daily = false;
|
|
|
|
/** @var string[] Lowercase three-letter day abbreviations, e.g. mon, tue */
|
|
public array $days = [];
|
|
|
|
/** @var int[] Client type ids: 1=Group, 2=Individual */
|
|
public array $clientTypes = [];
|
|
|
|
/** Whitelisted: renewal_date | policy_end_date */
|
|
public string $dateField = 'renewal_date';
|
|
|
|
/** @var int[] Reminder offsets: positive = days before due, negative = days after due */
|
|
public array $daysBeforeList = [30];
|
|
|
|
public bool $includeClientEmail = false;
|
|
|
|
/** When true, also include policies whose dateField is already past */
|
|
public bool $includeOverdue = false;
|
|
|
|
/** @var string[] */
|
|
public array $toEmails = [];
|
|
|
|
/** @var string[] */
|
|
public array $ccEmails = [];
|
|
|
|
/** @var string[] */
|
|
public array $bccEmails = [];
|
|
|
|
public string $fromMail = '';
|
|
|
|
/** Loaded from notifications.subject when present */
|
|
public string $mailSubject = '';
|
|
|
|
/** Loaded from notifications.mail_content when present */
|
|
public string $mailContent = '';
|
|
|
|
public ?int $notificationId = null;
|
|
|
|
private const ALLOWED_DATE_FIELDS = ['renewal_date', 'policy_end_date'];
|
|
|
|
public const TEMPLATE_NAME = 'retail_reminder_mail';
|
|
|
|
public function __construct()
|
|
{
|
|
parent::__construct();
|
|
|
|
// Env defaults (seed / fallback)
|
|
$defaults = [
|
|
'enabled' => env('pt.renewalReminder.enabled', 'true'),
|
|
'daily' => env('pt.renewalReminder.daily', 'true'),
|
|
'days' => env('pt.renewalReminder.days', 'mon,tue,wed,thu,fri'),
|
|
'clientTypes' => env('pt.renewalReminder.clientTypes', '2'),
|
|
'dateField' => env('pt.renewalReminder.dateField', 'renewal_date'),
|
|
'daysBefore' => env('pt.renewalReminder.daysBefore', '30'),
|
|
'includeClientEmail' => env('pt.renewalReminder.includeClientEmail', 'true'),
|
|
'includeOverdue' => env('pt.renewalReminder.includeOverdue', 'false'),
|
|
'toEmails' => env('pt.renewalReminder.toEmails', ''),
|
|
'ccEmails' => env('pt.renewalReminder.ccEmails', ''),
|
|
'bccEmails' => env('pt.renewalReminder.bccEmails', ''),
|
|
'fromMail' => env('pt.renewalReminder.fromMail', ''),
|
|
];
|
|
|
|
$dbConfig = $this->loadConfigFromNotification();
|
|
$merged = array_merge($defaults, $dbConfig);
|
|
|
|
$this->applyMergedConfig($merged);
|
|
}
|
|
|
|
/**
|
|
* @return array<string, mixed>
|
|
*/
|
|
private function loadConfigFromNotification(): array
|
|
{
|
|
try {
|
|
$db = Database::connect();
|
|
$row = $db->table('notifications')
|
|
->where('template_name', self::TEMPLATE_NAME)
|
|
->groupStart()
|
|
->where('client_id', null)
|
|
->orWhere('client_id', 0)
|
|
->orWhere('client_id', '')
|
|
->groupEnd()
|
|
->orderBy('id', 'DESC')
|
|
->get()
|
|
->getRowArray();
|
|
|
|
if (empty($row)) {
|
|
log_message('error', '[PtRenewalReminderConfig] No notifications row for retail_reminder_mail; using env defaults');
|
|
return [];
|
|
}
|
|
|
|
$this->notificationId = isset($row['id']) ? (int) $row['id'] : null;
|
|
$this->mailSubject = trim((string) ($row['subject'] ?? ''));
|
|
$this->mailContent = (string) ($row['mail_content'] ?? '');
|
|
|
|
$json = trim((string) ($row['config_json'] ?? ''));
|
|
if ($json === '') {
|
|
log_message('error', '[PtRenewalReminderConfig] config_json empty for notification_id=' . ($this->notificationId ?? 'n/a'));
|
|
return [];
|
|
}
|
|
|
|
$decoded = json_decode($json, true);
|
|
if (! is_array($decoded)) {
|
|
log_message('error', '[PtRenewalReminderConfig] Invalid config_json for notification_id=' . ($this->notificationId ?? 'n/a'));
|
|
return [];
|
|
}
|
|
|
|
return $decoded;
|
|
} catch (\Throwable $e) {
|
|
log_message(
|
|
'error',
|
|
'[PtRenewalReminderConfig] Failed loading notification config: ' . $e->getMessage()
|
|
);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $merged
|
|
*/
|
|
private function applyMergedConfig(array $merged): void
|
|
{
|
|
$this->enabled = $this->toBool($merged['enabled'] ?? false);
|
|
$this->daily = $this->toBool($merged['daily'] ?? false);
|
|
|
|
$daysRaw = $merged['days'] ?? 'mon,tue,wed,thu,fri';
|
|
if (is_array($daysRaw)) {
|
|
$daysRaw = implode(',', $daysRaw);
|
|
}
|
|
$this->days = array_values(array_filter(array_map(
|
|
static fn (string $day): string => strtolower(substr(trim($day), 0, 3)),
|
|
explode(',', (string) $daysRaw)
|
|
)));
|
|
|
|
$clientTypesRaw = $merged['clientTypes'] ?? '2';
|
|
if (is_array($clientTypesRaw)) {
|
|
$this->clientTypes = array_values(array_unique(array_filter(array_map(
|
|
static fn ($type): int => (int) $type,
|
|
$clientTypesRaw
|
|
), static fn (int $type): bool => $type > 0)));
|
|
} else {
|
|
$this->clientTypes = array_values(array_unique(array_filter(array_map(
|
|
static fn (string $type): int => (int) trim($type),
|
|
explode(',', (string) $clientTypesRaw)
|
|
), static fn (int $type): bool => $type > 0)));
|
|
}
|
|
|
|
$dateField = strtolower(trim((string) ($merged['dateField'] ?? 'renewal_date')));
|
|
if (! in_array($dateField, self::ALLOWED_DATE_FIELDS, true)) {
|
|
log_message(
|
|
'error',
|
|
'[PtRenewalReminderConfig] Invalid dateField "' . $dateField . '", falling back to renewal_date'
|
|
);
|
|
$dateField = 'renewal_date';
|
|
}
|
|
$this->dateField = $dateField;
|
|
|
|
$this->daysBeforeList = self::parseDaysBeforeList($merged['daysBefore'] ?? 30);
|
|
|
|
$this->includeClientEmail = $this->toBool($merged['includeClientEmail'] ?? true);
|
|
$this->includeOverdue = $this->toBool($merged['includeOverdue'] ?? false);
|
|
|
|
$this->toEmails = $this->parseEmailList($merged['toEmails'] ?? '');
|
|
$this->ccEmails = $this->parseEmailList($merged['ccEmails'] ?? '');
|
|
$this->bccEmails = $this->parseEmailList($merged['bccEmails'] ?? '');
|
|
$this->fromMail = trim((string) ($merged['fromMail'] ?? ''));
|
|
}
|
|
|
|
public function shouldRunToday(): bool
|
|
{
|
|
if (! $this->enabled) {
|
|
return false;
|
|
}
|
|
|
|
if ($this->daily) {
|
|
return true;
|
|
}
|
|
|
|
$today = strtolower(date('D'));
|
|
|
|
return in_array($today, $this->days, true);
|
|
}
|
|
|
|
public function getDateField(): string
|
|
{
|
|
return $this->dateField;
|
|
}
|
|
|
|
/**
|
|
* First / primary offset (legacy callers).
|
|
*/
|
|
public function getDaysBefore(): int
|
|
{
|
|
return $this->daysBeforeList[0] ?? 30;
|
|
}
|
|
|
|
/**
|
|
* @return int[]
|
|
*/
|
|
public function getDaysBeforeList(): array
|
|
{
|
|
return $this->daysBeforeList;
|
|
}
|
|
|
|
public function getDaysBeforeDisplay(): string
|
|
{
|
|
return implode(', ', $this->daysBeforeList);
|
|
}
|
|
|
|
/**
|
|
* Map each offset to the calendar date it matches (today + offset).
|
|
*
|
|
* @return array<int, string> offset => Y-m-d
|
|
*/
|
|
public function getTargetDates(?string $today = null): array
|
|
{
|
|
$today = $today ?: date('Y-m-d');
|
|
$map = [];
|
|
foreach ($this->daysBeforeList as $offset) {
|
|
$map[(int) $offset] = date('Y-m-d', strtotime($today . ' ' . sprintf('%+d days', (int) $offset)));
|
|
}
|
|
|
|
return $map;
|
|
}
|
|
|
|
/**
|
|
* Parse "30, 15, 7, 1, -2, -5, -10" (or a single int / array) into unique offsets.
|
|
*
|
|
* @return int[]
|
|
*/
|
|
public static function parseDaysBeforeList(mixed $value): array
|
|
{
|
|
if (is_int($value) || is_float($value)) {
|
|
$raw = [(int) $value];
|
|
} elseif (is_array($value)) {
|
|
$raw = $value;
|
|
} else {
|
|
$raw = preg_split('/[,\s]+/', trim((string) $value), -1, PREG_SPLIT_NO_EMPTY) ?: [];
|
|
}
|
|
|
|
$out = [];
|
|
$seen = [];
|
|
foreach ($raw as $item) {
|
|
$item = trim((string) $item);
|
|
if ($item === '' || ! is_numeric($item)) {
|
|
continue;
|
|
}
|
|
$n = (int) $item;
|
|
if ($n > 365 || $n < -365 || isset($seen[$n])) {
|
|
continue;
|
|
}
|
|
$seen[$n] = true;
|
|
$out[] = $n;
|
|
}
|
|
|
|
return $out !== [] ? $out : [30];
|
|
}
|
|
|
|
/**
|
|
* @return int[]
|
|
*/
|
|
public function getClientTypes(): array
|
|
{
|
|
return $this->clientTypes;
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getToEmails(): array
|
|
{
|
|
return $this->toEmails;
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getCcEmails(): array
|
|
{
|
|
return $this->ccEmails;
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
public function getBccEmails(): array
|
|
{
|
|
return $this->bccEmails;
|
|
}
|
|
|
|
public function getFromMail(): string
|
|
{
|
|
return $this->fromMail;
|
|
}
|
|
|
|
public function includeClientEmail(): bool
|
|
{
|
|
return $this->includeClientEmail;
|
|
}
|
|
|
|
public function includeOverdue(): bool
|
|
{
|
|
return $this->includeOverdue;
|
|
}
|
|
|
|
public function getMailSubject(): string
|
|
{
|
|
return $this->mailSubject;
|
|
}
|
|
|
|
public function getMailContent(): string
|
|
{
|
|
return $this->mailContent;
|
|
}
|
|
|
|
/**
|
|
* Flatten config for UI / API response.
|
|
*
|
|
* @return array<string, mixed>
|
|
*/
|
|
public function toArray(): array
|
|
{
|
|
return [
|
|
'enabled' => $this->enabled,
|
|
'daily' => $this->daily,
|
|
'days' => implode(',', $this->days),
|
|
'clientTypes' => $this->clientTypes,
|
|
'dateField' => $this->dateField,
|
|
'daysBefore' => $this->getDaysBeforeDisplay(),
|
|
'includeClientEmail' => $this->includeClientEmail,
|
|
'includeOverdue' => $this->includeOverdue,
|
|
'toEmails' => implode(',', $this->toEmails),
|
|
'ccEmails' => implode(',', $this->ccEmails),
|
|
'bccEmails' => implode(',', $this->bccEmails),
|
|
'fromMail' => $this->fromMail,
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return string[]
|
|
*/
|
|
private function parseEmailList(mixed $value): array
|
|
{
|
|
if ($value === null || $value === '') {
|
|
return [];
|
|
}
|
|
|
|
if (is_array($value)) {
|
|
$emails = array_map('trim', $value);
|
|
} else {
|
|
$emails = array_map('trim', explode(',', (string) $value));
|
|
}
|
|
|
|
$unique = [];
|
|
foreach ($emails as $email) {
|
|
if ($email === '' || ! filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
|
continue;
|
|
}
|
|
$key = strtolower($email);
|
|
if (! isset($unique[$key])) {
|
|
$unique[$key] = $email;
|
|
}
|
|
}
|
|
|
|
return array_values($unique);
|
|
}
|
|
|
|
private function toBool(mixed $value): bool
|
|
{
|
|
if (is_bool($value)) {
|
|
return $value;
|
|
}
|
|
|
|
return in_array(strtolower((string) $value), ['1', 'true', 'yes', 'on'], true);
|
|
}
|
|
}
|