tmgmt_xtm-8.x-5.x-dev/src/Plugin/tmgmt/Translator/Connector.php

src/Plugin/tmgmt/Translator/Connector.php
<?php

namespace Drupal\tmgmt_xtm\Plugin\tmgmt\Translator;

use Drupal\file\Entity\File;
use Drupal\file\FileInterface;
use Drupal\tmgmt\Entity\JobItem;
use Drupal\tmgmt\JobInterface;
use Drupal\tmgmt\JobItemInterface;
use Drupal\tmgmt\TMGMTException;
use Drupal\tmgmt_xtm\Exception\TMGMTXtmException;
use Drupal\Core\Url;
use Drupal\tmgmt\Entity\Job;
use Drupal\tmgmt\Entity\Translator;
use GuzzleHttp\Utils;
use Masterminds\HTML5\Exception;
use RuntimeException;
use SimpleXMLElement;

/**
 * Provides XTM API functionalities.
 *
 * @package Drupal\tmgmt_xtm\Plugin\tmgmt\Translator
 */
class Connector {
  const INTEGRATION_KEY = '163209fcd9394e34b625f371f66a0cb7';
  const XTM_ACTION_ARCHIVE = 'ARCHIVE';

  const CREATE_PROJECT_FOR_PMMTOM = 'createProjectForPMMTOM';
  const UPDATE_PROJECT_ACTIVITY = 'updateProjectActivity';
  const CHECK_PROJECT_COMPLETION = 'checkProjectCompletion';
  const DOWNLOAD_PROJECT_MTOM = 'downloadProjectMTOM';
  const DOWNLOAD_JOB_MTOM = 'downloadJobMTOM';
  const FIND_CUSTOMER = 'findCustomer';
  const FIND_TEMPLATE = 'findTemplate';
  const GET_XTM_INFO = 'getXTMInfo';

  const XTM_TEMPLATE_SCOPE_ALL = "ALL";
  const XTM_PROJECT_CUSTOMER_ID = 'xtm_project_customer_id';
  const PROJECT_NAME_PREFIX = 'project_name_prefix';
  const XTM_API_URL = 'xtm_api_url';
  const API_TEMPLATE_ID = 'api_template_id';
  const XTM_API_USER_ID = 'xtm_api_user_id';
  const XTM_API_PASSWORD = 'xtm_api_password';
  const XTM_API_CLIENT_NAME = 'xtm_api_client_name';
  const API_PROJECT_MODE = 'api_project_mode';
  const CALLBACKS_QUEUE = 'callbacks_queue';

  const TMGMT_JOB_ID = 'tmgmtJobId';
  const XTM_LOGGER = 'tmgmt_xtm';

  /**
   * An array of available api actions.
   *
   * @var array
   */
  protected $availableActions = [
    self::CREATE_PROJECT_FOR_PMMTOM,
    self::UPDATE_PROJECT_ACTIVITY,
    self::FIND_TEMPLATE,
    self::DOWNLOAD_PROJECT_MTOM,
    self::DOWNLOAD_JOB_MTOM,
    self::CHECK_PROJECT_COMPLETION,
    self::GET_XTM_INFO,
    self::FIND_CUSTOMER,
  ];

  /**
   * The ID of the XTM job, used to execute DOWNLOAD_JOB_MTOM.
   *
   * @var int
   */
  protected $xtmJobId = NULL;

