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

Crest

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