Server IP : 80.87.202.40 / Your IP : 216.73.216.169 Web Server : Apache System : Linux rospirotorg.ru 5.14.0-539.el9.x86_64 #1 SMP PREEMPT_DYNAMIC Thu Dec 5 22:26:13 UTC 2024 x86_64 User : bitrix ( 600) PHP Version : 8.2.27 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /home/bitrix/ext_www/ilovecveti.ru/bitrix/modules/translate/lib/controller/import/ |
Upload File : |
<?php namespace Bitrix\Translate\Controller\Import; use Bitrix\Main; use Bitrix\Main\Localization\Loc; use Bitrix\Translate; /** * Harvester of the lang folder disposition. */ class ImportCsv extends Translate\Controller\Action implements Translate\Controller\ITimeLimit, Translate\Controller\IProcessParameters { use Translate\Controller\Stepper; use Translate\Controller\ProcessParams; /** @var int */ private $seekLine; /** @var string */ protected static $documentRoot; /** @var string[] */ protected static $enabledLanguages; /** @var string[] */ protected static $sourceEncoding; /** @var boolean */ protected static $isUtfMode; /** @var int Session tab counter. */ private $tabId = 0; /** @var string */ private $encodingIn; /** @var string */ private $updateMethod; /** @var string */ private $csvFilePath; /** @var Translate\IO\CsvFile */ private $csvFile; /** @var string[] */ private $languageList; /** @var string[] */ private $columnList; /** @var int */ private $importedPhraseCount = 0; /** * \Bitrix\Main\Engine\Action constructor. * * @param string $name Action name. * @param Main\Engine\Controller $controller Parent controller object. * @param array $config Additional configuration. */ public function __construct($name, Main\Engine\Controller $controller, array $config = []) { $fields = ['tabId', 'encodingIn', 'updateMethod', 'csvFilePath', 'seekLine', 'importedPhrasesCount']; $this->keepField($fields); foreach ($fields as $key) { if (!empty($config[$key])) { $this->{$key} = $config[$key]; } } self::$documentRoot = \rtrim(Translate\IO\Path::tidy(Main\Application::getDocumentRoot()), '/'); self::$enabledLanguages = Translate\Config::getEnabledLanguages(); foreach (self::$enabledLanguages as $languageId) { self::$sourceEncoding[$languageId] = \mb_strtolower(Main\Localization\Translation::getSourceEncoding($languageId)); } parent::__construct($name, $controller, $config); } /** * Runs controller action. * * @param boolean $runBefore Flag to run onBeforeRun event handler. * * @return array */ public function run($runBefore = false) { if ($runBefore) { $this->onBeforeRun(); } $this->csvFile = new Translate\IO\CsvFile($this->csvFilePath); $this->csvFile ->setFieldsType(Translate\IO\CsvFile::FIELDS_TYPE_WITH_DELIMITER) ->setFirstHeader(false) ; if (!$this->csvFile->openLoad()) { $this->addError(new Main\Error(Loc::getMessage('TR_IMPORT_EMPTY_FILE_ERROR'))); return [ 'STATUS' => Translate\Controller\STATUS_COMPLETED ]; } if (!$this->verifyCsvFile()) { return [ 'STATUS' => Translate\Controller\STATUS_COMPLETED ]; } if ($this->csvFile->hasUtf8Bom()) { $this->encodingIn = 'utf-8'; } if ($this->isNewProcess) { $this->clearProgressParameters(); $this->totalItems = 0; while ($csvRow = $this->csvFile->fetch()) { $this->totalItems ++; } $this->processedItems = 0; $this->seekLine = 0; $this->saveProgressParameters(); return [ 'STATUS' => Translate\Controller\STATUS_PROGRESS, 'PROCESSED_ITEMS' => 0, 'TOTAL_ITEMS' => $this->totalItems, ]; } else { $progressParams = $this->getProgressParameters(); if (isset($progressParams['totalItems'])) { $this->totalItems = $progressParams['totalItems']; } if (isset($progressParams['seekLine'])) { $this->seekLine = $progressParams['seekLine']; } if (isset($progressParams['importedPhrasesCount'])) { $this->importedPhraseCount = $progressParams['importedPhrasesCount']; } } return $this->performStep('runImporting'); } /** * Imports data from csv file. * * @return array */ private function runImporting(): array { $fileIndex = $this->columnList['file']; $keyIndex = $this->columnList['key']; $currentLine = 0; $maxLinePortion = 500; $hasFinishedReading = false; while (true) { $linePortion = 0; $phraseList = []; while ($csvRow = $this->csvFile->fetch()) { $currentLine ++; if ($this->seekLine > 0) { if ($currentLine <= $this->seekLine) { continue; } } if ( !\is_array($csvRow) || empty($csvRow) || (\count($csvRow) == 1 && ($csvRow[0] === null || $csvRow[0] === '')) ) { continue; } $rowErrors = []; $filePath = (isset($csvRow[$fileIndex]) ? $csvRow[$fileIndex] : ''); $key = (isset($csvRow[$keyIndex]) ? $csvRow[$keyIndex] : ''); if ($filePath == '' || $key == '') { if ($filePath == '') { $rowErrors[] = Loc::getMessage('TR_IMPORT_ERROR_DESTINATION_FILEPATH_ABSENT'); } if ($key == '') { $rowErrors[] = Loc::getMessage('TR_IMPORT_ERROR_PHRASE_CODE_ABSENT'); } $this->addError(new Main\Error(Loc::getMessage( 'TR_IMPORT_ERROR_LINE_FILE_EXT', [ '#LINE#' => ($currentLine + 1), '#ERROR#' => implode('; ', $rowErrors) ] ))); continue; } $linePortion ++; if (!isset($phraseList[$filePath])) { $phraseList[$filePath] = []; } foreach ($this->languageList as $languageId) { if (!isset($phraseList[$filePath][$languageId])) { $phraseList[$filePath][$languageId] = []; } $langIndex = $this->columnList[$languageId]; if (!isset($csvRow[$langIndex]) || (empty($csvRow[$langIndex]) && $csvRow[$langIndex] !== '0')) { continue; } //$phrase = str_replace("\\\\", "\\", $csvRow[$langIndex]); $phrase = $csvRow[$langIndex]; $encodingOut = self::$sourceEncoding[$languageId]; if (!empty($this->encodingIn) && $this->encodingIn !== $encodingOut) { $phrase = Main\Text\Encoding::convertEncoding($phrase, $this->encodingIn, $encodingOut); } $checked = true; if ($encodingOut === 'utf-8') { $validPhrase = \preg_replace("/[^\x01-\x7F]/", '', $phrase);// remove ASCII characters if ($validPhrase !== $phrase) { $checked = Translate\Text\StringHelper::validateUtf8OctetSequences($phrase); } unset($validPhrase); } if ($checked) { $phraseList[$filePath][$languageId][$key] = $phrase; } else { $rowErrors[] = Loc::getMessage('TR_IMPORT_ERROR_NO_VALID_UTF8_PHRASE', ['#LANG#' => $languageId]); } unset($checked, $phrase); } if (!empty($rowErrors)) { $this->addError(new Main\Error(Loc::getMessage( 'TR_IMPORT_ERROR_LINE_FILE_BIG', [ '#LINE#' => ($currentLine + 1), '#FILENAME#' => $filePath, '#PHRASE#' => $key, '#ERROR#' => implode('; ', $rowErrors), ] ))); } unset($rowErrors); if ($linePortion >= $maxLinePortion) { break; } } if ($csvRow === null) { $hasFinishedReading = true; } unset($csvRow); $this->processedItems += $linePortion; foreach ($phraseList as $filePath => $translationList) { if (Translate\IO\Path::isLangDir($filePath, true) !== true) { $this->addError(new Main\Error(Loc::getMessage('TR_IMPORT_ERROR_FILE_NOT_LANG', ['#FILE#' => $filePath]))); continue; } $filePath = Translate\IO\Path::normalize('/'.$filePath); foreach ($translationList as $languageId => $fileMessages) { if (empty($fileMessages)) { continue; } $langFilePath = Translate\IO\Path::replaceLangId($filePath, $languageId); if (\Rel2Abs('/', $langFilePath) !== $langFilePath) { $this->addError(new Main\Error(Loc::getMessage('TR_IMPORT_ERROR_BAD_FILEPATH', ['#FILE#' => $filePath]))); break; } $fullPath = self::$documentRoot. $langFilePath; $fullPath = Main\Localization\Translation::convertLangPath($fullPath, $languageId); $langFile = new Translate\File($fullPath); $langFile->setLangId($languageId); $langFile->setOperatingEncoding(self::$sourceEncoding[$languageId]); if (!$langFile->loadTokens()) { if (!$langFile->load() && $langFile->hasErrors()) { foreach ($langFile->getErrors() as $error) { if ($error->getCode() !== 'EMPTY_CONTENT') { $this->addError($error); } } } } if (\count($this->getErrors()) > 0) { continue; } $hasDataToUpdate = false; /** @var \ArrayAccess $langFile */ foreach ($fileMessages as $key => $phrase) { switch ($this->updateMethod) { // import only new messages case Translate\Controller\Import\Csv::METHOD_ADD_ONLY: if (!isset($langFile[$key]) || (empty($langFile[$key]) && $langFile[$key] !== '0')) { $langFile[$key] = $phrase; $hasDataToUpdate = true; $this->importedPhraseCount ++; } break; // update only existing messages case Translate\Controller\Import\Csv::METHOD_UPDATE_ONLY: if (isset($langFile[$key]) && $langFile[$key] !== $phrase) { $langFile[$key] = $phrase; $hasDataToUpdate = true; $this->importedPhraseCount ++; } break; // import new messages and replace all existing with new ones case Translate\Controller\Import\Csv::METHOD_ADD_UPDATE: if ($langFile[$key] !== $phrase) { $langFile[$key] = $phrase; $hasDataToUpdate = true; $this->importedPhraseCount ++; } break; } } if ($hasDataToUpdate) { // backup if ($langFile->isExists() && Translate\Config::needToBackUpFiles()) { if (!$langFile->backup()) { $this->addError(new Main\Error( Loc::getMessage('TR_IMPORT_ERROR_CREATE_BACKUP', ['#FILE#' => $langFilePath]) )); } } // sort phrases by key if (Translate\Config::needToSortPhrases()) { if (\in_array($languageId, Translate\Config::getNonSortPhraseLanguages()) === false) { $langFile->sortPhrases(); } } try { if (!$langFile->save()) { if ($langFile->hasErrors()) { $this->addErrors($langFile->getErrors()); } } } catch (Main\IO\IoException $exception) { if (!$langFile->isExists()) { $this->addError(new Main\Error( Loc::getMessage('TR_IMPORT_ERROR_WRITE_CREATE', ['#FILE#' => $langFilePath]) )); } else { $this->addError(new Main\Error( Loc::getMessage('TR_IMPORT_ERROR_WRITE_UPDATE', ['#FILE#' => $langFilePath]) )); } } } } } if ($this->instanceTimer()->hasTimeLimitReached()) { $this->seekLine = $currentLine; break; } if ($hasFinishedReading) { $this->declareAccomplishment(); $this->clearProgressParameters(); break; } } $this->csvFile->close(); if ($this->instanceTimer()->hasTimeLimitReached() !== true) { $this->declareAccomplishment(); $this->clearProgressParameters(); } return [ 'PROCESSED_ITEMS' => $this->processedItems, 'TOTAL_ITEMS' => $this->totalItems, 'TOTAL_PHRASES' => $this->importedPhraseCount, ]; } /** * Validates uploaded csv file. * * @return boolean */ private function verifyCsvFile(): bool { $testDelimiters = [ Translate\IO\CsvFile::DELIMITER_TZP, Translate\IO\CsvFile::DELIMITER_TAB, Translate\IO\CsvFile::DELIMITER_ZPT, ]; foreach ($testDelimiters as $delimiter) { $this->csvFile->setFieldDelimiter($delimiter); $this->csvFile->moveFirst(); $rowHead = $this->csvFile->fetch(); if ( !\is_array($rowHead) || empty($rowHead) || empty($rowHead[0]) || (\count($rowHead) < 3) ) { continue; } break; } if ( !is_array($rowHead) || empty($rowHead) || empty($rowHead[0]) || (count($rowHead) < 3) ) { $this->addError(new Main\Error(Loc::getMessage('TR_IMPORT_ERR_EMPTY_FIRST_ROW'))); return false; } $this->languageList = self::$enabledLanguages; $this->columnList = array_flip($rowHead); foreach ($this->languageList as $keyLang => $langID) { if (!isset($this->columnList[$langID])) { unset($this->languageList[$keyLang]); } } if (!isset($this->columnList['file'])) { $this->addError(new Main\Error(Loc::getMessage('TR_IMPORT_ERR_DESTINATION_FIELD_ABSENT'))); } if (!isset($this->columnList['key'])) { $this->addError(new Main\Error(Loc::getMessage('TR_IMPORT_ERR_PHRASE_CODE_FIELD_ABSENT'))); } if (empty($this->languageList)) { $this->addError(new Main\Error(Loc::getMessage('TR_IMPORT_ERR_LANGUAGE_LIST_ABSENT'))); } return count($this->getErrors()) === 0; } }