  /**
   * Retrieves available XTM templates based on the translator and customer ID.
   *
   * @param \Drupal\tmgmt\Entity\Translator $translator
   *   The translator entity.
   * @param bool $global
   *   Whether to include global templates.
   * @param mixed|null $customerId
   *   The customer ID for filtering templates.
   *   Defaults to the translator's customer ID setting.
   *
   * @return array
   *   An array of available templates.
   */
  public function getTemplates(Translator $translator, $global = TRUE, $customerId = NULL) {
    try {
      if (TRUE === is_null($customerId)) {
        $customerId = $translator->getSetting(self::XTM_PROJECT_CUSTOMER_ID);
      }

      $input = ['filter' => ['scope' => self::XTM_TEMPLATE_SCOPE_ALL]];
      $output = [];
      $response = $this->doRequest($translator, self::FIND_TEMPLATE, $input);
      if (empty($response->templates)) {
        return $output;
      }
      foreach ($this->parseToArray($response->templates) as $template) {
        if (($global && !isset($template->customer)) || $template->customer->id == $customerId) {
          $output[$template->template->id] = $template->template->name;
        }
      }

      return $output;
    }
    catch (\SoapFault $fault) {
      \Drupal::logger(self::XTM_LOGGER)->notice(Utils::jsonEncode($fault));
      \Drupal::messenger()->addError($fault->faultstring);
      return [];
    }
    catch (TMGMTXtmException $e) {
      \Drupal::logger(self::XTM_LOGGER)->notice($e->getMessage());
      \Drupal::messenger()->addError($e->getMessage());
      return [];
    }
  }

  /**
   * Fetches XTM-related information for a given translator.
   *
   * @param \Drupal\tmgmt\Entity\Translator $translator
   *   The translator entity.
   *
   * @return array
   *   An array containing XTM information.
   */
  public function getXTMInfo(Translator $translator) {
    $response = $this->doRequest($translator, self::GET_XTM_INFO, []);

    if (empty($response->xtmInfo)) {
      return [];
    }

    return $response->xtmInfo;
  }

  /**
   * Generates the callback URL for the specified job IDs.
   *
   * @param string $ids
   *   The job IDs for which the callback URL is generated.
   *
   * @return string
   *   The absolute callback URL as a string.
   */
  public function getCallbackUrl($ids): string {
    return Url::fromRoute(
      'tmgmt_xtm.callback',
      [self::TMGMT_JOB_ID => $ids],
      [
        'absolute' => TRUE,
        'https' => $this->isHttpsProtocol(),
      ]
    )->toString();
  }

  /**
   * Checks if the current request is using the HTTPS protocol.
   *
   * @return bool
   *   TRUE if the protocol is HTTPS, FALSE otherwise.
   */
  private function isHttpsProtocol(): bool {
    if ((isset($_SERVER['HTTPS']) && (($_SERVER['HTTPS'] == 'on') || ($_SERVER['HTTPS'] == '1'))) || (isset($_SERVER['HTTPS']) &&    $_SERVER['SERVER_PORT'] == 443)) {
      return TRUE;
    }
    elseif (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https' || !empty($_SERVER['HTTP_X_FORWARDED_SSL']) && $_SERVER['HTTP_X_FORWARDED_SSL'] == 'on') {
      return TRUE;
    }
    else {
      return FALSE;
    }
  }

  /**
   * Sets the XTM job ID.
   *
   * @param int $xtmJobId
   *   The XTM job ID to set.
   */
  public function setXtmJobId(int $xtmJobId) {
    $this->xtmJobId = $xtmJobId;
  }

  /**
   * Retrieve translated project from XTM service.
   *
   * @param \Drupal\tmgmt\Entity\Job $job
   *   Job entity which contains all necessary data for translation retrieval.
   *
   * @return bool
   *   A bool value indicating whether the translation process
   *   was completed successfully.
   * @throws TMGMTXtmException
   */
  public function retrieveTranslation(Job $job) {
    try {
      $this->isTempDirectoryWritable();
      if ($job->getState() == JobInterface::STATE_FINISHED) {
        return FALSE;
      }
      $reference = $job->getReference();
      $requestParameters = $this->getRequestTranslationParameters($reference);

      $filesMTOM = $this->doRequest(
        $job->getTranslator(),
        $requestParameters['action'],
        $requestParameters['params']
      );

      $xtmJobs = $this->xtmJobId ? $filesMTOM->jobs : $filesMTOM->project->jobs;

      if (empty($xtmJobs)) {
        throw new TMGMTXtmException("Could not get translated files from project #@projectId for job #@jobId or project has not been completed.",
          [
            '@projectId' => $reference,
            '@jobId' => $job->id(),
          ]
        );
      }

      foreach ($this->parseToArray($xtmJobs) as $targetFile) {
        $this->populateJobs($job, $targetFile);
      }

      return TRUE;
    }
    catch (\SoapFault $fault) {
      \Drupal::logger(self::XTM_LOGGER)->notice("XTM retrieveTranslation: Project ID: #" . $job->getReference() . " and Job ID: #" . $job->id() . ": " . Utils::jsonEncode($fault));
      \Drupal::messenger()->addError($fault->faultstring);

      $job->addMessage($fault->faultstring, [], 'error');
      return FALSE;
    }
    catch (TMGMTXtmException | \Exception $e) {
      \Drupal::logger(self::XTM_LOGGER)->notice("XTM retrieveTranslation: Project ID: #" . $job->getReference() . " and Job ID: #" . $job->id() . ": " . Utils::jsonEncode($e));
      \Drupal::messenger()->addError($e->getMessage());
      $job->addMessage($e->getMessage(), [], 'error');
      return FALSE;
    }
  }

