tmgmt_xtm-8.x-5.x-dev/src/Plugin/tmgmt/Translator/Helper.php
src/Plugin/tmgmt/Translator/Helper.php
<?php
namespace Drupal\tmgmt_xtm\Plugin\tmgmt\Translator;
use Drupal\tmgmt\Entity\Job;
use Drupal\tmgmt\Entity\Translator;
/**
* Provides utility functions for the XTM translator plugin.
*
* The Helper class contains methods and constants that assist in the
* manipulation and processing of data for the XTM translation service.
*
* @package Drupal\tmgmt_xtm\Plugin\tmgmt\Translator
*/
class Helper {
/**
* The maximum length allowed for a project name.
*/
const PROJECT_NAME_LENGTH = 90;
/**
* Available project modes.
*
* @var array
*/
private $projectModes = [
0 => 'Single file - translation returned at the end of the project',
1 => 'Multiple files - translation returned when each file is complete',
2 => 'Multiple files - translation returned when all files are complete',
];
/**
* Read a file from the ZIP archive.
*
* @param string $filePath
* path to ZIP file.
*
* @return string
* ZIP content.
*/
public function readZipArchive($filePath) {
$zip = @fopen('zip://' . $filePath, 'r');
if (!$zip) {
return '';
}
$content = '';
while (!feof($zip)) {
$content .= fread($zip, 2);
}
fclose($zip);
return $content;
}
/**
* Return all available project modes (with translations).
*
* @return array
* An array of project modes containing project mode id with description
*/
public function getProjectModes() {
$out = [];
foreach ($this->projectModes as $value) {
$out[] = t($value);
}
return $out;
}
/**
* Returns XTM formatted language code for given tmgmt lang code.
*
* @param string $lang
* Tmgmt language code to be converted to XTM format.
* @param \Drupal\tmgmt\Entity\Translator $translator
* Translator needs to have remote_language_mappings configured.
*
* @return mixed
* XTM lang code if given lang was found in translators language mappings,
* empty string otherwise
*/
public function mapLanguageToXTMFormat($lang, Translator $translator) {
$pluginWrapper = $translator->getSetting('plugin_wrapper');
return $pluginWrapper['remote_languages_mappings'][$lang] ?? '';
}
/**
* Gets an array of XTM language codes.
*
* @return array
* An associative array where the keys are language codes and the values
* are the corresponding language names.
*/
public function getXtmLanguage() {
return json_decode(file_get_contents(__DIR__ . "/countryList.json"), TRUE);
}
/**
* Cleans a label by removing unwanted characters and decoding HTML entities.
*
* @param string $label
* The label to be cleaned.
*
* @return string
* The cleaned label.
*/
public function clearLabel($label) {
$label = str_replace("'", "'", $label);
$pattern = [
'"',
"*",
"$",
"#",
"^",
"@",
"!",
"?",
"~",
"\\",
"/",
"&",
":",
";",
"<",
">",
"{",
"}",
"|",
];
return trim(urldecode(strip_tags(str_replace($pattern, "", html_entity_decode($label)))));
}
/**
* Trims the project name if it exceeds the maximum length.
*
* @param string $str
* The project name to be trimmed.
*
* @return string
* The trimmed project name with "..." appended if it was shortened,
* otherwise returns the original name.
*/
public function cutProjectName($str) {
return strlen($str) > self::PROJECT_NAME_LENGTH ? substr($str, 0, self::PROJECT_NAME_LENGTH) . "..." : $str;
}
/**
* Cleans and sanitizes a file name by removing special characters.
*
* @param string $label
* The file name to be cleaned.
*
* @return string
* The sanitized file name with special characters removed and spaces normalized.
*/
public function clearFileName($label) {
return trim(preg_replace('/ +/', ' ', preg_replace(
'/[^A-Za-z0-9 ]/',
' ',
urldecode(preg_replace("/&#?[a-z0-9]+;/i", "", strip_tags($label)))
)));
}
/**
* Creates a single XML file for a given translation job.
*
* @param \Drupal\tmgmt\Entity\Job $job
* The translation job entity for which the XML file is to be created.
*
* @return array
* An array containing the file name, the XML content, and any external descriptors.
*/
public function createSingleXMLFile(Job $job) {
$xml = new \SimpleXMLElement('<xtm-drupal-jobs></xtm-drupal-jobs>');
$data = \Drupal::service('tmgmt.data')->filterTranslatable($this->getJobData($job));
$data = $this->reorderItems($data);
foreach ($data as $id => $text) {
$str = $this->stripInvalidXml($text['#text']);
$xml->addChild('xtm-drupal-job', htmlspecialchars($str))->addAttribute('id', $id);
}
return [
[
'fileName' => $this->filterFileName($job->label(), $job->id()),
'fileMTOM' => $xml->asXML(),
'externalDescriptors' => [],
],
];
}
/**
* Creates multiple XML files for the given translation job.
*
* Each XML file contains a translatable text from the job, organized by its ID.
*
* @param \Drupal\tmgmt\Entity\Job $job
* The translation job entity.
*
* @return array
* An array of files where each file contains:
* - fileName: The name of the file generated.
* - fileMTOM: The XML content of the file.
* - externalDescriptors: Additional descriptors, empty in this case.
*/
public function createMultipleXMLFiles(Job $job) {
$files = [];
$data = \Drupal::service('tmgmt.data')->filterTranslatable($this->getJobData($job));
$data = $this->reorderItems($data);
foreach ($data as $id => $text) {
$xml = new \SimpleXMLElement('<xtm-drupal-jobs></xtm-drupal-jobs>');
$xml->addChild('xtm-drupal-job', htmlspecialchars($text['#text']))->addAttribute('id', $id);
$files[] = [
'fileName' => $this->filterFileName($job->label(), $id),
'fileMTOM' => $xml->asXML(),
'externalDescriptors' => [],
];
}
return $files;
}
/**
* Strips invalid XML characters from a given string.
*
* @param string $value
* The input string from which invalid XML characters will be removed.
*
* @return string
* The sanitized string with invalid XML characters replaced by spaces.
*/
protected function stripInvalidXml($value) {
$ret = "";
if (empty($value)) {
return $ret;
}
$length = strlen($value);
for ($i = 0; $i < $length; $i++) {
$current = ord($value[$i]);
if (($current == 0x9) ||
($current == 0xA) ||
($current == 0xD) ||
(($current >= 0x20) && ($current <= 0xD7FF)) ||
(($current >= 0xE000) && ($current <= 0xFFFD)) ||
(($current >= 0x10000) && ($current <= 0x10FFFF))
) {
$ret .= chr($current);
}
else {
$ret .= " ";
}
}
return $ret;
}
/**
* Reorders the items in the given data array, prioritizing node titles.
*
* The method reorders the items so that any key containing 'node_title'
* appears first, followed by the remaining items in their original order.
*
* @param array $data
* An associative array of translatable data items.
*
* @return array
* The reordered data array with 'node_title' items prioritized.
*/
protected function reorderItems($data) {
$out = [];
foreach ($data as $key => $value) {
if (preg_match('/node_title/i', $key)) {
$out[$key] = $value;
unset($data[$key]);
}
}
return $out += $data;
}
/**
* Create filtered name of MTOM file.
*
* @param string $label
* The main label for file.
* @param string $id
* File name suffix.
*
* @return string
* Cleared label and id combined into XML filename
*/
protected function filterFileName($label, $id) {
$name = $this->clearFileName($label);
return str_replace(['@name', '@id'], [$name, $id], '@name_[@id].xml');
}
/**
* Retrieves the data for all active or inactive job items in a translation job.
*
* @param \Drupal\tmgmt\Entity\Job $job
* The translation job entity from which to retrieve the job items' data.
*
* @return array
* An associative array where the keys are the job item IDs and the values
* are the data arrays for each job item. Each data array contains the translatable
* content and may include a '#label' entry if the data label was not initially set.
*/
protected function getJobData(Job $job) {
$data = [];
$jobItems = $job->getItems();
if ($job->isContinuous()) {
// We want to send only the files from the last job item.
$jobItems = [array_key_last($jobItems) => end($jobItems)];
}
foreach ($jobItems as $tjiid => $jobItem) {
/** @var \Drupal\tmgmt\Entity\JobItem $jobItem */
if ($jobItem->isActive() || $jobItem->isInactive()) {
$data[$tjiid] = $jobItem->getData();
// If not set, use the job item label as the data label.
if (!isset($data[$tjiid]['#label'])) {
$data[$tjiid]['#label'] = $jobItem->getSourceLabel();
}
}
}
return $data;
}
}
