CrestRoad/laravel/app/Services/SessionCommunicationService...

578 lines
19 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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);
}
}
}