  /**
   * Retrieve translated project from XTM service for continuous job.
   *
   * @param \Drupal\tmgmt\Entity\Job $job
   *   Job entity which contains all necessary data for translation retrieval.
   *
   * @return bool
   *   A bool value indicating whether the translation process
   *   was completed successfully.
   * @throws Exception
   * @throws TMGMTException
   * @throws \SoapFault
   */
  public function retrieveContinuousTranslation(Job $job) {
    // Get active job items
    $jobItems = $job->getItems(['state' => JobItemInterface::STATE_ACTIVE]);
    $referencedItems = $receivedTranslations = 0;

    foreach ($jobItems as $jobItem) {
      $reference = $this->getReferenceFromRemoteMappings($jobItem);
      if (!$reference)
        continue;
      $referencedItems++;
      try {
        $this->isTempDirectoryWritable();
        $requestParameters = $this->getRequestTranslationParameters($reference);

        $filesMTOM = $this->doRequest(
          $job->getTranslator(),
          $requestParameters['action'],
          $requestParameters['params']
        );

        $xtmJobs = $this->xtmJobId ? $filesMTOM->jobs : $filesMTOM->project->jobs;

        if (empty($xtmJobs)) {
          throw new TMGMTXtmException("Could not get translated files from project #@projectId for job item #@jobItemId or project has not been completed.",
            [
              '@projectId' => $reference,
              '@jobItemId' => $jobItem->id(),
            ]
          );
        }

        foreach ($this->parseToArray($xtmJobs) as $targetFile) {
          $this->populateJobItem($job, $jobItem, $targetFile);
        }

        $receivedTranslations++;
      } catch(\SoupFault | TMGMTXtmException | \RuntimeException $e) {
        $message = $e instanceof \SoapFault ? $e->faultstring : $e->getMessage();
        \Drupal::logger(self::XTM_LOGGER)->notice("XTM retrieveTranslation: Project ID: #" . $job->getReference() . " and Job Item ID: #" . $jobItem->id() . ": " . Utils::jsonEncode($message));
        \Drupal::messenger()->addError($message);

        $job->addMessage($message, [], 'error');
        return FALSE;
      }
    }

    if ($receivedTranslations === $referencedItems) {
      return TRUE;
    }

    return FALSE;
  }

  /**
   * Checks the project status for the given job.
   *
   * @param \Drupal\tmgmt\Entity\Job $job
   *   The job entity for which the project status is checked.
   *
   * @return object|array
   *   An array containing project status information
   *   or an empty array if the project is not found.
   */
  public function checkProjectStatus(Job $job) {
    $input = ['project' => ['id' => $job->getReference()]];

    $response = $this->doRequest($job->getTranslator(), self::CHECK_PROJECT_COMPLETION, $input);

    if (empty($response->project)) {
      return [];
    }
    $output = $response->project;
    $output->jobs = $this->parseToArray($response->project->jobs);

    return $output;
  }

