lionbridge_translation_provider-8.x-2.4/tmgmt_contentapi/src/Services/AnalysisCodeApi.php

tmgmt_contentapi/src/Services/AnalysisCodeApi.php
<?php

namespace Drupal\tmgmt_contentapi\Services;

use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Logger\LoggerChannelFactoryInterface;
use GuzzleHttp\ClientInterface;
use Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface;
use Drupal\tmgmt\TranslatorInterface;

/**
 * Class AnalysisCodeApi.
 *
 * This class provides methods to interact with the analysis code configuration
 * for the TMGMT Content API module.
 */
class AnalysisCodeApi {

  /**
   * Guzzle client.
   *
   * @var \GuzzleHttp\ClientInterface
   */
  protected $client;

  /**
   * Logger Factory.
   *
   * @var \Drupal\Core\Logger\LoggerChannelFactoryInterface
   */
  protected $loggerFactory;

  /**
   * Configuration Factory.
   *
   * @var \Drupal\Core\Config\ConfigFactoryInterface
   */
  protected $configFactory;

  /**
   * Key-Value Store.
   *
   * @var \Drupal\Core\Cache\CacheBackendInterface
   */
  protected $kvStore;

  /**
   * AnalysisCodeClient constructor.
   *
   * @param \GuzzleHttp\ClientInterface $client
   *   The Guzzle HTTP client.
   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
   *   The logger factory service.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
   *   The configuration factory service.
   * @param \Drupal\Core\KeyValueStore\KeyValueStoreExpirableInterface $kv_store
   *   The key-value store service.
   */
  public function __construct($client, $logger_factory, $config_factory, $kv_store) {
    $this->client = $client;
    $this->loggerFactory = $logger_factory;
    $this->configFactory = $config_factory;
    $this->kvStore = $kv_store;
  }

  /**
   * Factory method to create an instance of AnalysisCodeApi.
   *
   * @param \Symfony\Component\DependencyInjection\ContainerInterface $container
   *   The service container.
   *
   * @return static
   *   A new instance of AnalysisCodeApi.
   */
  public static function create($container) {
    return new static(
      $container->get('http_client'),
      $container->get('logger.factory'),
      $container->get('config.factory'),
      $container->get('keyvalue.expirable'),

    );
  }

  /**
   * Get API credentials and endpoint from configuration.
   *
   * @return array
   *   Associative array with keys: freeway_auth_url, freeway_service_url, api_login_name, api_login_password, analysis_code.
   */
  public function getApiConfig() {
    $analysis_settings = $this->configFactory->get('tmgmt.translator.contentapi')->get('settings.code-analysis-settings');
    return [
      'freeway_auth_url' => $analysis_settings['freeway_auth_url'] ?? 'https://fwapi.lionbridge.com/Obvibundles/freewayauth.asmx',
      'freeway_service_url' => $analysis_settings['freeway_service_url'] ?? 'https://fwapi.lionbridge.com/Obvibundles/freewayservice.asmx',
      'analysis_code_username' => $analysis_settings['analysis_code_username'] ?? '',
      'analysis_code_password' => $analysis_settings['analysis_code_password'] ?? '',
      'analysis_code' => $analysis_settings['code_group'] ?? '',
    ];
  }

