Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
importcsv.php
1<?php
3
7
8
15{
16 use Translate\Controller\Stepper;
17 use Translate\Controller\ProcessParams;
18
20 private $seekLine;
21
23 protected static $documentRoot;
25 protected static $enabledLanguages;
27 protected static $sourceEncoding;
29 protected static $isUtfMode;
30
32 private $tabId = 0;
33
35 private $encodingIn;
36
38 private $updateMethod;
39
41 private $csvFilePath;
42
44 private $csvFile;
45
46
48 private $languageList;
49
51 private $columnList;
52
54 private $importedPhraseCount = 0;
55
56
57
65 public function __construct($name, Main\Engine\Controller $controller, array $config = [])
66 {
67 $fields = ['tabId', 'encodingIn', 'updateMethod', 'csvFilePath', 'seekLine', 'importedPhrasesCount'];
68
69 $this->keepField($fields);
70
71 foreach ($fields as $key)
72 {
73 if (!empty($config[$key]))
74 {
75 $this->{$key} = $config[$key];
76 }
77 }
78
79 self::$documentRoot = \rtrim(Translate\IO\Path::tidy(Main\Application::getDocumentRoot()), '/');
80
81 self::$enabledLanguages = Translate\Config::getEnabledLanguages();
82
83 foreach (self::$enabledLanguages as $languageId)
84 {
85 self::$sourceEncoding[$languageId] = \mb_strtolower(Main\Localization\Translation::getSourceEncoding($languageId));
86 }
87
88 parent::__construct($name, $controller, $config);
89 }
90
98 public function run($runBefore = false)
99 {
100 if ($runBefore)
101 {
102 $this->onBeforeRun();
103 }
104
105 $this->csvFile = new Translate\IO\CsvFile($this->csvFilePath);
106
107 $this->csvFile
108 ->setFieldsType(Translate\IO\CsvFile::FIELDS_TYPE_WITH_DELIMITER)
109 ->setFirstHeader(false)
110 ;
111
112 if (!$this->csvFile->openLoad())
113 {
114 $this->addError(new Main\Error(Loc::getMessage('TR_IMPORT_EMPTY_FILE_ERROR')));
115
116 return [
117 'STATUS' => Translate\Controller\STATUS_COMPLETED
118 ];
119 }
120 if (!$this->verifyCsvFile())
121 {
122 return [
123 'STATUS' => Translate\Controller\STATUS_COMPLETED
124 ];
125 }
126
127 if ($this->csvFile->hasUtf8Bom())
128 {
129 $this->encodingIn = 'utf-8';
130 }
131
132 if ($this->isNewProcess)
133 {
135
136 $this->totalItems = 0;
137 while ($csvRow = $this->csvFile->fetch())
138 {
139 $this->totalItems ++;
140 }
141
142 $this->processedItems = 0;
143 $this->seekLine = 0;
144
145 $this->saveProgressParameters();
146
147 return [
148 'STATUS' => Translate\Controller\STATUS_PROGRESS,
149 'PROCESSED_ITEMS' => 0,
150 'TOTAL_ITEMS' => $this->totalItems,
151 ];
152 }
153 else
154 {
155 $progressParams = $this->getProgressParameters();
156
157 if (isset($progressParams['totalItems']))
158 {
159 $this->totalItems = $progressParams['totalItems'];
160 }
161 if (isset($progressParams['seekLine']))
162 {
163 $this->seekLine = $progressParams['seekLine'];
164 }
165 if (isset($progressParams['importedPhrasesCount']))
166 {
167 $this->importedPhraseCount = $progressParams['importedPhrasesCount'];
168 }
169 }
170
171 return $this->performStep('runImporting');
172 }
173
179 private function runImporting(): array
180 {
181 $fileIndex = $this->columnList['file'];
182 $keyIndex = $this->columnList['key'];
183
184 $currentLine = 0;
185 $maxLinePortion = 500;
186 $hasFinishedReading = false;
187
188 while (true)
189 {
190 $linePortion = 0;
191 $phraseList = [];
192
193 while ($csvRow = $this->csvFile->fetch())
194 {
195 $currentLine ++;
196
197 if ($this->seekLine > 0)
198 {
199 if ($currentLine <= $this->seekLine)
200 {
201 continue;
202 }
203 }
204
205 if (
206 !\is_array($csvRow) ||
207 empty($csvRow) ||
208 (\count($csvRow) == 1 && ($csvRow[0] === null || $csvRow[0] === ''))
209 )
210 {
211 continue;
212 }
213
214 $rowErrors = [];
215
216 $filePath = (isset($csvRow[$fileIndex]) ? $csvRow[$fileIndex] : '');
217 $key = (isset($csvRow[$keyIndex]) ? $csvRow[$keyIndex] : '');
218 if ($filePath == '' || $key == '')
219 {
220 if ($filePath == '')
221 {
222 $rowErrors[] = Loc::getMessage('TR_IMPORT_ERROR_DESTINATION_FILEPATH_ABSENT');
223 }
224 if ($key == '')
225 {
226 $rowErrors[] = Loc::getMessage('TR_IMPORT_ERROR_PHRASE_CODE_ABSENT');
227 }
228 $this->addError(new Main\Error(Loc::getMessage(
229 'TR_IMPORT_ERROR_LINE_FILE_EXT',
230 [
231 '#LINE#' => ($currentLine + 1),
232 '#ERROR#' => implode('; ', $rowErrors)
233 ]
234 )));
235
236 continue;
237 }
238
239 $linePortion ++;
240
241 if (!isset($phraseList[$filePath]))
242 {
243 $phraseList[$filePath] = [];
244 }
245 foreach ($this->languageList as $languageId)
246 {
247 if (!isset($phraseList[$filePath][$languageId]))
248 {
249 $phraseList[$filePath][$languageId] = [];
250 }
251
252 $langIndex = $this->columnList[$languageId];
253 if (!isset($csvRow[$langIndex]) || (empty($csvRow[$langIndex]) && $csvRow[$langIndex] !== '0'))
254 {
255 continue;
256 }
257
258 //$phrase = str_replace("\\\\", "\\", $csvRow[$langIndex]);
259 $phrase = $csvRow[$langIndex];
260
261 $encodingOut = self::$sourceEncoding[$languageId];
262
263 if (!empty($this->encodingIn) && $this->encodingIn !== $encodingOut)
264 {
265 $phrase = Main\Text\Encoding::convertEncoding($phrase, $this->encodingIn, $encodingOut);
266 }
267
268 $checked = true;
269 if ($encodingOut === 'utf-8')
270 {
271 $validPhrase = \preg_replace("/[^\x01-\x7F]/", '', $phrase);// remove ASCII characters
272 if ($validPhrase !== $phrase)
273 {
274 $checked = Translate\Text\StringHelper::validateUtf8OctetSequences($phrase);
275 }
276 unset($validPhrase);
277 }
278
279 if ($checked)
280 {
281 $phraseList[$filePath][$languageId][$key] = $phrase;
282 }
283 else
284 {
285 $rowErrors[] = Loc::getMessage('TR_IMPORT_ERROR_NO_VALID_UTF8_PHRASE', ['#LANG#' => $languageId]);
286 }
287
288 unset($checked, $phrase);
289 }
290
291 if (!empty($rowErrors))
292 {
293 $this->addError(new Main\Error(Loc::getMessage(
294 'TR_IMPORT_ERROR_LINE_FILE_BIG',
295 [
296 '#LINE#' => ($currentLine + 1),
297 '#FILENAME#' => $filePath,
298 '#PHRASE#' => $key,
299 '#ERROR#' => implode('; ', $rowErrors),
300 ]
301 )));
302 }
303 unset($rowErrors);
304
305
306 if ($linePortion >= $maxLinePortion)
307 {
308 break;
309 }
310 }
311
312 if ($csvRow === null)
313 {
314 $hasFinishedReading = true;
315 }
316 unset($csvRow);
317
318 $this->processedItems += $linePortion;
319
320 foreach ($phraseList as $filePath => $translationList)
321 {
322 if (Translate\IO\Path::isLangDir($filePath, true) !== true)
323 {
324 $this->addError(new Main\Error(Loc::getMessage('TR_IMPORT_ERROR_FILE_NOT_LANG', ['#FILE#' => $filePath])));
325 continue;
326 }
327
328 $filePath = Translate\IO\Path::normalize('/'.$filePath);
329
330 foreach ($translationList as $languageId => $fileMessages)
331 {
332 if (empty($fileMessages))
333 {
334 continue;
335 }
336
337 $langFilePath = Translate\IO\Path::replaceLangId($filePath, $languageId);
338
339 if (\Rel2Abs('/', $langFilePath) !== $langFilePath)
340 {
341 $this->addError(new Main\Error(Loc::getMessage('TR_IMPORT_ERROR_BAD_FILEPATH', ['#FILE#' => $filePath])));
342 break;
343 }
344
345 $fullPath = self::$documentRoot. $langFilePath;
346 $fullPath = Main\Localization\Translation::convertLangPath($fullPath, $languageId);
347
348 $langFile = new Translate\File($fullPath);
349 $langFile->setLangId($languageId);
350 $langFile->setOperatingEncoding(self::$sourceEncoding[$languageId]);
351
352 if (!$langFile->loadTokens())
353 {
354 if (!$langFile->load() && $langFile->hasErrors())
355 {
356 foreach ($langFile->getErrors() as $error)
357 {
358 if ($error->getCode() !== 'EMPTY_CONTENT')
359 {
360 $this->addError($error);
361 }
362 }
363 }
364 }
365 if (\count($this->getErrors()) > 0)
366 {
367 continue;
368 }
369
370 $hasDataToUpdate = false;
371
373 foreach ($fileMessages as $key => $phrase)
374 {
375 switch ($this->updateMethod)
376 {
377 // import only new messages
378 case Translate\Controller\Import\Csv::METHOD_ADD_ONLY:
379 if (!isset($langFile[$key]) || (empty($langFile[$key]) && $langFile[$key] !== '0'))
380 {
381 $langFile[$key] = $phrase;
382 $hasDataToUpdate = true;
383 $this->importedPhraseCount ++;
384 }
385 break;
386
387 // update only existing messages
388 case Translate\Controller\Import\Csv::METHOD_UPDATE_ONLY:
389 if (isset($langFile[$key]) && $langFile[$key] !== $phrase)
390 {
391 $langFile[$key] = $phrase;
392 $hasDataToUpdate = true;
393 $this->importedPhraseCount ++;
394 }
395 break;
396
397
398 // import new messages and replace all existing with new ones
399 case Translate\Controller\Import\Csv::METHOD_ADD_UPDATE:
400 if ($langFile[$key] !== $phrase)
401 {
402 $langFile[$key] = $phrase;
403 $hasDataToUpdate = true;
404 $this->importedPhraseCount ++;
405 }
406 break;
407 }
408 }
409
410 if ($hasDataToUpdate)
411 {
412 // backup
413 if ($langFile->isExists() && Translate\Config::needToBackUpFiles())
414 {
415 if (!$langFile->backup())
416 {
417 $this->addError(new Main\Error(
418 Loc::getMessage('TR_IMPORT_ERROR_CREATE_BACKUP', ['#FILE#' => $langFilePath])
419 ));
420 }
421 }
422
423 // sort phrases by key
424 if (Translate\Config::needToSortPhrases())
425 {
426 if (\in_array($languageId, Translate\Config::getNonSortPhraseLanguages()) === false)
427 {
428 $langFile->sortPhrases();
429 }
430 }
431
432 try
433 {
434 if (!$langFile->save())
435 {
436 if ($langFile->hasErrors())
437 {
438 $this->addErrors($langFile->getErrors());
439 }
440 }
441 }
442 catch (Main\IO\IoException $exception)
443 {
444 if (!$langFile->isExists())
445 {
446 $this->addError(new Main\Error(
447 Loc::getMessage('TR_IMPORT_ERROR_WRITE_CREATE', ['#FILE#' => $langFilePath])
448 ));
449 }
450 else
451 {
452 $this->addError(new Main\Error(
453 Loc::getMessage('TR_IMPORT_ERROR_WRITE_UPDATE', ['#FILE#' => $langFilePath])
454 ));
455 }
456 }
457 }
458 }
459 }
460
461 if ($this->instanceTimer()->hasTimeLimitReached())
462 {
463 $this->seekLine = $currentLine;
464 break;
465 }
466
467 if ($hasFinishedReading)
468 {
469 $this->declareAccomplishment();
471 break;
472 }
473 }
474
475 $this->csvFile->close();
476
477 if ($this->instanceTimer()->hasTimeLimitReached() !== true)
478 {
479 $this->declareAccomplishment();
481 }
482
483 return [
484 'PROCESSED_ITEMS' => $this->processedItems,
485 'TOTAL_ITEMS' => $this->totalItems,
486 'TOTAL_PHRASES' => $this->importedPhraseCount,
487 ];
488 }
489
490
496 private function verifyCsvFile(): bool
497 {
498 $testDelimiters = [
499 Translate\IO\CsvFile::DELIMITER_TZP,
500 Translate\IO\CsvFile::DELIMITER_TAB,
501 Translate\IO\CsvFile::DELIMITER_ZPT,
502 ];
503 foreach ($testDelimiters as $delimiter)
504 {
505 $this->csvFile->setFieldDelimiter($delimiter);
506
507 $this->csvFile->moveFirst();
508 $rowHead = $this->csvFile->fetch();
509 if (
510 !\is_array($rowHead)
511 || empty($rowHead)
512 || empty($rowHead[0])
513 || (\count($rowHead) < 3)
514 )
515 {
516 continue;
517 }
518
519 break;
520 }
521
522 if (
523 !is_array($rowHead)
524 || empty($rowHead)
525 || empty($rowHead[0])
526 || (count($rowHead) < 3)
527 )
528 {
529 $this->addError(new Main\Error(Loc::getMessage('TR_IMPORT_ERR_EMPTY_FIRST_ROW')));
530
531 return false;
532 }
533
534 $this->languageList = self::$enabledLanguages;
535 $this->columnList = array_flip($rowHead);
536 foreach ($this->languageList as $keyLang => $langID)
537 {
538 if (!isset($this->columnList[$langID]))
539 {
540 unset($this->languageList[$keyLang]);
541 }
542 }
543 if (!isset($this->columnList['file']))
544 {
545 $this->addError(new Main\Error(Loc::getMessage('TR_IMPORT_ERR_DESTINATION_FIELD_ABSENT')));
546 }
547 if (!isset($this->columnList['key']))
548 {
549 $this->addError(new Main\Error(Loc::getMessage('TR_IMPORT_ERR_PHRASE_CODE_FIELD_ABSENT')));
550 }
551 if (empty($this->languageList))
552 {
553 $this->addError(new Main\Error(Loc::getMessage('TR_IMPORT_ERR_LANGUAGE_LIST_ABSENT')));
554 }
555
556 return count($this->getErrors()) === 0;
557 }
558}
addError(Error $error)
Definition action.php:200
addErrors(array $errors)
Definition action.php:213
static getMessage($code, $replace=null, $language=null)
Definition loc.php:29
__construct($name, Main\Engine\Controller $controller, array $config=[])
Definition importcsv.php:65
performStep($action, array $params=[])
Definition stepper.php:75
declareAccomplishment(bool $flag=true)
Definition stepper.php:136