  /**
   * Updates the project activity in XTM.
   *
   * @param \Drupal\tmgmt\Entity\Job $job
   *   The job entity for which the project activity is updated.
   * @param string $activity
   *   The activity to set for the project. Defaults to archiving the project.
   *
   * @return bool
   *   TRUE if the activity was updated successfully, FALSE otherwise.
   */
  public function updateProjectActivity(Job $job, $activity = self::XTM_ACTION_ARCHIVE) {
    try {
      $input = [
        'projects' => ['id' => $job->getReference()],
        'options' => ['activity' => $activity],
      ];
      $response = $this->doRequest($job->getTranslator(), self::UPDATE_PROJECT_ACTIVITY, $input);
      return $response->projects->result == 1;
    }
    catch (\SoapFault $fault) {
      \Drupal::logger(self::XTM_LOGGER)->notice('Project activity for Job #' . $job->id() . ' with Project #' . $job->getReference(). ' failed to update. ' . Utils::jsonEncode($fault));
      \Drupal::messenger()->addError($fault->faultstring);
      return FALSE;
    }
    catch (TMGMTXtmException $e) {
      \Drupal::logger(self::XTM_LOGGER)->notice('Project activity for Job #' . $job->id() . ' with Project #' . $job->getReference(). ' failed to update. ' . $e->getMessage());
      \Drupal::messenger()->addError($e->getMessage());
      return FALSE;
    }
  }

  /**
   * Finds a customer in XTM by ID.
   *
   * @param \Drupal\tmgmt\Entity\Translator $translator
   *   The translator entity used for the request.
   * @param int|null $id
   *   The customer ID to find, or NULL to search without an ID.
   *
   * @return mixed
   *   The response from the XTM API or FALSE on failure.
   */
  public function findCustomer(Translator $translator, $id) {
    $input = [];

    if (!is_null($id)) {
      $input['filter'] = [
        'customers' => [
          'id' => (int) $id,
        ],
      ];
    }
    try {
      return $this->doRequest($translator, self::FIND_CUSTOMER, $input);
    }
    catch (\SoapFault $fault) {
      \Drupal::logger(self::XTM_LOGGER)->notice(Utils::jsonEncode($fault));
      \Drupal::messenger()->addError($fault->faultstring);

      return FALSE;
    }
    catch (TMGMTXtmException $e) {
      watchdog_exception(self::XTM_LOGGER, $e, $e->getMessage());
      \Drupal::messenger()->addError($e->getMessage());
      return FALSE;
    }
  }

  /**
   * Submits a translation request to XTM for the given job.
   *
   * @param \Drupal\tmgmt\Entity\Job $job
   *   The job entity to be submitted for translation.
   *
   * @throws \Drupal\tmgmt\TMGMTException
   *   If the translation request fails.
   */
  public function xtmRequestTranslation($job, array $jobItems = NULL) {
    $translator = $job->getTranslator();
    $projectMTOM = $this->createProjectMTOM($job, $translator, $jobItems);
    if ($job->isContinuous()) {
      $jobItemIds = [];
      foreach ($jobItems as $jobItem) {
        $jobItemIds[] = $jobItem->id();
      }
      $callbackUrl = $this->getCallbackUrl(implode(',', $jobItemIds));
    } else {
      $callbackUrl = $this->getCallbackUrl($job->id());
    }
    $projectMTOM['projectCallback'][$job->getSetting(self::API_PROJECT_MODE) == 2
      ? 'projectFinishedCallback' : 'jobFinishedCallback'] = $callbackUrl;

    if ($job->getSetting(self::API_TEMPLATE_ID)) {
      $projectMTOM['template'] = ['id' => $job->getSetting(self::API_TEMPLATE_ID)];
    }

    $input = [
      'project' => $projectMTOM,
      'options' => ['autopopulate' => TRUE],
    ];

    $mtomRequestService = \Drupal::service('tmgmt_xtm.mtom_request_service');

    $request = $mtomRequestService->prepareTranslationFilesRequest(
      $translator,
      self::CREATE_PROJECT_FOR_PMMTOM,
      $input
    );

    try {
      $response = $mtomRequestService->sendTranslationFilesRequest($request);
      if ($response->getStatusCode() !== 200) {
        throw new \Exception('HTTP error: ' . $response->getStatusCode() . 'for Job ID: #' . $job->id());
      }

      $responseBody = $response->getBody()->getContents();
      if (empty($responseBody)) {
        throw new \Exception('Empty response body for Job ID: #' . $job->id());
      }

      $xml = new SimpleXMLElement($responseBody);
      // Register the namespace
      $xml->registerXPathNamespace('ns1', 'http://pm.v2.webservice.projectmanagergui.xmlintl.com/');
      $parsedResponse =  json_decode(json_encode($xml->xpath('//ns1:createProjectForPMMTOMResponse/return')[0]));

      if (isset($parsedResponse->project)) {
        if ($job->isContinuous()) {
          foreach ($jobItems as $jobItem) {
            $jobItem->addRemoteMapping('xtm_project_id', $parsedResponse->project->projectDescriptor->id);
          }
        }
        $job->reference = $parsedResponse->project->projectDescriptor->id;
        $job->save();
        $job->submitted(
          'The project has been successfully submitted for translation. Project ID: @project_id.',
          ['@project_id' => $job->getReference()]
        );
      }
    } catch (\Exception $e) {
      \Drupal::logger(self::XTM_LOGGER)->notice('Job #' . $job->id() . ' has been rejected. ' . $e->getMessage());
      $job->rejected(
        'Job has been rejected with the following error: @error',
        ['@error' => $e->getMessage()]
      );
    }
  }

