Initial commit - Typo3 11.5.41
This commit is contained in:
429
typo3conf/ext/lfeditor/Classes/Service/FileBaseXLFService.php
Normal file
429
typo3conf/ext/lfeditor/Classes/Service/FileBaseXLFService.php
Normal file
@@ -0,0 +1,429 @@
|
||||
<?php
|
||||
|
||||
namespace SGalinski\Lfeditor\Service;
|
||||
|
||||
/***************************************************************
|
||||
* Copyright notice
|
||||
*
|
||||
* (c) sgalinski Internet Services (https://www.sgalinski.de)
|
||||
*
|
||||
* All rights reserved
|
||||
*
|
||||
* This script is part of the TYPO3 project. The TYPO3 project is
|
||||
* free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* The GNU General Public License can be found at
|
||||
* http://www.gnu.org/copyleft/gpl.html.
|
||||
*
|
||||
* This script is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* This copyright notice MUST APPEAR in all copies of the script!
|
||||
***************************************************************/
|
||||
|
||||
use SGalinski\Lfeditor\Exceptions\LFException;
|
||||
use SGalinski\Lfeditor\Utility\ExtensionUtility;
|
||||
use SGalinski\Lfeditor\Utility\SgLib;
|
||||
use SGalinski\Lfeditor\Utility\Typo3Lib;
|
||||
use TYPO3\CMS\Core\Utility\ArrayUtility;
|
||||
|
||||
/**
|
||||
* base workspace class (xlf)
|
||||
*/
|
||||
class FileBaseXLFService extends FileBaseService {
|
||||
/**
|
||||
* extended init
|
||||
*
|
||||
* @param string $file name of the file (can be a path, if you need this (no check))
|
||||
* @param string $path path to the file
|
||||
* @param string $metaFile
|
||||
* @return void
|
||||
* @throws LFException
|
||||
*/
|
||||
public function init($file, $path, $metaFile) {
|
||||
$this->setFileType('xlf');
|
||||
parent::init($file, $path, $metaFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* calls the parent function and convert all values from utf-8 to the original charset
|
||||
*
|
||||
* @return void
|
||||
* @throws LFException raised if the parent read file method fails
|
||||
*/
|
||||
public function readFile() {
|
||||
$this->readXlfFile();
|
||||
|
||||
foreach ($this->localLang['default'] as $constant => $value) {
|
||||
if (empty($value)) {
|
||||
$this->localLang['default'][$constant] = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* reads a language file
|
||||
*
|
||||
* @param string $file language file
|
||||
* @param string $langKey language shortcut
|
||||
* @return array language content
|
||||
* @throws LFException thrown if no data can be found
|
||||
*/
|
||||
protected function readLLFile($file, $langKey) {
|
||||
if (!\is_file($file)) {
|
||||
throw new LFException('failure.select.noLangfile');
|
||||
}
|
||||
|
||||
// LIBXML_NOCDATA treat CDATA node as text node.
|
||||
// htmlentities in CDATA are not modified,
|
||||
// and those - normaly encoded if XML is valid - outside CDATA will be decoded.
|
||||
|
||||
$xmlContent = \simplexml_load_string(\file_get_contents($file), \SimpleXMLElement::class, LIBXML_NOCDATA);
|
||||
|
||||
// Cast XML to associative array with json_decode/encode.
|
||||
// This way, value is XML are casted to string.
|
||||
$xmlContent = \json_decode(\json_encode($xmlContent), TRUE);
|
||||
|
||||
// check data
|
||||
if (!\is_array($xmlContent['file']['body'])) {
|
||||
throw new LFException('failure.search.noFileContent', 0, '(' . $file . ')');
|
||||
}
|
||||
|
||||
if ($langKey === 'default') {
|
||||
$this->meta = $xmlContent['file']['header'] ?? [];
|
||||
}
|
||||
$this->meta['@attributes'] = $xmlContent['file']['@attributes'];
|
||||
|
||||
return $xmlContent['file']['body'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the trans-unit parts of the xlf language files into flat
|
||||
* source arrays with the target language or the source if the target is en
|
||||
*
|
||||
* @param array $sourceData
|
||||
* @return array
|
||||
*/
|
||||
public function resolveTranslationUnitsArrayIntoFlatArray(array $sourceData) {
|
||||
$flatData = [];
|
||||
|
||||
if (isset($sourceData['trans-unit']['@attributes'])) {
|
||||
$sourceData['trans-unit'] = [$sourceData['trans-unit']];
|
||||
}
|
||||
if (!array_key_exists('trans-unit', $sourceData)) {
|
||||
return $flatData;
|
||||
}
|
||||
|
||||
foreach ((array) $sourceData['trans-unit'] as $data) {
|
||||
if (isset($data['@attributes']['id'])) {
|
||||
$constant = $data['@attributes']['id'];
|
||||
if (array_key_exists('target', $data)) {
|
||||
$flatData[$constant] = $data['target'];
|
||||
} else {
|
||||
$flatData[$constant] = $data['source'];
|
||||
}
|
||||
|
||||
if (empty($flatData[$constant])) {
|
||||
$flatData[$constant] = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $flatData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the absolute language file with all localized sub files
|
||||
*
|
||||
* @return void
|
||||
* @throws LFException
|
||||
*/
|
||||
public function readXlfFile() {
|
||||
$localLang = [];
|
||||
// read absolute file
|
||||
$localLang['default'] = $this->readLLFile($this->absFile, 'default');
|
||||
$localLang['default'] = $this->resolveTranslationUnitsArrayIntoFlatArray($localLang['default']);
|
||||
|
||||
// loop all languages
|
||||
$originLang = [];
|
||||
$languages = SgLib::getSystemLanguages();
|
||||
foreach ($languages as $lang) {
|
||||
$originLang[$lang] = $this->absFile;
|
||||
if ($lang === 'default') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// get localized file
|
||||
$content = array_key_exists($lang, $localLang) ? $localLang[$lang] : '';
|
||||
$lFile = $this->getLocalizedFile($content, $lang);
|
||||
if ($this->checkLocalizedFile(\basename($lFile), $lang)) {
|
||||
$originLang[$lang] = $lFile;
|
||||
$localLang[$lang] = [];
|
||||
|
||||
if (!\is_file($lFile)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// read the content
|
||||
$localSubLanguage = $this->readLLFile($lFile, $lang);
|
||||
$localLang[$lang] = $this->resolveTranslationUnitsArrayIntoFlatArray($localSubLanguage);
|
||||
|
||||
// merge arrays and save origin of current language
|
||||
ArrayUtility::mergeRecursiveWithOverrule($localLang, $localSubLanguage);
|
||||
}
|
||||
}
|
||||
|
||||
// copy all to object variables, if everything was ok
|
||||
$this->localLang = $localLang;
|
||||
$this->originLang = $originLang;
|
||||
}
|
||||
|
||||
/**
|
||||
* checks the localLang array to find localized version of the language
|
||||
* (checks l10n directory too)
|
||||
*
|
||||
* @param string $content language content (only one language)
|
||||
* @param string $langKey language shortcut
|
||||
* @return string localized file (absolute)
|
||||
*/
|
||||
protected function getLocalizedFile($content, $langKey) {
|
||||
$file = '';
|
||||
if ($this->session->getDataByKey('editingMode') !== 'extension') {
|
||||
$file = $this->getLocalizedFileName($this->absFile, $langKey);
|
||||
}
|
||||
if (!\is_file($file)) {
|
||||
$file = \dirname($this->absFile) . '/' . $this->nameLocalizedFile($langKey);
|
||||
}
|
||||
return Typo3Lib::fixFilePath($file);
|
||||
}
|
||||
|
||||
/**
|
||||
* checks a filename if its a localized file reference
|
||||
*
|
||||
* @param string $filename
|
||||
* @param string $langKey language shortcut
|
||||
* @return boolean true(localized) or false
|
||||
*/
|
||||
public function checkLocalizedFile($filename, $langKey) {
|
||||
if (!\preg_match('/^(' . $langKey . ')\..*\.xlf$/', $filename)) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* get the name of a localized file
|
||||
*
|
||||
* @param string $langKey language shortcut
|
||||
* @return string localized file (only filename)
|
||||
*/
|
||||
public function nameLocalizedFile($langKey) {
|
||||
return $langKey . '.' . \basename($this->relFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* generates the xml header
|
||||
*
|
||||
* @return string xml header
|
||||
*/
|
||||
private function getXMLHeader() {
|
||||
return '<?xml version="1.0" encoding="utf-8" standalone="yes" ?>' . "\n";
|
||||
}
|
||||
|
||||
/**
|
||||
* converts the array to a xml string
|
||||
*
|
||||
* @param array $phpArray
|
||||
* @param string $targetLanguage
|
||||
* @param array $enLanguage
|
||||
* @return string xml content
|
||||
*/
|
||||
private function array2xml($phpArray, $targetLanguage, $enLanguage) {
|
||||
$targetLanguage = \htmlspecialchars($targetLanguage);
|
||||
$targetLanguageAttribute = ($targetLanguage !== 'default' ? ' target-language="' . $targetLanguage . '"' : '');
|
||||
|
||||
$lfeditorExtConf = ExtensionUtility::getExtensionConfiguration();
|
||||
$date = ($lfeditorExtConf['changeXlfDate'] ? \gmdate('Y-m-d\TH:i:s\Z') : $this->meta['@attributes']['date']);
|
||||
|
||||
$xmlString = $this->getXMLHeader() . '<xliff version="1.0">
|
||||
<file source-language="en"' . $targetLanguageAttribute . ' datatype="plaintext" original="messages" date="' . $date . '">
|
||||
###HEADER###
|
||||
###BODY###
|
||||
</file>
|
||||
</xliff>';
|
||||
|
||||
$header = '<header/>';
|
||||
if (\is_array($phpArray['header']) && \count($phpArray['header'])) {
|
||||
$header = '<header>' . "\n";
|
||||
foreach ($phpArray['header'] as $tagName => $tagValue) {
|
||||
$tagName = \htmlspecialchars($tagName);
|
||||
$header .= "\t\t\t" . '<' . $tagName . '>' . \htmlspecialchars(implode(' ', $tagValue)) .
|
||||
'</' . $tagName . '>' . "\n";
|
||||
}
|
||||
$header .= "\t\t" . '</header>';
|
||||
}
|
||||
$xmlString = \str_replace('###HEADER###', $header, $xmlString);
|
||||
|
||||
$body = '<body/>';
|
||||
if (\is_array($phpArray['data']) && \count($phpArray['data'])) {
|
||||
$body = '<body>' . "\n";
|
||||
$startCDATA = "<![CDATA[";
|
||||
$endCDATA = "]]>";
|
||||
$approved = ($targetLanguage !== 'default' ? ' approved="yes"' : '');
|
||||
foreach ($phpArray['data'] as $constant => $value) {
|
||||
if (!array_key_exists($constant, $enLanguage)) {
|
||||
continue;
|
||||
}
|
||||
$enValue = $enLanguage[$constant];
|
||||
|
||||
// CDATA markers are stripped when reading the XML file from the disk,
|
||||
// and html entities are not decoded.
|
||||
// So, for simplicity and safety, when writing back the data to the file,
|
||||
// we will always enclose value in CDATA, so html entities are preserved
|
||||
// and XML remains valid.
|
||||
$value = $startCDATA . $value . $endCDATA;
|
||||
$enValue = $startCDATA . $enValue . $endCDATA;
|
||||
|
||||
$body .= "\t\t\t" . '<trans-unit id="' . \htmlspecialchars(
|
||||
$constant
|
||||
) . '"' . $approved . $this->addPreserveSpaceAttribute(
|
||||
$value,
|
||||
$enValue,
|
||||
$targetLanguage
|
||||
) . '>' . "\n";
|
||||
|
||||
if ($targetLanguage !== 'default') {
|
||||
$body .= "\t\t\t\t" . '<source>' . $enValue . '</source>' . "\n";
|
||||
$body .= "\t\t\t\t" . '<target>' . $value . '</target>' . "\n";
|
||||
} else {
|
||||
$body .= "\t\t\t\t" . '<source>' . $value . '</source>' . "\n";
|
||||
}
|
||||
$body .= "\t\t\t" . '</trans-unit>' . "\n";
|
||||
}
|
||||
$body .= "\t\t" . '</body>';
|
||||
}
|
||||
$xmlString = \str_replace('###BODY###', $body, $xmlString);
|
||||
|
||||
return $xmlString;
|
||||
}
|
||||
|
||||
/**
|
||||
* prepares the content of a language file
|
||||
*
|
||||
* @param array $localLang
|
||||
* @return array new xml array
|
||||
*/
|
||||
private function getLangContent($localLang) {
|
||||
$content = [];
|
||||
if (!\is_array($localLang) || !\count($localLang)) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
\ksort($localLang);
|
||||
foreach ($localLang as $const => $value) {
|
||||
$content[$const] = \str_replace("\r", '', $value);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* prepares the meta array for nicer saving
|
||||
*
|
||||
* @return array meta content
|
||||
*/
|
||||
private function prepareMeta() {
|
||||
if (\is_array($this->meta) && \count($this->meta)) {
|
||||
foreach ($this->meta as $label => $value) {
|
||||
$this->meta[$label] = \str_replace("\r", '', $value);
|
||||
}
|
||||
}
|
||||
$this->addGeneratorString();
|
||||
|
||||
$metadata = $this->meta;
|
||||
unset($metadata['@attributes']);
|
||||
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* prepares the final content
|
||||
*
|
||||
* @param array | NULL $editedLanguages
|
||||
* @return array language files as key and content as value
|
||||
*/
|
||||
protected function prepareFileContents($editedLanguages = NULL) {
|
||||
// prepare Content
|
||||
$metaData = $this->prepareMeta();
|
||||
$languages = SgLib::getSystemLanguages();
|
||||
$languageFiles = [];
|
||||
$enLanguage = $this->getLangContent($this->localLang['default']);
|
||||
foreach ($languages as $lang) {
|
||||
if ($lang === 'default') {
|
||||
continue;
|
||||
}
|
||||
// If default language content and $lang language content are not edited, skip this file.
|
||||
if ($editedLanguages !== NULL &&
|
||||
!\in_array('default', $editedLanguages, TRUE) && !\in_array($lang, $editedLanguages, TRUE)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (\array_key_exists($lang, $this->localLang) && \is_array($this->localLang[$lang]) && \count(
|
||||
$this->localLang[$lang]
|
||||
)) {
|
||||
$file = $this->originLang[$lang];
|
||||
$data = [
|
||||
'header' => $metaData,
|
||||
'data' => $this->getLangContent($this->localLang[$lang]),
|
||||
];
|
||||
if (array_key_exists($file, $languageFiles)) {
|
||||
$languageFiles[$file] .= $this->array2xml($data, $lang, $enLanguage);
|
||||
} else {
|
||||
$languageFiles[$file] = $this->array2xml($data, $lang, $enLanguage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// only a localized file?
|
||||
if ($this->checkLocalizedFile(\basename($this->absFile), \implode('|', SgLib::getSystemLanguages()))) {
|
||||
return $languageFiles;
|
||||
}
|
||||
if ($editedLanguages !== NULL && !\in_array('default', $editedLanguages, TRUE)) {
|
||||
return $languageFiles;
|
||||
}
|
||||
|
||||
// prepare content for the main file
|
||||
$data = [
|
||||
'header' => $metaData,
|
||||
'data' => $enLanguage,
|
||||
];
|
||||
$languageFiles[$this->absFile] = $this->array2xml($data, 'default', $enLanguage);
|
||||
|
||||
return $languageFiles;
|
||||
}
|
||||
|
||||
/**
|
||||
* If constant's source (en) or target value contains multiple spaces or LF character,
|
||||
* returns string ' xml:space="preserve"'.
|
||||
*
|
||||
* @param string $value
|
||||
* @param string $enValue
|
||||
* @param string $targetLanguage
|
||||
* @return string
|
||||
*/
|
||||
protected function addPreserveSpaceAttribute($value, $enValue, $targetLanguage) {
|
||||
$valueContainsSpacesOrLF = \strpos($value, ' ') !== FALSE || \strpos($value, "\n") !== FALSE;
|
||||
$enValueContainsSpacesOrLF = FALSE;
|
||||
if ($targetLanguage !== 'default') {
|
||||
$enValueContainsSpacesOrLF = \strpos($enValue, ' ') !== FALSE || \strpos($enValue, "\n") !== FALSE;
|
||||
}
|
||||
return ($valueContainsSpacesOrLF || $enValueContainsSpacesOrLF ? ' xml:space="preserve"' : '');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user