  /**
   * Authenticates with FreewayAuth SOAP API and retrieves a ticket.
   *
   * @param array $freeway_auth_creds
   *   Optional array of credentials to override the default configuration.
   *
   * @return string|null
   *   The authentication ticket, or NULL on failure.
   */
  protected function getApiTicketFromConfig($freeway_auth_creds = []) {
    // If credentials are provided, use them; otherwise, use the config.
    if (!empty($freeway_auth_creds)) {
      $api = $freeway_auth_creds;
    }
    else {
      // Get API configuration from the service.
      $api = $this->getApiConfig();
    }
    $url = $api['freeway_auth_url'];
    $username = $api['analysis_code_username'];
    $password = $api['analysis_code_password'];
    $soapBody = sprintf(<<< XML
    <?xml version="1.0" encoding="utf-8"?>
    <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
      <soap:Body>
        <Logon xmlns="http://tempuri.org/">
          <Username>%s</Username>
          <Password>%s</Password>
        </Logon>
      </soap:Body>
    </soap:Envelope>
    XML,
      htmlspecialchars($username),
      htmlspecialchars($password)
    );
    try {
      $response = $this->soapRequestWithRetry('Logon', $url, $soapBody, 'POST', 3, 1000);
      // Check if the response is valid.
      if ($response === NULL) {
        $this->loggerFactory->get('TMGMT_CONTENTAPI_ANALYSIS_CODE')->error('Logon: Failed to get response from SOAP API.');
        return NULL;
      }
      $body = (string) $response->getBody();
      $xml = @simplexml_load_string($body);
      if ($xml === FALSE) {
        $this->loggerFactory->get('TMGMT_CONTENTAPI_ANALYSIS_CODE')->error('Failed to parse SOAP response for ticket.');
        return NULL;
      }
      $xml->registerXPathNamespace('soap', 'http://schemas.xmlsoap.org/soap/envelope/');
      $resultNodes = $xml->xpath('//soap:Body/soap:LogonResponse/soap:LogonResult');
      if (!empty($resultNodes)) {
        return (string) $resultNodes[0];
      }
      $resultNodes = $xml->xpath('//*[local-name()="LogonResult"]');
      if (!empty($resultNodes)) {
        return (string) $resultNodes[0];
      }
      $this->loggerFactory->get('TMGMT_CONTENTAPI_ANALYSIS_CODE')->error('No ticket found in SOAP response.');
      return NULL;
    }
    catch (\Exception $e) {
      $this->loggerFactory->get('TMGMT_CONTENTAPI_ANALYSIS_CODE')->error('Ticket SOAP API call failed: @msg', ['@msg' => $e->getMessage()]);
      return NULL;
    }
  }

  /**
   * Calls the Freeway SOAP API to fetch customer analysis codes using Guzzle.
   *
   * @param string $ticket
   *   The authentication ticket.
   * @param string $analysisCodeLabel
   *   The analysis code label to filter.
   *
   * @return array|null
   *   Parsed analysis code groups or NULL on failure.
   */
  protected function fetchCustomerAnalysisCodes(string $ticket, string $analysisCodeLabel = '') {
    $api = $this->getApiConfig();
    if ($analysisCodeLabel != '') {
      $analysisCodeLabel = htmlspecialchars($analysisCodeLabel);
    }
    $url = $api['freeway_service_url'];
    $soapBody = sprintf(<<<XML
    <?xml version="1.0" encoding="utf-8"?>
    <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">
      <soap:Body>
        <GetCustomerAnalysisCodes xmlns="http://tempuri.org/">
          <Ticket>%s</Ticket>
          <AnalysisCodeLabel>%s</AnalysisCodeLabel>
        </GetCustomerAnalysisCodes>
      </soap:Body>
    </soap:Envelope>
    XML,
      htmlspecialchars($ticket),
      $analysisCodeLabel
    );

    try {
      $response = $this->soapRequestWithRetry('GetCustomerAnalysisCodes', $url, $soapBody, 'POST', 3, 1000);
      // Check if the response is valid.
      if ($response === NULL) {
        $this->loggerFactory->get('TMGMT_CONTENTAPI_ANALYSIS_CODE')->error('GetCustomerAnalysisCodes: Failed to get response from SOAP API.');
        return NULL;
      }
      $body = (string) $response->getBody();
      $xml = @simplexml_load_string($body);
      if ($xml === FALSE) {
        $this->loggerFactory->get('TMGMT_CONTENTAPI_ANALYSIS_CODE')->error('Failed to parse SOAP response.');
        return NULL;
      }
      $xml->registerXPathNamespace('soap', 'http://schemas.xmlsoap.org/soap/envelope/');
      $xml->registerXPathNamespace('t', 'http://tempuri.org/');
      $resultNodes = $xml->xpath('//soap:Body//t:AnalysisCodeGroup');
      $groups = [];
      foreach ($resultNodes as $group) {
        $level = (string) $group['Level'];
        $name = (string) $group['Name'];
        $codes = [];
        if (isset($group->AnalysisCodes->AnalysisCode)) {
          foreach ($group->AnalysisCodes->AnalysisCode as $code) {
            $codes[] = (array) $code;
          }
        }
        $groups[] = [
          'level' => $level,
          'name' => $name,
          'codes' => $codes,
        ];
      }
      return $groups;
    }
    catch (\Exception $e) {
      $this->loggerFactory->get('TMGMT_CONTENTAPI_ANALYSIS_CODE')->error('SOAP API call failed: @msg', ['@msg' => $e->getMessage()]);
      return NULL;
    }
  }