  /**
   * Retrieves the XTM reference attached to jobItem.
   *
   * @param \Drupal\tmgmt\Entity\JobItem $jobItem
   *   The jobItem entity for which the reference is being retrieved.
   *
   * @return int
   *   The XTM project ID reference.
   */
  public function getReferenceFromRemoteMappings(JobItem $jobItem = null) {
    if (!$jobItem)
      return null;
    $remoteMappings = $jobItem->getRemoteMappings();
    if (!$remoteMappings)
      return null;
    $remoteMapping = array_shift($remoteMappings);
    return $remoteMapping->getRemoteIdentifier1();
  }

  /**
   * Sends a request to the XTM API.
   *
   * @param \Drupal\tmgmt\Entity\Translator $translator
   *   The translator entity used for the request.
   * @param string $action
   *   The API action to be performed.
   * @param array $query
   *   The query parameters for the API request.
   * @param array $options
   *   Additional options for the request.
   *
   * @return mixed
   *   The response from the XTM API.
   *
   * @throws \SoapFault
   *   If there is an issue with the SOAP request.
   */
  protected function doRequest(Translator $translator, $action, array $query = [], array $options = []) {
    $this->checkRequestConditions($translator, $action);
    $loginAPI = [
      'loginAPI' => [
        'userId' => $translator->getSetting(self::XTM_API_USER_ID),
        'password' => $translator->getSetting(self::XTM_API_PASSWORD),
        'client' => $translator->getSetting(self::XTM_API_CLIENT_NAME),
        'integrationKey' => self::INTEGRATION_KEY,
      ],
    ];
    $client = new \SoapClient($translator->getSetting(self::XTM_API_URL));
    $result = $client->__soapCall(
      $action,
      [array_merge($query, $loginAPI)]
    );

    return $result->return;
  }

  /**
   * Checks if the SOAP extension is enabled.
   *
   * @return bool
   *   TRUE if the SOAP extension is enabled, FALSE otherwise.
   */
  private function isSoapEnabled() {
    return class_exists('SoapClient');
  }

  /**
   * Checks if the WSDL file is accessible.
   *
   * @param string $wsdl
   *   The URL or path to the WSDL file.
   *
   * @return bool
   *   TRUE if the WSDL file is available, FALSE otherwise.
   */
  private function isWsdlAvailable($wsdl) {
    return !!@file_get_contents($wsdl);
  }

  /**
   * Converts an item to an array if it is not already an array.
   *
   * @param mixed $item
   *   The item to be converted to an array.
   *
   * @return array
   *   The item converted to an array.
   */
  private function parseToArray($item) {
    if (is_array($item)) {
      return $item;
    }
    return [$item];
  }

