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 '
';
}
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 .= ' . ')
';
}
}
$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);
}
}
}