  /**
   * Checks if the given ticket is still valid using CheckTicketExpiry SOAP API.
   *
   * @param string $ticket
   *   The authentication ticket.
   *
   * @return bool
   *   TRUE if the ticket is valid for at least 10 minutes, FALSE otherwise.
   */
  protected function isTicketValid($ticket) {
    $api = $this->getApiConfig();
    $url = $api['freeway_auth_url'] ?: 'https://fwapi.staging.lionbridge.com/Obvibundles/freewayauth.asmx';
    $soapBody = sprintf(<<<XML
    <?xml version="1.0" encoding="utf-8"?>
    <soap:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">'
      <soap:Body>
        <CheckTicketExpiry xmlns="http://tempuri.org/">
          <Ticket>%s</Ticket>
          </CheckTicketExpiry>
      </soap:Body>
    </soap:Envelope>
    XML,
      htmlspecialchars($ticket)
    );

    // Implement retry logic with exponential backoff
    // and error handling for the SOAP request.
    // Maximum number of retry attempts.
    $maxRetries = 3;
    // Delay between retries in milliseconds (1000ms = 1 second)
    $retryDelay = 1000;
    $attempt = 0;
    $response = NULL;
    try {
      $response = $this->soapRequestWithRetry('CheckTicketExpiry', $url, $soapBody, 'POST', 3, 1000);
      if ($response === NULL) {
        $this->loggerFactory->get('TMGMT_CONTENTAPI_ANALYSIS_CODE')->error('CheckTicketExpiry: Failed to get response from SOAP API.');
        return NULL;
      }
      $body = (string) $response->getBody();
      $xml = @simplexml_load_string($body);
      if ($xml === FALSE) {
        $this->loggerFactory->get('TMGMT_CONTENTAPI_ANALYSIS_CODE')->error('Failed to parse SOAP response for ticket expiry.');
        return FALSE;
      }
      $xml->registerXPathNamespace('soap', 'http://schemas.xmlsoap.org/soap/envelope/');
      $resultNodes = $xml->xpath('//soap:Body/soap:CheckTicketExpiryResponse/soap:CheckTicketExpiryResult');
      if (!empty($resultNodes)) {
        $minutes = (int) $resultNodes[0];
        // Consider valid if more than 10 minutes left.
        return $minutes > 10;
      }
      $resultNodes = $xml->xpath('//*[local-name()="CheckTicketExpiryResult"]');
      if (!empty($resultNodes)) {
        $minutes = (int) $resultNodes[0];
        return $minutes > 10;
      }
      $this->loggerFactory->get('TMGMT_CONTENTAPI_ANALYSIS_CODE')->error('No expiry info found in SOAP response.');
      return FALSE;
    }
    catch (\Exception $e) {
      $this->loggerFactory->get('TMGMT_CONTENTAPI_ANALYSIS_CODE')->error('Ticket expiry SOAP API call failed: @msg', ['@msg' => $e->getMessage()]);
      return FALSE;
    }
  }