  /**
   * Generates parameters for a translation request based on a reference ID.
   *
   * @param int $reference
   *   The reference ID for the project or job.
   *
   * @return array
   *   The parameters array for the translation request.
   */
  private function getRequestTranslationParameters(int $reference) {
    if ($this->xtmJobId) {
      return [
        'action' => self::DOWNLOAD_JOB_MTOM,
        'params' => [
          'jobs' => [
            'id' => $this->xtmJobId,
          ],
        ],
      ];
    }
    return [
      'action' => self::DOWNLOAD_PROJECT_MTOM,
      'params' => [
        'project' => [
          'id' => $reference,
        ],
      ],
    ];
  }

  /**
   * Creates the project parameters for MTOM-based translation requests.
   *
   * @param Job $job
   *   The job entity for which the project is being created.
   * @param Translator $translator
   *   The translator entity associated with the job.
   * @param array|null $jobItems
   *   An array of $jobItems passed from source form
   *   while requesting continuous job.
   *
   * @return array
   *   An array of parameters required to create the XTM project.
   */
  private function createProjectMTOM(Job $job, Translator $translator, array $jobItems = NULL) {
    $helper = new Helper();
    $prefix = $translator->getSetting(self::PROJECT_NAME_PREFIX);

    $projectName = ($prefix ? "[$prefix] " : '');

    $jobItems = $jobItems ?? $job->getItems();

    if ($job->isContinuous()) {
      $jobItem = end($jobItems);
      $projectName .= $jobItem->getSourceLabel();
    } else {
      $projectName .= $helper->clearLabel($job->label());
    }

    $projectData = [
      'name' => $helper->cutProjectName($projectName),
      'sourceLanguage' => $helper->mapLanguageToXTMFormat($job->getRemoteSourceLanguage(), $translator),
      'targetLanguages' => [$helper->mapLanguageToXTMFormat($job->getRemoteTargetLanguage(), $translator)],
      'translationFiles' => array_map(
        static fn (array $xmlFile) => [
          'fileName'            => $xmlFile['fileName'],
          'fileMTOM'            => $xmlFile['fileMTOM'],
          'externalDescriptors' => $xmlFile['externalDescriptors'],
          'contentType'         => 'application/xml',
        ],
        ($job->getSetting(self::API_PROJECT_MODE) == 0)
          ? $helper->createSingleXMLFile($job)
          : $helper->createMultipleXMLFiles($job)
      ),
      'referenceId' => $job->id(),
      'customer' => ['id' => $translator->getSetting(self::XTM_PROJECT_CUSTOMER_ID)],
    ];

    /** @var \Drupal\tmgmt\Data $dataService */
    $dataService = \Drupal::service('tmgmt.data');
    if (method_exists($dataService, 'getTranslatableFiles')) {
      foreach ($jobItems as $item) {
        foreach ($dataService->getTranslatableFiles($item->getData()) as $key => $file) {
          $fileInfo = pathinfo($file->getFilename());
          $filename = sprintf(
            '[%s][%s][%s]%s.%s',
            $item->id(),
            $file->id(),
            $key,
            $helper->clearFileName($fileInfo['filename']),
            $fileInfo['extension']
          );

          $projectData['translationFiles'][] = [
            'fileName'            => $filename,
            'fileMTOM'            => file_get_contents($file->getFileUri()),
            'externalDescriptors' => [],
            'contentType' => mime_content_type($file->getFileUri()),
          ];
        }
      }
    }

    return $projectData;
  }

  /**
   * Checks the conditions required to make a request to the XTM SOAP service.
   *
   * @param \Drupal\tmgmt\Entity\Translator $translator
   *   The translator entity associated with the job.
   * @param string $action
   *   The action to be performed (e.g., creating a project, checking status).
   *
   * @throws \Drupal\tmgmt_xtm\Exception\TMGMTXtmException
   *   If any of the required conditions are not met, an exception is thrown.
   */
  private function checkRequestConditions(Translator $translator, $action) {
    if (!$this->isSoapEnabled()) {
      \Drupal::logger(self::XTM_LOGGER)->notice("The SOAP extension library is not installed.");
      throw new TMGMTXtmException('The SOAP extension library is not installed.');
    }

    if (!$this->isWsdlAvailable($translator->getSetting(self::XTM_API_URL))) {
      \Drupal::logger(self::XTM_LOGGER)
        ->notice("Could not connect to the XTM SOAP service. Please check settings. URL:" .
          $translator->getSetting(self::XTM_API_URL));
      throw new TMGMTXtmException('Could not connect to the XTM SOAP service. Please check settings.  URL:'
        . $translator->getSetting(self::XTM_API_URL));
    }

    if (!in_array($action, $this->availableActions)) {
      \Drupal::logger(self::XTM_LOGGER)->notice("XTM SOAP service");
      throw new TMGMTXtmException('Invalid action requested: @action', ['@action' => $action]);
    }
  }

