AlkantarClanX12
| Current Path : /www/capitalgmcbuickregina_830/public/wp-content/plugins/leadbox/classes/ |
| Current File : /www/capitalgmcbuickregina_830/public/wp-content/plugins/leadbox/classes/lead-router.php |
<?php if (!defined('ABSPATH')) exit;
/**
* LBT-1468 — Routes a lead to the legacy backend, Leadbox Platform, or both,
* based on the configured lead_intake_target.
*
* The return value is always the standardized response array
* ({ success, data, count, message, error_code, http_status, latency_ms,
* timestamp }) of the AUTHORITATIVE backend, so the Ninja Forms action's
* success / retry logic is unchanged regardless of target:
*
* - legacy => returns the legacy result (authoritative).
* - leadbox_os => returns the Leadbox Platform result (authoritative).
* - both => returns the LEGACY result (authoritative); Leadbox Platform is
* dispatched AFTER the HTTP response is sent to the visitor
* (see flush_os_queue) so it never blocks or fails the legacy
* POST, never enqueues a retry, and never adds latency the
* visitor can feel — while still capturing the real outcome.
*
* Per-dispatch outcomes are written to a dedicated text log (LB_Lead_Dispatch_Log)
* whenever target !== 'legacy', so default-legacy sites stay silent.
*
* @see classes/os-api.php, classes/lead-mapper.php, classes/settings.php,
* classes/lead-dispatch-log.php
*/
final class LB_Lead_Router
{
/** @var array Pending Leadbox Platform dispatches to fire post-response ('both' mode). */
private $os_queue = array();
/** @var bool Whether the shutdown flush handler has been registered. */
private $shutdown_hooked = false;
public static function getInstance()
{
static $inst = null;
if ($inst === null) {
$inst = new LB_Lead_Router();
}
return $inst;
}
/**
* @param LB_Lead_Dto $lead
* @param array $action_settings Optional per-call context. Recognized keys:
* - nf_submission_id : Ninja Forms entry id (for log correlation)
* - is_retry : bool, set by the retry queue
* @return array Standardized response from the authoritative backend.
*/
public function send($lead, $action_settings = array())
{
if (false === ($lead instanceof LB_Lead_Dto)) {
throw new Exception('$lead needs to be instance of Lead Object');
}
$target = LB_API_Settings::getInstance()->getLeadIntakeTarget();
$form_id = $lead->getFormId();
switch ($target) {
case 'leadbox_os':
// Authoritative: must run inline/blocking, the form's success depends on it.
$result = LB_OS_API_Client::getInstance()->CreateLead($lead);
$this->log_dispatch($target, 'leadbox_os', $form_id, $result, $lead, $action_settings);
return $result;
case 'both':
// Legacy is authoritative and drives the action's success/retry path.
$result = LB_API_Client::getInstance()->CreateLead($lead);
$this->log_dispatch($target, 'legacy', $form_id, $result, $lead, $action_settings);
// Leadbox Platform copy is dispatched post-response (see flush_os_queue);
// its outcome is logged but never affects the return value or the retry enqueue.
$this->enqueue_os_dispatch($lead, $form_id, $action_settings);
return $result;
case 'legacy':
default:
$result = LB_API_Client::getInstance()->CreateLead($lead);
$this->log_dispatch($target, 'legacy', $form_id, $result, $lead, $action_settings);
return $result;
}
}
/**
* Queue a Leadbox Platform dispatch for the 'both' dual-write mode and make
* sure the post-response flush handler is registered.
*/
private function enqueue_os_dispatch($lead, $form_id, $action_settings)
{
$this->os_queue[] = array(
'lead' => $lead,
'form_id' => $form_id,
'action_settings' => is_array($action_settings) ? $action_settings : array(),
);
if (!$this->shutdown_hooked) {
$this->shutdown_hooked = true;
// Low priority so it runs after WordPress has produced the page output.
add_action('shutdown', array($this, 'flush_os_queue'), 999);
}
}
/**
* Fire the queued Leadbox Platform dispatches AFTER the response is sent.
*
* On PHP-FPM we call fastcgi_finish_request() first: the visitor's browser
* already has its response and the connection is closed, so we can safely
* BLOCK on the platform POST and read the real HTTP status/error to log it —
* with zero perceived latency. Without FPM we fall back to fire-and-forget so
* we never delay the visitor (no status available there; logged as such).
*
* Fully isolated: any error is swallowed so it can never disturb the request.
*/
public function flush_os_queue()
{
if (empty($this->os_queue)) {
return;
}
$can_block = function_exists('fastcgi_finish_request');
if ($can_block) {
@fastcgi_finish_request();
}
$queue = $this->os_queue;
$this->os_queue = array();
foreach ($queue as $item) {
try {
// blocking = $can_block: read the real status only once the visitor is served.
$os_result = LB_OS_API_Client::getInstance()->CreateLead($item['lead'], $can_block);
$extra = $item['action_settings'];
if (!$can_block) {
// Fire-and-forget: no readable status on this server.
$extra['note'] = 'sent_no_confirmation';
}
$this->log_dispatch('both', 'leadbox_os', $item['form_id'], $os_result, $item['lead'], $extra);
} catch (Exception $e) {
if (function_exists('leadbox_logger')) {
leadbox_logger()->error('Leadbox Platform dual-write threw', array(
'form_id' => $item['form_id'],
'error' => $e->getMessage(),
));
}
}
}
}
/**
* Per-dispatch observability. Keeps the existing PSR-3 trail (info on success,
* error on failure) AND, when the platform is in play (target !== 'legacy'),
* writes a structured line to the dedicated dispatch text log.
*
* @param string $target Configured target (legacy|leadbox_os|both).
* @param string $backend Which backend this dispatch hit (legacy|leadbox_os).
* @param int $form_id
* @param array $result Standardized response array.
* @param LB_Lead_Dto $lead Source lead (for masked contact in the log).
* @param array $extra Optional: nf_submission_id, is_retry, note.
*/
private function log_dispatch($target, $backend, $form_id, $result, $lead = null, $extra = array())
{
$success = is_array($result) && !empty($result['success']);
$error_code = is_array($result) && isset($result['error_code']) ? $result['error_code'] : null;
// Existing structured-logger trail (unchanged behavior).
if (function_exists('leadbox_logger')) {
$context = array(
'target' => $target,
'backend' => $backend,
'form_id' => $form_id,
'success' => $success,
'error_code' => $error_code,
);
if ($success) {
leadbox_logger()->info('lead_dispatch', $context);
} else {
leadbox_logger()->error('lead_dispatch_failed', $context);
}
}
// Dedicated dispatch text log — only when the platform is involved, so
// default-legacy sites produce no output. (LBT-1468 dispatch logging)
if ($target !== 'legacy' && class_exists('LB_Lead_Dispatch_Log')) {
if (!is_array($extra)) {
$extra = array();
}
$data = (is_array($result) && isset($result['data']) && is_array($result['data'])) ? $result['data'] : array();
$lead_id = null;
foreach (array('lead_id', 'conversation_id', 'Id') as $k) {
if (isset($data[$k]) && $data[$k] !== '') {
$lead_id = $data[$k];
break;
}
}
$lead_arr = ($lead instanceof LB_Lead_Dto) ? $lead->ToArray() : array();
$email = isset($lead_arr['email']) ? $lead_arr['email'] : '';
$phone = '';
foreach (array('phone', 'phonenumber') as $k) {
if (!empty($lead_arr[$k])) {
$phone = $lead_arr[$k];
break;
}
}
LB_Lead_Dispatch_Log::record(array(
'target' => $target,
'backend' => $backend,
'form_id' => $form_id,
'nf_submission_id' => isset($extra['nf_submission_id']) ? $extra['nf_submission_id'] : null,
'success' => $success,
'http_status' => is_array($result) && isset($result['http_status']) ? $result['http_status'] : null,
'error_code' => $error_code,
'message' => is_array($result) && isset($result['message']) ? $result['message'] : null,
'latency_ms' => is_array($result) && isset($result['latency_ms']) ? $result['latency_ms'] : null,
'lead_id' => $lead_id,
'is_retry' => !empty($extra['is_retry']),
'email' => $email,
'phone' => $phone,
'note' => isset($extra['note']) ? $extra['note'] : null,
));
}
}
}