  /**
   * Gets a valid ticket, reusing the last one if still valid, else fetches a new one.
   *
   * @param array $freeway_auth_creds
   *   Optional array of credentials to override the default configuration.
   * @param bool $force_new_ticket
   *   If TRUE, forces the retrieval of a new ticket even if the existing one is still valid.
   *
   * @return string|null
   *   The valid authentication ticket, or NULL on failure.
   */
  public function getValidApiTicket($freeway_auth_creds = [], $force_new_ticket = FALSE) {
    $kv_store = $this->kvStore->get('lionbridge_translation_provider');
    $analysis_ticket = $kv_store->get('analysis_ticket', NULL);
    $expire_analysis_ticket_time = $kv_store->get('analysis_ticket_expiration', 0);
    $current_time = time();
    // Calculate the time remaining until the ticket expires.
    if ($expire_analysis_ticket_time) {
      $time_remaining = $expire_analysis_ticket_time - $current_time;
    }
    // If the ticket null generate new one. Else return existing ticket.
    // If ticket suppose expire in 10 minutes, then get new ticket.
    if ($analysis_ticket == NULL || $time_remaining < 600 || $force_new_ticket) {
      // If the ticket is null or about to expire in less than 10 minutes, fetch a new one.
      $analysis_ticket = $this->getApiTicketFromConfig($freeway_auth_creds);
      // Set expiration time to 1 hour from now.
      $expiration_time = time() + 3600;
      // Store the new ticket and expiration time in the key-value store.
      $this->kvStore->get('lionbridge_translation_provider')->setWithExpire('analysis_ticket', $analysis_ticket, 3600);
      // Store the expiration time for the ticket.
      $this->kvStore->get('lionbridge_translation_provider')->setWithExpire('analysis_ticket_expiration', $expiration_time, 3600);
      // Log the new ticket retrieval.
      $this->loggerFactory->get('TMGMT_CONTENTAPI_ANALYSIS_CODE')->info('Retrieved new analysis ticket');
    }
    return $analysis_ticket;
  }

  /**
   * Function to make a SOAP request with retry logic.
   *
   * @param string $method_name
   *   The name of the SOAP method to call.
   * @param string $url
   *   The URL of the SOAP service endpoint.
   * @param string $soapBody
   *   The SOAP body to send in the request.
   * @param string $method
   *   The HTTP method to use (default is 'POST').
   * @param int $maxRetries
   *   The maximum number of retry attempts (default is 3).
   * @param int $retryDelay
   *   The initial delay between retries in milliseconds (default is 1000).
   *
   * @return \Psr\Http\Message\ResponseInterface|
   *   Returns the response from the SOAP request, or NULL on failure.
   */
  protected function soapRequestWithRetry($method_name, $url, $soapBody, $method = 'POST', $maxRetries = 3, $retryDelay = 1000) {
    $response = NULL;
    $attempt = 0;
    while ($attempt < $maxRetries) {
      try {
        $response = $this->client->request($method, $url, [
          'headers' => [
            'Content-Type' => 'text/xml; charset=utf-8',
          ],
          'body' => $soapBody,
          'timeout' => 30,
        ]);
        // Exit loop if request is successful.
        break;
      }
      catch (\Exception $e) {
        $this->loggerFactory->get('TMGMT_CONTENTAPI_ANALYSIS_CODE')->error('@method_name SOAP API call failed: @msg',
         [
          '@method_name' => $method_name,
          '@msg' => $e->getMessage()
        ]);
        $attempt++;
        if ($attempt < $maxRetries) {
          // Convert milliseconds to microseconds.
          usleep($retryDelay * 1000);
          // Exponential backoff.
          $retryDelay *= 2;
        }
      }
    }
    return $response;
  }

  /**
   * Function to test the connection to the SOAP API. with provided crdentials.
   */
  public function testFreewayConnection($freeway_auth_creds = [], $force_new_ticket = FALSE) {
    $ticket = $this->getValidApiTicket($freeway_auth_creds, $force_new_ticket);
    if (!$ticket) {
      return FALSE;
    }
    return TRUE;
  }