  /**
   * Processes and populates job data from an XTM MTOM file.
   *
   * @param \Drupal\tmgmt\Entity\Job $job
   *   The job entity to populate with translation data.
   * @param mixed $file
   *   The XTM file object containing MTOM data.
   *
   * @throws \Drupal\tmgmt_xtm\Exception\TMGMTXtmException
   *   Thrown when there is an issue with downloading or processing the file.
   * @throws \Masterminds\HTML5\Exception|\Drupal\tmgmt\TMGMTException
   *   Thrown when there is an issue parsing the XML file.
   */
  private function populateJobs(Job $job, $file) {
    $tempFile = FileHelper::getTemp($file);
    if (file_exists($tempFile)) {
      unlink($tempFile);
    }
    $reference = $job->getReference();
    $jobId = $job->id();
    if (empty($file->fileMTOM)) {
      throw new Exception("Could not read XTM file. Project ID: #" . $reference . " and Job ID: #" . $jobId . ".");
    }

    if (file_put_contents($tempFile, $file->fileMTOM) === FALSE) {
      throw new TMGMTXtmException("Could not download a file #@fileId from project #@projectId for job #@jobId.",
        [
          '@fileId' => $file->fileDescriptor->id,
          '@projectId' => $reference,
          '@jobId' => $jobId,
        ]
      );
    }

    $this->saveTranslationData($job, $file);

    if (file_exists($tempFile)) {
      unlink($tempFile);
    }
  }

  /**
   * Processes and populates job item data from an XTM MTOM file.
   *
   * @param \Drupal\tmgmt\Entity\Job $job
   *    The job entity.
   *
   * @param \Drupal\tmgmt\Entity\JobItem $jobItem
   *   The job item entity to populate with translation data.
   * @param mixed $file
   *   The XTM file object containing MTOM data.
   *
   * @throws \Drupal\tmgmt_xtm\Exception\TMGMTXtmException
   *   Thrown when there is an issue with downloading or processing the file.
   * @throws \Masterminds\HTML5\Exception|\Drupal\tmgmt\TMGMTException
   *   Thrown when there is an issue parsing the XML file.
   */
  private function populateJobItem(Job $job, JobItem $jobItem, $file) {
    $tempFile = FileHelper::getTemp($file);
    if (file_exists($tempFile)) {
      unlink($tempFile);
    }
    $reference = $this->getReferenceFromRemoteMappings($jobItem);
    $jobItemId = $jobItem->id();
    if (empty($file->fileMTOM)) {
      throw new RuntimeException("Could not read XTM file. Project ID: #" . $reference . " and Job ID: #" . $jobItemId . ".");
    }

    if (file_put_contents($tempFile, $file->fileMTOM) === FALSE) {
      throw new TMGMTXtmException("Could not download a file #@fileId from project #@projectId for job #@jobItemId.",
        [
          '@fileId' => $file->fileDescriptor->id,
          '@projectId' => $reference,
          '@jobItemId' => $jobItemId,
        ]
      );
    }

    $this->saveTranslationData($job, $file, $jobItem);

    if (file_exists($tempFile)) {
      unlink($tempFile);
    }
  }

