578 lines
19 KiB
PHP
578 lines
19 KiB
PHP
<?php
|
||
|
||
namespace App\Services;
|
||
|
||
use App\Http\Requests\LambdaRequest;
|
||
use App\Models\Session;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Illuminate\Support\Facades\Log;
|
||
|
||
class SessionCommunicationService
|
||
{
|
||
public const EMAIL_SUBJECT_PREFIX = 'CREST Research and Training Support';
|
||
|
||
protected static $feedbackTemplateCodes = [
|
||
'crest_thankyou_and_feedback',
|
||
'crest_feedback_new',
|
||
'post_impact_assessment',
|
||
'pre_impact_assessment',
|
||
];
|
||
|
||
/**
|
||
* Normalize Lambda contact/get_all response items to a foreach-safe array.
|
||
*/
|
||
public static function extractContactItems($response): array
|
||
{
|
||
if (!is_object($response) || !isset($response->data)) {
|
||
return [];
|
||
}
|
||
|
||
if (!isset($response->data->items)) {
|
||
return [];
|
||
}
|
||
|
||
$items = $response->data->items;
|
||
|
||
if (is_array($items)) {
|
||
return $items;
|
||
}
|
||
|
||
if (!is_object($items)) {
|
||
return [];
|
||
}
|
||
|
||
// Single contact object stored in items (not a list wrapper).
|
||
if (isset($items->id) && (isset($items->phone) || isset($items->name) || isset($items->email))) {
|
||
return [$items];
|
||
}
|
||
|
||
return array_values((array) $items);
|
||
}
|
||
|
||
public function resolveSessionForMode($session): ?Session
|
||
{
|
||
if ($session instanceof Session) {
|
||
return $session;
|
||
}
|
||
|
||
if (is_numeric($session)) {
|
||
return Session::find((int) $session);
|
||
}
|
||
|
||
if (!is_object($session)) {
|
||
return null;
|
||
}
|
||
|
||
if (!empty($session->session_id)) {
|
||
$loaded = Session::find($session->session_id);
|
||
if ($loaded) {
|
||
return $loaded;
|
||
}
|
||
}
|
||
|
||
if (!empty($session->code)) {
|
||
$loaded = Session::where('code', $session->code)->first();
|
||
if ($loaded) {
|
||
return $loaded;
|
||
}
|
||
}
|
||
|
||
if (!empty($session->id) && empty($session->template_code ?? null)) {
|
||
return Session::find($session->id);
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
public function resolveMode($session): string
|
||
{
|
||
$sessionModel = $this->resolveSessionForMode($session);
|
||
|
||
if (!$sessionModel) {
|
||
if (is_object($session) && !empty($session->communication_mode)) {
|
||
$mode = strtolower(trim((string) $session->communication_mode));
|
||
if (in_array($mode, [Session::MODE_WHATSAPP, Session::MODE_EMAIL, Session::MODE_BOTH], true)) {
|
||
return $mode;
|
||
}
|
||
}
|
||
|
||
return Session::MODE_WHATSAPP;
|
||
}
|
||
|
||
$mode = strtolower(trim((string) ($sessionModel->communication_mode ?? '')));
|
||
|
||
if (!in_array($mode, [Session::MODE_WHATSAPP, Session::MODE_EMAIL, Session::MODE_BOTH], true)) {
|
||
return Session::MODE_WHATSAPP;
|
||
}
|
||
|
||
return $mode;
|
||
}
|
||
|
||
public function resolveSessionId($session): int
|
||
{
|
||
$sessionModel = $this->resolveSessionForMode($session);
|
||
if ($sessionModel) {
|
||
return (int) $sessionModel->id;
|
||
}
|
||
|
||
if (is_object($session)) {
|
||
return (int) ($session->session_id ?? $session->id ?? 0);
|
||
}
|
||
|
||
return (int) ($session['session_id'] ?? $session['id'] ?? 0);
|
||
}
|
||
|
||
public function shouldSendWhatsApp(string $mode): bool
|
||
{
|
||
return in_array($mode, [Session::MODE_WHATSAPP, Session::MODE_BOTH], true);
|
||
}
|
||
|
||
public function shouldSendEmail(string $mode): bool
|
||
{
|
||
return in_array($mode, [Session::MODE_EMAIL, Session::MODE_BOTH], true);
|
||
}
|
||
|
||
public function normalizeModeForSave(?string $mode): string
|
||
{
|
||
$mode = strtolower(trim((string) $mode));
|
||
|
||
if (!in_array($mode, [Session::MODE_WHATSAPP, Session::MODE_EMAIL, Session::MODE_BOTH], true)) {
|
||
return Session::MODE_WHATSAPP;
|
||
}
|
||
|
||
return $mode;
|
||
}
|
||
|
||
public function buildSessionVariables($session, $sendRow = null): array
|
||
{
|
||
$sessionModel = $this->resolveSessionForMode($session);
|
||
$session = $sessionModel ?: (is_object($session) ? $session : (object) $session);
|
||
|
||
return [
|
||
'SESSION_NAME' => $session->number ?? '',
|
||
'SESSION_DATE' => isset($session->start_date)
|
||
? date('d M, Y', strtotime($session->start_date))
|
||
: '',
|
||
'SESSION_TYPE' => strtoupper($session->type ?? ''),
|
||
'SESSION_BROCHURE' => 'https://road.xltr.in/brochure/' . ($session->code ?? ''),
|
||
'SESSION_CODE' => $session->code ?? '',
|
||
];
|
||
}
|
||
|
||
public function applyFeedbackSessionCode(string $sessionCode, $contact, string $templateCode): string
|
||
{
|
||
if (!in_array($templateCode, self::$feedbackTemplateCodes, true)) {
|
||
return $sessionCode;
|
||
}
|
||
|
||
$contactId = is_object($contact) ? ($contact->id ?? '') : ($contact['id'] ?? '');
|
||
|
||
return $sessionCode . '_' . $contactId;
|
||
}
|
||
|
||
public function buildContactVariables($contact, $session, $sendRow): array
|
||
{
|
||
$sessionVars = $this->buildSessionVariables($session, $sendRow);
|
||
$templateCode = is_object($sendRow)
|
||
? ($sendRow->template_code ?? '')
|
||
: ($sendRow['template_code'] ?? '');
|
||
|
||
$sessionCode = $this->applyFeedbackSessionCode(
|
||
$sessionVars['SESSION_CODE'],
|
||
$contact,
|
||
$templateCode
|
||
);
|
||
|
||
$sendId = is_object($sendRow) ? ($sendRow->id ?? 0) : ($sendRow['id'] ?? 0);
|
||
|
||
$vars = array_merge($sessionVars, [
|
||
'SESSION_CODE' => $sessionCode,
|
||
'RECIPIENT_NAME' => is_object($contact) ? ($contact->name ?? '') : ($contact['name'] ?? ''),
|
||
'CERT_PARAM' => (is_object($contact) ? ($contact->phone ?? '') : ($contact['phone'] ?? '')) . '_' . $sendId,
|
||
]);
|
||
|
||
if (is_object($contact)) {
|
||
foreach (get_object_vars($contact) as $key => $value) {
|
||
if (is_string($key) && $key !== '' && !is_array($value) && !is_object($value)) {
|
||
$vars[strtoupper($key)] = (string) $value;
|
||
}
|
||
}
|
||
}
|
||
|
||
return $vars;
|
||
}
|
||
|
||
public function substituteParams(?string $pipeList, array $vars): array
|
||
{
|
||
if ($pipeList === null || $pipeList === '') {
|
||
return [];
|
||
}
|
||
|
||
$params = explode('||', $pipeList);
|
||
foreach ($params as &$var) {
|
||
if (isset($vars[$var]) && $vars[$var] !== '') {
|
||
$var = $vars[$var];
|
||
}
|
||
}
|
||
|
||
return $params;
|
||
}
|
||
|
||
public function renderTemplateBody(string $body, array $vars, array $numericValues = []): string
|
||
{
|
||
$rendered = $body;
|
||
|
||
foreach ($vars as $key => $value) {
|
||
$rendered = str_replace('{{' . $key . '}}', $value, $rendered);
|
||
$rendered = str_replace('{' . $key . '}', $value, $rendered);
|
||
}
|
||
|
||
foreach ($numericValues as $index => $value) {
|
||
$placeholder = '{{' . ($index + 1) . '}}';
|
||
$rendered = str_replace($placeholder, (string) $value, $rendered);
|
||
}
|
||
|
||
return $rendered;
|
||
}
|
||
|
||
/**
|
||
* Ensure absolute URLs (e.g. road.xltr.in/path → https://road.xltr.in/path).
|
||
*/
|
||
public function normalizeUrlScheme(string $url): string
|
||
{
|
||
$url = trim($url);
|
||
if ($url === '') {
|
||
return '';
|
||
}
|
||
|
||
if (!preg_match('#^https?://#i', $url)) {
|
||
$url = 'https://' . ltrim($url, '/');
|
||
}
|
||
|
||
return $url;
|
||
}
|
||
|
||
public function buildButtonSection(array $buttonValues): string
|
||
{
|
||
if (empty($buttonValues)) {
|
||
return '';
|
||
}
|
||
|
||
$lines = ["\n"];
|
||
foreach ($buttonValues as $i => $value) {
|
||
$value = trim((string) $value);
|
||
if ($value === '') {
|
||
continue;
|
||
}
|
||
if (filter_var($value, FILTER_VALIDATE_URL)) {
|
||
$lines[] = 'Link: ' . $value;
|
||
} else {
|
||
$lines[] = $value;
|
||
}
|
||
}
|
||
|
||
return implode("\n", $lines);
|
||
}
|
||
|
||
/**
|
||
* Build email CTA button from template button_url / button_label + substituted params.
|
||
*/
|
||
public function buildEmailCallToAction(array $config, array $vars, array $buttonParamValues = []): string
|
||
{
|
||
$urlTemplate = trim($config['button_url'] ?? '');
|
||
$label = trim($config['button_label'] ?? '');
|
||
|
||
if ($urlTemplate === '') {
|
||
return '';
|
||
}
|
||
|
||
$url = $this->renderTemplateBody($urlTemplate, $vars, $buttonParamValues);
|
||
$url = $this->normalizeUrlScheme($url);
|
||
|
||
if (!filter_var($url, FILTER_VALIDATE_URL)) {
|
||
$paramValue = trim((string) ($buttonParamValues[0] ?? ''));
|
||
if ($paramValue !== '' && filter_var($this->normalizeUrlScheme($paramValue), FILTER_VALIDATE_URL)) {
|
||
$url = $this->normalizeUrlScheme($paramValue);
|
||
} elseif ($paramValue !== '') {
|
||
$base = $this->normalizeUrlScheme(
|
||
$this->renderTemplateBody(rtrim($urlTemplate, '/'), $vars, $buttonParamValues)
|
||
);
|
||
$url = rtrim($base, '/') . '/' . ltrim($paramValue, '/');
|
||
}
|
||
}
|
||
|
||
$url = $this->normalizeUrlScheme($url);
|
||
|
||
if ($url === '' || !filter_var($url, FILTER_VALIDATE_URL)) {
|
||
return '';
|
||
}
|
||
|
||
if ($label === '') {
|
||
$label = 'Click here';
|
||
}
|
||
$label = $this->renderTemplateBody($label, $vars, $buttonParamValues);
|
||
|
||
return '<table role="presentation" cellspacing="0" cellpadding="0" border="0" style="margin:28px auto 8px;">'
|
||
. '<tr><td align="center" style="border-radius:6px;background-color:#1a3a5c;">'
|
||
. '<a href="' . e($url) . '" target="_blank" rel="noopener noreferrer" '
|
||
. 'style="display:inline-block;padding:14px 28px;font-size:15px;font-weight:bold;color:#ffffff;text-decoration:none;">'
|
||
. e($label)
|
||
. '</a></td></tr></table>';
|
||
}
|
||
|
||
public function buildInteraktTemplate(array $config, array $vars): array
|
||
{
|
||
if (!empty($config['header_values']) && is_array($config['header_values'])) {
|
||
$headerParams = $config['header_values'];
|
||
} else {
|
||
$headerParams = $this->substituteParams($config['header_params'] ?? '', $vars);
|
||
}
|
||
|
||
$bodyParams = $this->substituteParams($config['body_params'] ?? '', $vars);
|
||
$buttonParams = $this->substituteParams($config['button_params'] ?? '', $vars);
|
||
|
||
$template = [
|
||
'name' => $config['template_code'],
|
||
'languageCode' => 'en',
|
||
];
|
||
|
||
if (count($headerParams) > 0) {
|
||
$template['headerValues'] = $headerParams;
|
||
}
|
||
if (count($bodyParams) > 0) {
|
||
$template['bodyValues'] = $bodyParams;
|
||
}
|
||
if (count($buttonParams) > 0) {
|
||
$template['buttonValues'] = (object) ['0' => $buttonParams];
|
||
}
|
||
|
||
return $template;
|
||
}
|
||
|
||
public function buildEmailBody(string $templateBody, array $vars, array $buttonValues = [], array $headerValues = [], array $numericValues = [], array $config = []): string
|
||
{
|
||
$body = $this->renderTemplateBody($templateBody, $vars, $numericValues);
|
||
|
||
$imageHtml = '';
|
||
foreach ($headerValues as $headerValue) {
|
||
if (filter_var($headerValue, FILTER_VALIDATE_URL)) {
|
||
$imageHtml .= '<p><img src="' . e($headerValue) . '" alt="Crest" style="max-width:100%;height:auto;" /></p>';
|
||
}
|
||
}
|
||
|
||
$ctaHtml = $this->buildEmailCallToAction($config, $vars, $buttonValues);
|
||
if ($ctaHtml === '') {
|
||
$body .= $this->buildButtonSection($buttonValues);
|
||
}
|
||
|
||
return $imageHtml . nl2br(e($body)) . $ctaHtml;
|
||
}
|
||
|
||
public function buildEmailSubject($session, $sendRow): string
|
||
{
|
||
$sessionName = is_object($session) ? ($session->number ?? 'Session') : ($session['number'] ?? 'Session');
|
||
$title = is_object($sendRow) ? ($sendRow->template_title ?? null) : ($sendRow['template_title'] ?? null);
|
||
$prefix = self::EMAIL_SUBJECT_PREFIX;
|
||
|
||
if (!empty($title)) {
|
||
return $prefix . ' – ' . $title;
|
||
}
|
||
|
||
return $prefix . ' – ' . $sessionName;
|
||
}
|
||
|
||
public function contactEmail($contact): ?string
|
||
{
|
||
$candidates = [];
|
||
if (is_object($contact)) {
|
||
$candidates = [
|
||
$contact->email ?? null,
|
||
$contact->user_email ?? null,
|
||
$contact->Email ?? null,
|
||
];
|
||
} elseif (is_array($contact)) {
|
||
$candidates = [
|
||
$contact['email'] ?? null,
|
||
$contact['user_email'] ?? null,
|
||
];
|
||
}
|
||
|
||
foreach ($candidates as $email) {
|
||
$email = trim((string) $email);
|
||
if ($email !== '' && filter_var($email, FILTER_VALIDATE_EMAIL)) {
|
||
return $email;
|
||
}
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* Load full contact from Lambda when get_all omitted email (fields projection).
|
||
*/
|
||
public function enrichContactEmail($contact)
|
||
{
|
||
if ($this->contactEmail($contact)) {
|
||
return $contact;
|
||
}
|
||
|
||
$contactId = is_object($contact) ? ($contact->id ?? null) : ($contact['id'] ?? null);
|
||
if (!$contactId) {
|
||
return $contact;
|
||
}
|
||
|
||
try {
|
||
$response = LambdaRequest::getrequest(
|
||
'contact',
|
||
'contact/get_one',
|
||
['id' => $contactId],
|
||
env('LAMBDA_ACCESS_TOKEN', 'default_value')
|
||
);
|
||
|
||
if (!empty($response->data->item->email) && is_object($contact)) {
|
||
$contact->email = $response->data->item->email;
|
||
}
|
||
} catch (\Throwable $e) {
|
||
Log::warning('enrichContactEmail failed for contact ' . $contactId . ': ' . $e->getMessage());
|
||
}
|
||
|
||
return $contact;
|
||
}
|
||
|
||
public function queueWhatsApp($contact, int $sessionId, array $interaktPayload): void
|
||
{
|
||
$phone = is_object($contact) ? ($contact->phone ?? '') : ($contact['phone'] ?? '');
|
||
$contactId = is_object($contact) ? ($contact->id ?? null) : ($contact['id'] ?? null);
|
||
|
||
DB::table('raphiwa_send')->insert([
|
||
'to' => $phone,
|
||
'text' => json_encode($interaktPayload),
|
||
'contact_id' => $contactId,
|
||
'session_id' => $sessionId,
|
||
'is_sent' => 0,
|
||
'status' => 1,
|
||
'created_at' => now(),
|
||
'updated_at' => now(),
|
||
]);
|
||
}
|
||
|
||
public function queueEmail($contact, int $sessionId, string $subject, string $bodyHtml, ?string $templateCode = null): bool
|
||
{
|
||
$contact = $this->enrichContactEmail($contact);
|
||
$email = $this->contactEmail($contact);
|
||
if (!$email) {
|
||
$contactId = is_object($contact) ? ($contact->id ?? 'unknown') : 'unknown';
|
||
Log::info('email_send skipped: no valid email on contact', [
|
||
'contact_id' => $contactId,
|
||
'session_id' => $sessionId,
|
||
]);
|
||
return false;
|
||
}
|
||
|
||
$contactId = is_object($contact) ? ($contact->id ?? null) : ($contact['id'] ?? null);
|
||
|
||
DB::table('email_send')->insert([
|
||
'to' => $email,
|
||
'subject' => $subject,
|
||
'body_html' => $bodyHtml,
|
||
'contact_id' => $contactId,
|
||
'session_id' => $sessionId,
|
||
'template_code' => $templateCode,
|
||
'is_sent' => 0,
|
||
'status' => 1,
|
||
'created_at' => now(),
|
||
'updated_at' => now(),
|
||
]);
|
||
|
||
return true;
|
||
}
|
||
|
||
public function queueEmailFromText($session, $contact, string $plainText, ?string $templateCode = null): bool
|
||
{
|
||
if (!$this->shouldSendEmail($this->resolveMode($session))) {
|
||
return false;
|
||
}
|
||
|
||
$sessionId = is_object($session) ? (int) ($session->id ?? 0) : (int) ($session['id'] ?? 0);
|
||
$subject = 'Crest – ' . (is_object($session) ? ($session->number ?? 'Message') : ($session['number'] ?? 'Message'));
|
||
$bodyHtml = nl2br(e($plainText));
|
||
|
||
return $this->queueEmail($contact, $sessionId, $subject, $bodyHtml, $templateCode);
|
||
}
|
||
|
||
public function dispatchForContact($session, $contact, array $config): void
|
||
{
|
||
$sessionModel = $this->resolveSessionForMode($session);
|
||
$mode = $this->resolveMode($session);
|
||
$sessionId = $this->resolveSessionId($session);
|
||
$vars = $this->buildContactVariables($contact, $sessionModel ?: $session, $config);
|
||
|
||
if (!empty($config['header_values']) && is_array($config['header_values'])) {
|
||
$headerParams = $config['header_values'];
|
||
} else {
|
||
$headerParams = $this->substituteParams($config['header_params'] ?? '', $vars);
|
||
}
|
||
|
||
$bodyParams = $this->substituteParams($config['body_params'] ?? '', $vars);
|
||
$buttonParams = $this->substituteParams($config['button_params'] ?? '', $vars);
|
||
|
||
if ($this->shouldSendWhatsApp($mode)) {
|
||
$phone = is_object($contact) ? ($contact->phone ?? '') : ($contact['phone'] ?? '');
|
||
$contactId = is_object($contact) ? ($contact->id ?? '') : ($contact['id'] ?? '');
|
||
|
||
$template = $this->buildInteraktTemplate($config, $vars);
|
||
$params = [
|
||
'countryCode' => '+91',
|
||
'phoneNumber' => $phone,
|
||
'callbackData' => $phone . '_' . $contactId,
|
||
'type' => 'Template',
|
||
'template' => $template,
|
||
];
|
||
|
||
$this->queueWhatsApp($contact, $sessionId, $params);
|
||
}
|
||
|
||
if ($this->shouldSendEmail($mode)) {
|
||
$templateBody = trim($config['template_body'] ?? '');
|
||
if ($templateBody !== '') {
|
||
$bodyHtml = $this->buildEmailBody($templateBody, $vars, $buttonParams, $headerParams, $bodyParams, $config);
|
||
$subject = $this->buildEmailSubject($sessionModel ?: $session, $config);
|
||
$this->queueEmail(
|
||
$contact,
|
||
$sessionId,
|
||
$subject,
|
||
$bodyHtml,
|
||
$config['template_code'] ?? null
|
||
);
|
||
} else {
|
||
Log::warning('email_send skipped: empty template_body', [
|
||
'session_id' => $sessionId,
|
||
'template_code' => $config['template_code'] ?? null,
|
||
]);
|
||
}
|
||
}
|
||
}
|
||
|
||
public function dispatchPlainText($session, $contact, string $message, bool $sendWhatsApp = true): void
|
||
{
|
||
$mode = $this->resolveMode($session);
|
||
$sessionId = is_object($session) ? (int) ($session->id ?? 0) : (int) ($session['id'] ?? 0);
|
||
|
||
if ($sendWhatsApp && $this->shouldSendWhatsApp($mode)) {
|
||
$phone = is_object($contact) ? ($contact->phone ?? '') : ($contact['phone'] ?? '');
|
||
$params = [
|
||
'countryCode' => '+91',
|
||
'phoneNumber' => $phone,
|
||
'callbackData' => 'email_mirror',
|
||
'type' => 'Text',
|
||
'data' => ['message' => $message],
|
||
];
|
||
$this->queueWhatsApp($contact, $sessionId, $params);
|
||
}
|
||
|
||
if ($this->shouldSendEmail($mode)) {
|
||
$this->queueEmailFromText($session, $contact, $message);
|
||
}
|
||
}
|
||
}
|