  /**
   * Parses the analysis codes from the API response.
   * 
   * @param array $freeway_auth_creds
   *
   * @param bool $raw
   *   If TRUE, returns the raw analysis codes without parsing.
   *
   * @return array
   *   Parsed analysis codes grouped by level and label.
   */
  public function freewayAnalysisCodes($freeway_auth_creds = [], $raw = FALSE) {
    if(empty($freeway_auth_creds)) {
      // If no credentials are provided, use the default configuration.
      $freeway_auth_creds = $this->getApiConfig();
    }

    $ticket = $this->getValidApiTicket($freeway_auth_creds);
    if (!$ticket) {
      $this->loggerFactory->get('TMGMT_CONTENTAPI_ANALYSIS_CODE')->error('Failed to retrieve a valid API ticket.');
      return [];
    }
    // If raw is true, return the raw analysis codes without parsing.
    $analysisCodes = $this->fetchCustomerAnalysisCodes($ticket);
    if ($raw) {
      return $analysisCodes;
    }
    // Initialize an empty array to hold the parsed codes.
    if (empty($analysisCodes)) {
      return [];
    }
    $parsedCodes = [];
    foreach ($analysisCodes as $codeGroup) {
      if (isset($codeGroup['level']) && isset($codeGroup['name']) && isset($codeGroup['codes']) && is_array($codeGroup['codes'])) {
        $label = $codeGroup['name'];
        $level = 'level_' . $codeGroup['level'];
        $values = [];
        foreach ($codeGroup['codes'] as $code) {
          if (isset($code['@attributes']['Name'])) {
            $values[] = $code['@attributes']['Name'];
          }
        }
        $parsedCodes[$level][$label] = $values;
      }
    }
    return $parsedCodes ?? ['level_1' => [], 'level_2' => [], 'level_3' => []];
  }

  /**
   * Function to generate a form element of type 'select' with analysis codes.
   * 
   * @param \Drupal\tmgmt\TranslatorInterface $translator
   *   Translator object to get code analysis settings.
   */
  public function verifyAndGenerateAnalysisCodeSelect(TranslatorInterface $translator) {
    // Get code analysis settings from translator.
    $code_analysis_settings = $translator->getSetting('code-analysis-settings');

    // Validate required settings are present.
    if (empty($code_analysis_settings['freeway_auth_url']) ||
        empty($code_analysis_settings['freeway_service_url']) ||
        empty($code_analysis_settings['analysis_code_username']) ||
        empty($code_analysis_settings['analysis_code_password']) ||
        !isset($code_analysis_settings['code_group']) ||
        !is_array($code_analysis_settings['code_group'])) {
      return [];
    }
    // If code_group all false, return empty array.
    $all_visibility_false = TRUE;
    foreach ($code_analysis_settings['code_group'] as $selected) {
      if ($selected) {
        $all_visibility_false = FALSE;
        break;
      }
    }
    if ($all_visibility_false) {
      return [];
    }

    // Prepare credentials for API authentication.
    $freeway_auth_creds = [
      'freeway_auth_url' => $code_analysis_settings['freeway_auth_url'],
      'freeway_service_url' => $code_analysis_settings['freeway_service_url'],
      'analysis_code_username' => $code_analysis_settings['analysis_code_username'] ?? '',
      'analysis_code_password' => $code_analysis_settings['analysis_code_password'] ?? '',
      'analysis_code' => $code_analysis_settings['code_group'] ?? [],
    ];

    // Fetch analysis codes from the API.
    $analysis_codes = $this->freewayAnalysisCodes($freeway_auth_creds);
    if (empty($analysis_codes)) {
      return [];
    }

    // Build options array from analysis codes.
    $options = [];
    foreach ($analysis_codes as $level => $codes) {
      foreach ($codes as $label => $values) {
        foreach ($values as $value) {
          $options[$level][$label][$value] = $value;
        }
      }
    }

    // Filter options based on selected code groups.
    $code_group = $code_analysis_settings['code_group'];
    if (is_array($code_group)) {
      foreach ($code_group as $level => $selected) {
        if (!$selected) {
          unset($options[$level]);
        }
      }
    }

    return $options;
  }

  function jsonToDynamicTable($data) {
    if(empty($data)) {
      return '';
    }
    $return_string = 'Freeway testing --- passed';
    foreach ($data as $key => $value) {
      if (is_array($value)) {
        foreach ($value as $sub_key => $sub_value) {
            $sub_string = $sub_key . ', [' . implode(', ', $sub_value) . ']';
        }
        $return_string .= "\n" .$key . ': ' . $sub_string;
      } else {
        $return_string .= "\n" .$key . ': ' . $sub_string;
      }
    }
    return $return_string;
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc