AlkantarClanX12

Your IP : 216.73.217.24


Current Path : /www/capitalgmcbuickregina_830/public/wp-content/plugins/leadbox/classes/
Upload File :
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,
      ));
    }
  }
}