tmgmt_xtm-8.x-5.x-dev/src/Plugin/tmgmt/Translator/XtmTranslator.php
src/Plugin/tmgmt/Translator/XtmTranslator.php
<?php
namespace Drupal\tmgmt_xtm\Plugin\tmgmt\Translator;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\tmgmt\ContinuousTranslatorInterface;
use Drupal\tmgmt\Entity\Translator;
use Drupal\tmgmt\JobInterface;
use Drupal\tmgmt\TMGMTException;
use Drupal\tmgmt\Translator\AvailableResult;
use Drupal\tmgmt\Translator\TranslatableResult;
use Drupal\tmgmt\TranslatorInterface;
use Drupal\tmgmt\TranslatorPluginBase;
use GuzzleHttp\ClientInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Xtm translator plugin.
*
* @TranslatorPlugin(
* id = "xtm",
* label = @Translation("XTM"),
* description = @Translation("Xtm Translator service."),
* ui = "Drupal\tmgmt_xtm\XtmTranslatorUi",
* logo = "icons/xtm.jpg",
* map_remote_languages = false,
* files = true,
* )
*/
class XtmTranslator extends TranslatorPluginBase implements
ContainerFactoryPluginInterface,
ContinuousTranslatorInterface {
/**
* Name of parameter that contains source string to be translated.
*
* @var string
*/
protected $qParamName = 'q';
/**
* Maximum supported characters.
*
* @var int
*/
protected $maxCharacters = 100000;
/**
* Guzzle HTTP client.
*
* @var \GuzzleHttp\ClientInterface
*/
protected $client;
/**
* Constructs a LocalActionBase object.
*
* @param \GuzzleHttp\ClientInterface $client
* The Guzzle HTTP client.
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param array $plugin_definition
* The plugin implementation definition.
*/
public function __construct(
ClientInterface $client,
array $configuration,
$plugin_id,
array $plugin_definition,
) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->client = $client;
}
/**
* Aborts the translation job and updates the project activity in XTM.
*
* @param \Drupal\tmgmt\JobInterface $job
* The job to be aborted.
*
* @return bool
* Returns TRUE if the job was successfully aborted.
*/
public function abortTranslation(JobInterface $job) {
$connector = new Connector();
if ($connector->updateProjectActivity($job)) {
$job->aborted();
// Also look for existing jobs that reference the same XTM Project ID
// (as part of the same multilingual XTM Project) and set them as aborted.
$xtm_reference_id = $job->getReference();
$same_xtm_project_jobs = $this->getXtmReferenceIdJobs($xtm_reference_id);
foreach ($same_xtm_project_jobs as $aborting_job) {
$aborting_job->aborted();
}
}
else {
$message = $this->t('The job was aborted, but the project activity has not been updated in XTM. Please check settings and/or update the project manually.');
$job->aborted($message, [], 'warning');
$this->messenger()->addWarning($message);
}
return TRUE;
}
/**
* Gets all the jobs that reference a specific XTM Project ID.
*
* @param int $xtm_reference_id
* The XTM Project reference ID.
*
* @return array
* An array of job entities that reference the given XTM Project ID.
*/
protected function getXtmReferenceIdJobs($xtm_reference_id) {
$job_ids = \Drupal::entityQuery('tmgmt_job')
->condition('reference', $xtm_reference_id)
->accessCheck(FALSE)
->execute();
return \Drupal::entityTypeManager()
->getStorage('tmgmt_job')
->loadMultiple(array_keys($job_ids));
}
/**
* {@inheritdoc}
*/
public static function create(
ContainerInterface $container,
array $configuration,
$plugin_id,
$plugin_definition,
) {
return new static(
$container->get('http_client'),
$configuration,
$plugin_id,
$plugin_definition
);
}
/**
* Overrides TMGMTDefaultTranslatorPluginController::checkAvailable().
*
* Checks whether the specified translator is available for use.
*
* @param \Drupal\tmgmt\TranslatorInterface $translator
* The translator entity to check for availability.
*
* @return \Drupal\tmgmt\Translator\AvailableResult
* An AvailableResult object indicating whether the translator is available.
*
* @throws \Drupal\Core\Entity\EntityMalformedException
* Thrown if the translator entity is malformed or has invalid data.
*/
public function checkAvailable(TranslatorInterface $translator) {
if ($translator->getSetting('xtm_api_url')) {
return AvailableResult::yes();
}
return AvailableResult::no(
t(
'@translator is not available. Make sure it is properly <a href=:configured>configured</a>.',
[
'@translator' => $translator->label(),
':configured' => $translator->toUrl()->toString(),
]
)
);
}
/**
* Checks if the job can be translated by the specified translator.
*
* @param \Drupal\tmgmt\TranslatorInterface $translator
* The translator entity that will handle the job.
* @param \Drupal\tmgmt\JobInterface $job
* The job to be checked for translatability.
*
* @return \Drupal\tmgmt\Translator\TranslatableResult
* A TranslatableResult object indicating whether the job is translatable.
*
* @throws \Drupal\Core\Entity\EntityMalformedException
* Thrown if the translator or job entity is malformed or has invalid data.
*/
public function checkTranslatable(TranslatorInterface $translator, JobInterface $job) {
foreach (\Drupal::service('tmgmt.data')->filterTranslatable($job->getData()) as $value) {
if (mb_strlen($value['#text']) > $this->maxCharacters) {
return TranslatableResult::no(t(
'The length of the job exceeded the max character count (@count).',
['@count' => $this->maxCharacters]
));
}
}
if ($this->checkAvailable($translator)->getSuccess()) {
$helper = new Helper();
$supportedXTMLanguages = $helper->getXtmLanguage();
$mapTargetLanguage = $helper->mapLanguageToXTMFormat($job->getTargetLangcode(), $translator);
$mapSourceLanguage = $helper->mapLanguageToXTMFormat($job->getSourceLangcode(), $translator);
if ($this->isLanguageOnXtmLanguageList($supportedXTMLanguages, $mapTargetLanguage) &&
$this->isLanguageOnXtmLanguageList($supportedXTMLanguages, $mapSourceLanguage)) {
return TranslatableResult::yes();
}
}
return TranslatableResult::no(t(
'Please go to your <a href=":settings_url">@translator Provider settings</a>, ensure the language mapping is correct and save the settings to translate from @source to @target.',
[
'@translator' => $translator->label(),
':settings_url' => $translator->toUrl()->toString(),
'@source' => $job->getSourceLanguage()->getName(),
'@target' => $job->getTargetLanguage()->getName(),
]
));
}
/**
* Implements TMGMTTranslatorPluginControllerInterface::requestTranslation().
*
* Requests the translation for the specified job.
*
* @param \Drupal\tmgmt\JobInterface $job
* The job for which the translation is requested.
*/
public function requestTranslation(JobInterface $job) {
$this->requestJobItemsTranslation($job->getItems());
}
/**
* Overrides TMGMTDefaultTranslatorPluginController::getSupportedRemoteLanguages().
*
* Returns an empty array,
* as this translator does not support remote languages.
*
* @param \Drupal\tmgmt\TranslatorInterface $translator
* The translator entity.
*
* @return array
* An empty array indicating no supported remote languages.
*/
public function getSupportedRemoteLanguages(TranslatorInterface $translator) {
return [];
}
/**
* Overrides TMGMTDefaultTranslatorPluginController::getDefaultRemoteLanguagesMappings().
*/
public function getDefaultRemoteLanguagesMappings() {
parent::getDefaultRemoteLanguagesMappings();
return [
'zh-hans' => 'zh-CHS',
'zh-hant' => 'zh-CHT',
];
}
/**
* Retrieves the supported target languages for a given source language.
*
* @param \Drupal\tmgmt\TranslatorInterface $translator
* The translator entity.
* @param string $source_language
* The source language code.
*
* @return array
* An array of supported target languages.
* If the source language is not found, an empty array is returned.
*/
public function getSupportedTargetLanguages(TranslatorInterface $translator, $source_language) {
$helper = new Helper();
$languages = $helper->getXtmLanguage();
if (array_key_exists($source_language, $languages)) {
return $languages[$source_language];
}
return [];
}
/**
* Determines if the job has checkout settings configured.
*
* @param \Drupal\tmgmt\JobInterface $job
* The translation job entity.
*
* @return bool
* TRUE if the job has checkout settings, otherwise FALSE.
*/
public function hasCheckoutSettings(JobInterface $job) {
parent::hasCheckoutSettings($job);
return TRUE;
}
/**
* Finds a customer based on the provided translator and optional ID.
*
* @param \Drupal\tmgmt\Entity\Translator $translator
* The translator entity used for the customer search.
* @param string|null $id
* (Optional) The customer ID to search for.
* If NULL, all customers are retrieved.
*
* @return array
* An array of customers matching the search criteria.
* Returns an empty array if no customers are found or if an error occurs.
*/
public function findCustomer(Translator $translator, $id = NULL) {
$connector = new Connector();
try {
$response = $connector->findCustomer($translator, $id);
}
catch (\Exception $exception) {
return [];
}
if (empty($response->customers)) {
return [];
}
return $this->parseToArray($response->customers);
}
/**
* Overrides the 'q' name to avoid collision with Drupal's 'q' parameter.
*
* This is necessary for Drupal testing where 'q' is used internally.
*
* @param string $name
* The new name for the 'q' parameter.
*/
final public function setQueryParamName($name) {
$this->qParamName = $name;
}
/**
* {@inheritdoc}
*/
public function requestJobItemsTranslation(array $jobItems) {
if (empty($jobItems)) {
\Drupal::logger('xtm-wpml-connector')->notice('No job items provided for translation.');
return;
};
/** @var \Drupal\tmgmt\Entity\Job $job */
$job = reset($jobItems)->getJob();
$connector = new Connector();
foreach ($jobItems as $jobItem) {
if ($job->isContinuous()) {
$translatable = $job->canRequestTranslation();
if (!$translatable->getSuccess()) {
\Drupal::logger($connector::XTM_LOGGER)->notice('Rejected Job ID: #' . $job->id() . '. ' . $translatable->getReason());
return;
}
$jobItem->active();
}
}
try {
$connector->xtmRequestTranslation($job, $jobItems);
}
catch (TMGMTException $e) {
\Drupal::logger($connector::XTM_LOGGER)->notice('Rejected Job ID: #' . $job->id() . '. ' . $e->getMessage());
$job->rejected(
'Translation has been rejected with following error: @error',
['@error' => $e->getMessage()],
);
}
}
/**
* 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
* An array containing the item. If the item is already an array,
* it is returned as is.
*/
private function parseToArray($item) {
if (is_array($item)) {
return $item;
}
else {
return [$item];
}
}
/**
* Checks if a language key exists in the list of supported XTM languages.
*
* @param array $supportedXtmLanguages
* An array of supported XTM languages.
* @param string $languageKey
* The language key to check in the XTM languages list.
*
* @return bool
* TRUE if the language key is found in the supported XTM languages,
* FALSE otherwise.
*/
private function isLanguageOnXtmLanguageList(array $supportedXtmLanguages, $languageKey) {
foreach ($supportedXtmLanguages as $spl) {
if (array_key_exists($languageKey, $spl)) {
return TRUE;
}
if (count($spl) > 1) {
// More than one language.
foreach ($spl as $lang) {
if (array_key_exists($languageKey, $lang)) {
return TRUE;
}
}
}
}
return FALSE;
}
}
