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(),
]
);
}
}
}