  private function saveTranslationData(Job $job, $file, JobItem $jobItem = null) {
    $reference = $job->isContinuous()
      ? $this->getReferenceFromRemoteMappings($jobItem)
      : $job->getReference();

    $jobId = $jobItem !== null ? $jobItem->id() : $job->id();

    $helper = new Helper();
    // Detect if this is an uploaded file or an XML with text content.
    if (preg_match('/^\[(\d+)\]\[(\d+)\]\[(.+)]/', $file->originalFileName, $match)) {
      $jobItems = $job->getItems();

      // Check if job item match is one of the current $job items
      if (array_key_exists($match[1], $jobItems)) {
        $filesProvider = new GenericFilesProvider($file);
        $files = $filesProvider->getFiles();
        if (count($files) != 1) {
          throw new \Exception('Unexpected amount of files in XTM ZIP file. Project ID: #' . $reference . ' and Job ID: #' . $jobId);
        }
        $filename = key($files);
        $content = $files[$filename];

        $source_file = File::load($match[2]);
        if (!$source_file instanceof FileInterface) {
          throw new \Exception('Source file ' . $match[2] . ' does not exist. Project ID: #' . $reference . ' and Job ID: #' . $jobId);
        }

        $job_item = JobItem::load($match[1]);
        if (!$job_item instanceof JobItemInterface) {
          throw new \Exception('Job item ' . $match[1] . ' does not exist. Project ID: #' . $reference . ' and Job ID: #' . $jobId);
        }

        /** @var \Drupal\tmgmt\Data $data_service */
        $data_service = \Drupal::service('tmgmt.data');

        $translation_file = $data_service->createFileTranslation($source_file, $job->getTargetLangcode(), $content);
        $job_item->addTranslatedData(['#file' => $translation_file->id()], $match[3]);
      }
    }
    else {
      $xmlFileProvider =  new XmlFilesProvider($file);

      foreach ($xmlFileProvider->getXmlFiles() as $xml){
        $keys = $this->getTargetLanguageKeys($xml);
        $jobId = $this->getJobId($job, $keys);

        foreach ($xml->children() as $xmlJob) {
          $data = [];
          $id = (string)$xmlJob->attributes()->id;
          if ($jobId > 0) {
            if ($file->targetLanguage != $helper->mapLanguageToXTMFormat($job->getTargetLangcode(),
                $job->getTranslator())
            ) {
              continue;
            }
            foreach ($keys as $key) {
              $id = str_replace($key, $jobId, $id);
            }
          }

          $data[$id]['#text'] = (string)$xmlJob;
          $job->addTranslatedData(\Drupal::service('tmgmt.data')->unflatten($data));
        }
      }
    }
  }

  /**
   * Retrieves the target language keys from an XML file.
   *
   * @param \SimpleXMLElement $xml
   *   The XML file containing job data.
   *
   * @return array
   *   An array of target language keys extracted from the XML file.
   */
  private function getTargetLanguageKeys($xml) {
    $keys = [];

    foreach ($xml->children() as $xmlJob2) {
      if ("" != (string) $xmlJob2->attributes()->keys) {
        $keys = Utils::jsonDecode((string) $xmlJob2->attributes()->keys, TRUE);
      }
    }

    return $keys;
  }

  /**
   * Retrieves the job ID based on the target language keys.
   *
   * @param \Drupal\tmgmt\Entity\Job $job
   *   The job entity for which the ID is being retrieved.
   * @param array $keys
   *   An array of target language keys.
   *
   * @return int
   *   The job ID corresponding to the target language.
   */
  private function getJobId(Job $job, $keys) {
    $jobId = 0;
    if ($keys != []) {
      $jobId = $keys[$job->getTargetLangcode()];

    }
    return $jobId;
  }

  /**
   * Checks if temporary directory is writable
   *
   *
   * @return void
   *   The XTM project ID reference.
   * @throws TMGMTXtmException
   *   Throws an TMGMTXtmException on a check fail
   */
  private function isTempDirectoryWritable()
  {
    if (!is_writable(\Drupal::service('file_system')->getTempDirectory())) {
      throw new TMGMTXtmException('The temporary directory is not writable.
         Please check settings or the permissions on <i>@name</i> directory.',
        [
          '@name' => \Drupal::service('file_system')->getTempDirectory(),
        ]
      );
    }
  }

}

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

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