Bitrix-D7 23.9
 
Загрузка...
Поиск...
Не найдено
file.php
1<?php
2
3namespace Bitrix\Translate;
4
9
10class File extends Translate\IO\File implements \Iterator, \Countable, \ArrayAccess
11{
13 protected $languageId;
14
16 protected $sourceEncoding;
17
20
22 protected $messages = null;
23
25 protected $messagesCount = null;
26
28 protected $messageCodes = [];
29
31 protected $messageEnclosure = [];
32
34 protected $dataPosition = 0;
35
37 protected $fileIndex;
38
39
40 //region Fabric
41
50 public static function instantiateByPath(string $path): self
51 {
52 if (empty($path) || !Translate\IO\Path::isPhpFile($path) || !\preg_match("#.+/lang/[a-z0-9]{2}/.+\.php$#", $path))
53 {
54 throw new Main\ArgumentException("Parameter 'path' has a wrong value");
55 }
56
57 $file = (new static($path))
58 ->setLangId(Translate\IO\Path::extractLangId($path));
59
60 return $file;
61 }
62
63
71 public static function instantiateByIndex(Index\FileIndex $fileIndex): self
72 {
73 return (new static($fileIndex->getFullPath()))->setLangId($fileIndex->getLangId());
74 }
75
76
85 public static function instantiateByIoFile(Main\IO\File $fileIn): self
86 {
87 if ($fileIn->getExtension() !== 'php')
88 {
89 throw new Main\ArgumentException();
90 }
91
92 return (new static($fileIn->getPath()))->setLangId(Translate\IO\Path::extractLangId($fileIn->getPath()));
93 }
94
95 //endregion
96
97 //region Language & Encoding
98
103 public function getLangId(): string
104 {
105 if (empty($this->languageId))
106 {
107 $this->languageId = Translate\IO\Path::extractLangId($this->getPath());
108 }
109
110 return $this->languageId;
111 }
112
120 public function setLangId(string $languageId): self
121 {
122 $this->languageId = $languageId;
123
124 return $this;
125 }
126
131 public function getSourceEncoding(): string
132 {
133 static $encodingCache = [];
134 if (empty($this->sourceEncoding))
135 {
136 $language = $this->getLangId();
137 if (isset($encodingCache[$language]))
138 {
139 $this->sourceEncoding = $encodingCache[$language];
140 }
141 else
142 {
143 $this->sourceEncoding = Main\Localization\Translation::getSourceEncoding($language);
144 $encodingCache[$language] = $this->sourceEncoding;
145 }
146 }
147
148 return $this->sourceEncoding;
149 }
150
158 public function setSourceEncoding(string $encoding): self
159 {
160 $this->sourceEncoding = $encoding;
161
162 return $this;
163 }
164
169 public function getOperatingEncoding(): string
170 {
171 if (empty($this->operatingEncoding))
172 {
173 $this->operatingEncoding = Main\Localization\Translation::getCurrentEncoding();
174 }
175
176 return $this->operatingEncoding;
177 }
178
186 public function setOperatingEncoding(string $encoding): self
187 {
188 $this->operatingEncoding = $encoding;
189
190 return $this;
191 }
192
193 // endregion
194
195 //region Validators
196
206 public function lint(
207 string $content = '',
208 array $validTokens = [\T_OPEN_TAG, \T_CLOSE_TAG, \T_WHITESPACE, \T_CONSTANT_ENCAPSED_STRING, \T_VARIABLE, \T_COMMENT, \T_DOC_COMMENT],
209 array $validChars = ['[', ']', ';', '=']
210 ): bool
211 {
212 $isValid = false;
213
214 if (empty($content))
215 {
216 if ($this->isExists())
217 {
218 $content = $this->getContents();
219 }
220 }
221 if (empty($content) || !\is_string($content))
222 {
223 $this->addError(new Main\Error("Parse Error: Empty content"));
224 return $isValid;
225 }
226
227 $tokens = \token_get_all($content);
228
229 $line = $tokens[0][2] || 1;
230 if (!is_array($tokens[0]) || $tokens[0][0] !== \T_OPEN_TAG)
231 {
232 $this->addError(new Main\Error("Parse Error: Wrong open tag ".\token_name($tokens[0][0])." '{$tokens[0][1]}' at line {$line}"));
233 }
234 else
235 {
236 $isValid = true;
237 foreach ($tokens as $token)
238 {
239 if (\is_array($token))
240 {
241 $line = $token[2];
242 if (
243 !\in_array($token[0], $validTokens) ||
244 ($token[0] === \T_VARIABLE && $token[1] != '$MESS')
245 )
246 {
247 $this->addError(new Main\Error("Parse Error: Wrong token ". \token_name($token[0]). " '{$token[1]}' at line {$line}"));
248 $isValid = false;
249 break;
250 }
251 }
252 elseif (\is_string($token))
253 {
254 if (!\in_array($token, $validChars))
255 {
256 $line ++;
257 $this->addError(new Main\Error("Parse Error: Expected character '{$token}' at line {$line}"));
258 $isValid = false;
259 break;
260 }
261 }
262 }
263 }
264
265 return $isValid;
266 }
267
268 // endregion
269
270 //region Load
271
278 public function load(): bool
279 {
280 $this->messages = [];
281 $this->messageCodes = [];
282 $this->messagesCount = 0;
283
284 if (!$this->isExists() || !$this->isFile() || ($this->getExtension() !== 'php'))
285 {
286 return false;
287 }
288
289 // language id
290 $langId = $this->getLangId();
291 if (empty($langId))
292 {
293 $this->addError(new Main\Error('Language Id must be filled'));
294 return false;
295 }
296
297 $content = $this->getContents();
298 if (
299 empty($content)
300 || !\is_string($content)
301 || $content === '<?'
302 || $content === '<?php'
303 )
304 {
305 $this->addError(new Main\Error('Empty content', 'EMPTY_CONTENT'));
306 return false;
307 }
308
309 // encoding
310 $targetEncoding = $this->getOperatingEncoding();
312 $convertEncoding = (\mb_strtolower($targetEncoding) != \mb_strtolower($sourceEncoding));
313 if ($convertEncoding)
314 {
315 $path = Main\Localization\Translation::convertLangPath($this->getPhysicalPath(), $this->getLangId());
316
317 if (Main\Localization\Translation::getDeveloperRepositoryPath() !== null)
318 {
319 $convertEncoding = (\stripos($path, Main\Localization\Translation::getDeveloperRepositoryPath()) === 0);
320 }
321 if (!$convertEncoding && Main\Localization\Translation::useTranslationRepository())
322 {
323 $convertEncoding = (\stripos($path, Main\Localization\Translation::getTranslationRepositoryPath()) === 0);
324 }
325 }
326
327 $messages = (function(){
328 if (isset($GLOBALS['MESS']))
329 {
330 unset($GLOBALS['MESS']);
331 }
332
333 $MESS = [];
334 \ob_start();
335 include $this->getPhysicalPath();
336 \ob_end_clean();
337
338 return $MESS;
339 })();
340
341 if (\is_array($messages) && \count($messages) > 0)
342 {
343 foreach ($messages as $phraseId => $phrase)
344 {
345 if ($convertEncoding)
346 {
347 $phrase = Main\Text\Encoding::convertEncoding($phrase, $sourceEncoding, $targetEncoding);
348 }
349
350 $this->messages[$phraseId] = $phrase;
351 $this->messageCodes[] = $phraseId;
352 $this->messagesCount ++;
353 }
354 }
355
356 return true;
357 }
358
364 public function loadTokens(): bool
365 {
366 $this->messages = [];
367 $this->messageCodes = [];
368 $this->messageEnclosure = [];
369 $this->messagesCount = 0;
370
371 if (!$this->isExists() || !$this->isFile() || ($this->getExtension() !== 'php'))
372 {
373 return false;
374 }
375
376 // language id
377 $langId = $this->getLangId();
378 if (empty($langId))
379 {
380 $this->addError(new Main\Error('Language Id must be filled'));
381 return false;
382 }
383
384 $content = $this->getContents();
385 if (
386 empty($content)
387 || !\is_string($content)
388 || $content === '<?'
389 || $content === '<?php'
390 )
391 {
392 $this->addError(new Main\Error('Empty content', 'EMPTY_CONTENT'));
393 return false;
394 }
395
396 $is = function ($token, $type, $value = null)
397 {
398 if (\is_string($token))
399 {
400 return $token === $type;
401 }
402 if (\is_array($token))
403 {
404 if ($token[0] === $type)
405 {
406 if ($value !== null)
407 {
408 return $token[1] === $value;
409 }
410 return true;
411 }
412 }
413 return false;
414 };
415
416 $tokens = \token_get_all($content);
417
418 $hasPhraseDefinition = false;
419 foreach ($tokens as $inx => $token)
420 {
421 if ($is($token, \T_WHITESPACE))
422 {
423 unset($tokens[$inx]);
424 continue;
425 }
426 if (!$hasPhraseDefinition && $is($token, \T_VARIABLE, '$MESS'))
427 {
428 $hasPhraseDefinition = true;
429 }
430 //if (is_array($token))$tokens[$inx][] = \token_name($token[0]);
431 }
432
433 if (!$hasPhraseDefinition)
434 {
435 $this->addError(new Main\Error("There are no phrase definitions"));
436 return false;
437 }
438
439 \array_splice($tokens, 0, 0);
440
441 $addPhrase = function ($phraseId, $phraseParts, $isHeredoc = false)
442 {
443 if ($phraseId != '')
444 {
445 $len = \mb_strlen($phraseId, $this->getOperatingEncoding());
446 $phraseId = \mb_substr($phraseId, 1, $len - 2, $this->getOperatingEncoding());// strip trailing quotes
447 $phraseId = \str_replace("\\\\", "\\", $phraseId);// strip slashes in code
448
449 $enclosure = $isHeredoc ? '<<<' : \mb_substr($phraseParts[0], 0, 1);// what quote
450
451 $phrase = '';
452 if ($isHeredoc)
453 {
454 $part = $phraseParts[0];
455 $len = \mb_strlen($part, $this->getOperatingEncoding());
456 $phrase = \mb_substr($part, 0, $len - 1, $this->getOperatingEncoding());// strip final \n
457 }
458 else
459 {
460 foreach ($phraseParts as $part)
461 {
462 $enclosure = \mb_substr($part, 0, 1);// what quote
463 // strip trailing quotes
464 if ($enclosure === '"' || $enclosure === "'")
465 {
466 $len = \mb_strlen($part, $this->getOperatingEncoding());
467 $part = \mb_substr($part, 1, $len - 2, $this->getOperatingEncoding());
468 }
469 //$part = StringHelper::unescapePhp($part, $enclosure);
470 $phrase .= $part;
471 }
472 }
473
474 $this->messages[$phraseId] = $phrase;
475 $this->messageCodes[] = $phraseId;
476 $this->messageEnclosure[$phraseId] = $enclosure;
477 $this->messagesCount++;
478 }
479 };
480
481 $startPhrase = false;
482 $endPhrase = false;
483 $inPhrase = false;
484 $inCode = false;
485 $isHeredoc = false;
486 $phraseId = '';
487 $phrase = [];
488 $whereIsPhrase = [];
489
490 foreach ($tokens as $inx => &$token)
491 {
492 if (!$startPhrase && $is($token, \T_VARIABLE, '$MESS'))
493 {
494 $startPhrase = true;
495 }
496
497 if ($startPhrase)
498 {
499 if ($is($token, '['))
500 {
501 $inCode = true;
502 }
503 elseif ($is($token, ']'))
504 {
505 $inCode = false;
506 }
507 elseif ($is($token, '='))
508 {
509 $inPhrase = true;
510 }
511 elseif ($is($token, ';'))
512 {
513 $endPhrase = true;
514 }
515 elseif ($is($token, \T_CLOSE_TAG))
516 {
517 $endPhrase = true;
518 }
519 elseif ($is($token, \T_START_HEREDOC))
520 {
521 $isHeredoc = true;
522 }
523
524 if (
525 $inPhrase
526 && $is($token, \T_VARIABLE, '$MESS')
527 && $is($tokens[$inx + 1], '[')
528 && $is($tokens[$inx + 2], \T_CONSTANT_ENCAPSED_STRING)
529 )
530 {
531 $clonePhraseId = $tokens[$inx + 2][1];
532 $cloneInx = $whereIsPhrase[$clonePhraseId];
533 $phrase[] = $tokens[$cloneInx][1];
534 $endPhrase = true;
535 }
536
537 if ($is($token, \T_CONSTANT_ENCAPSED_STRING) || $is($token, \T_ENCAPSED_AND_WHITESPACE))
538 {
539 if ($inPhrase)
540 {
541 $phrase[] = $token[1];
542 $whereIsPhrase[$phraseId] = $inx;
543 }
544 if ($inCode)
545 {
546 $phraseId = $token[1];
547 }
548 }
549
550 if ($endPhrase)
551 {
552 $addPhrase($phraseId, $phrase, $isHeredoc);
553
554 $phrase = [];
555 $phraseId = '';
556 $startPhrase = false;
557 $endPhrase = false;
558 $inPhrase = false;
559 $inCode = false;
560 $isHeredoc = false;
561 }
562 }
563
564 // todo: Handle here developer's comment from file
565 // \T_COMMENT T_DOC_COMMENT
566 }
567
568 if ($startPhrase)
569 {
570 $addPhrase($phraseId, $phrase, $isHeredoc);
571 }
572
573 return true;
574 }
575
576 //endregion
577
578 //region Save
579
587 public function save(): bool
588 {
589 // language id
590 $langId = $this->getLangId();
591 if (empty($langId))
592 {
593 throw new Main\SystemException("Language Id must be filled");
594 }
595
596 // encoding
597 $operatingEncoding = $this->getOperatingEncoding();
598 $sourceEncoding = $this->getSourceEncoding();
599 $convertEncoding = (\mb_strtolower($operatingEncoding) != \mb_strtolower($sourceEncoding));
600 if ($convertEncoding)
601 {
602 $path = Main\Localization\Translation::convertLangPath($this->getPhysicalPath(), $this->getLangId());
603
604 if (Main\Localization\Translation::getDeveloperRepositoryPath() !== null)
605 {
606 $convertEncoding = (\stripos($path, Main\Localization\Translation::getDeveloperRepositoryPath()) === 0);
607 }
608 if (!$convertEncoding && Main\Localization\Translation::useTranslationRepository())
609 {
610 $convertEncoding = (\stripos($path, Main\Localization\Translation::getTranslationRepositoryPath()) === 0);
611 }
612 }
613
614 $content = '';
615 foreach ($this->messages as $phraseId => $phrase)
616 {
617 if (empty($phrase) && $phrase !== '0')
618 {
619 // remove empty
620 continue;
621 }
622 $phrase = \str_replace(["\r\n", "\r"], ["\n", ''], $phrase);
623 if ($convertEncoding)
624 {
625 $phrase = Main\Text\Encoding::convertEncoding($phrase, $operatingEncoding, $sourceEncoding);
626 }
627 $enclosure = '"';
628 if (isset($this->messageEnclosure[$phraseId]))
629 {
630 $enclosure = $this->messageEnclosure[$phraseId];// preserve origin quote
631 }
632
633 $phraseId = StringHelper::escapePhp($phraseId, '"', "\\\\");
634 if (StringHelper::hasPhpTokens($phraseId, '"'))
635 {
636 $this->addError(new Main\Error("Phrase code contains php tokens"));
637 return false;
638 }
639
640 $phrase = StringHelper::escapePhp($phrase, $enclosure);
641 if (StringHelper::hasPhpTokens($phrase, $enclosure))
642 {
643 $this->addError(new Main\Error("Phrase contains php tokens"));
644 return false;
645 }
646
647 $row = '$MESS["'. $phraseId. '"] = ';
648 if ($enclosure === '<<<')
649 {
650 $row .= "<<<HTML\n". $phrase. "\nHTML";
651 }
652 else
653 {
654 $row .= $enclosure. $phrase. $enclosure;
655 }
656 $content .= "\n". $row. ';';
657 }
658 unset($phraseId, $phrase, $row);
659
660 if ($content <> '')
661 {
662 \set_error_handler(
663 function ($severity, $message, $file, $line)
664 {
665 throw new \ErrorException($message, $severity, $severity, $file, $line);
666 }
667 );
668
669 try
670 {
671 $result = parent::putContents('<?php'. $content. "\n");
672 }
673 catch (\ErrorException $exception)
674 {
675 \restore_error_handler();
676 throw new Main\IO\IoException($exception->getMessage());
677 }
678
679 \restore_error_handler();
680
681 if ($result === false)
682 {
683 $filePath = $this->getPath();
684 throw new Main\IO\IoException("Couldn't write language file '{$filePath}'");
685 }
686 }
687 else
688 {
689 // todo: Add module setting that will allow / disallow drop empty lang files.
690 if ($this->isExists())
691 {
692 $this->markWritable();
693 $this->delete();
694 }
695 }
696
697 return true;
698 }
699
705 public function removeEmptyParents(): bool
706 {
707 // todo: Add module setting that will allow / disallow drop empty lang folders.
708 $ret = true;
709 $parentFolder = $this->getDirectory();
710 while (true)
711 {
712 if ($parentFolder->isExists() && \count($parentFolder->getChildren()) > 0)
713 {
714 $ret = false;
715 break;
716 }
717 if ($parentFolder->isExists())
718 {
719 if ($parentFolder->delete() !== true)
720 {
721 $ret = false;
722 break;
723 }
724 }
725 if ($parentFolder->getName() === 'lang')
726 {
727 break;
728 }
729 $parentFolder = $parentFolder->getDirectory();
730 }
731
732 return $ret;
733 }
734
740 public function backup(): bool
741 {
742 if (!$this->isExists())
743 {
744 return true;
745 }
746
747 $langId = $this->getLangId();
748
749 $fullPath = $langFile = $this->getPhysicalPath();
750
751 if (Main\Localization\Translation::useTranslationRepository() && in_array($langId, Translate\Config::getTranslationRepositoryLanguages()))
752 {
753 if (\mb_strpos($langFile, Main\Localization\Translation::getTranslationRepositoryPath()) === 0)
754 {
755 $langFile = \str_replace(
756 Main\Localization\Translation::getTranslationRepositoryPath(). '/',
757 '',
758 $langFile
759 );
760 }
761 }
762 if (Main\Localization\Translation::getDeveloperRepositoryPath() !== null)
763 {
764 if (\mb_strpos($langFile, Main\Localization\Translation::getDeveloperRepositoryPath()) === 0)
765 {
766 $langFile = \str_replace(
767 Main\Localization\Translation::getDeveloperRepositoryPath(). '/',
768 '',
769 $langFile
770 );
771 }
772 }
773 if (\mb_strpos($langFile, Main\Application::getDocumentRoot()) === 0)
774 {
775 $langFile = \str_replace(
777 '',
778 $langFile
779 );
780 }
781
782 $backupFolder = Translate\Config::getBackupFolder(). '/'. \dirname($langFile). '/';
783 if (!Translate\IO\Path::checkCreatePath($backupFolder))
784 {
785 $this->addError(new Main\Error("Couldn't create backup path '{$backupFolder}'"));
786 return false;
787 }
788
789 $sourceFilename = \basename($langFile);
790 $prefix = \date('YmdHi');
791 $endpointBackupFilename = $prefix. '_'. $sourceFilename;
792 if (\file_exists($backupFolder. $endpointBackupFilename))
793 {
794 $i = 1;
795 while (\file_exists($backupFolder. '/'. $endpointBackupFilename))
796 {
797 $i ++;
798 $endpointBackupFilename = $prefix. '_'. $i. '_'. $sourceFilename;
799 }
800 }
801
802 $isSuccessfull = (bool) @\copy($fullPath, $backupFolder. '/'. $endpointBackupFilename);
803 @\chmod($backupFolder. '/'. $endpointBackupFilename, \BX_FILE_PERMISSIONS);
804
805 if (!$isSuccessfull)
806 {
807 $this->addError(new Main\Error("Couldn't backup file '{$fullPath}'"));
808 }
809
810 return $isSuccessfull;
811 }
812
813 //endregion
814
815
816 //region Index
817
823 public function getFileIndex(): Index\FileIndex
824 {
825 if (!$this->fileIndex instanceof Index\FileIndex)
826 {
827 $indexFileRes = Index\Internals\FileIndexTable::getList([
828 'filter' => [
829 '=LANG_ID' => $this->getLangId(),
830 '=FULL_PATH' => $this->getPath(),
831 ],
832 'limit' => 1
833 ]);
834 $this->fileIndex = $indexFileRes->fetchObject();
835 }
836
837 if (!$this->fileIndex instanceof Index\FileIndex)
838 {
839 $this->fileIndex = (new Index\FileIndex())
840 ->setFullPath($this->getPath())
841 ->setLangId($this->getLangId());
842 }
843
844 return $this->fileIndex;
845 }
846
852 public function updatePhraseIndex(): Index\FileIndex
853 {
854 $this->getFileIndex();
855 $fileId = $this->fileIndex->getId();
856 if ($fileId > 0)
857 {
858 $phraseId = Index\Internals\PhraseIndexTable::query()
859 ->registerRuntimeField(new Main\ORM\Fields\ExpressionField('MAXID', 'MAX(%s)', ['ID']))
860 ->addSelect('MAXID')
861 ->exec()
862 ->fetch()['MAXID'];
863
864 $pathId = $this->fileIndex->getPathId();
865 $phraseData = [];
866 $phraseCodeData = [];
867 foreach ($this as $code => $phrase)
868 {
869 $phraseId ++;
870 $langId = $this->getLangId();
871 $phraseCodeData[] = [
872 'ID' => $phraseId,
873 'FILE_ID' => $fileId,
874 'PATH_ID' => $pathId,
875 'LANG_ID' => $langId,
876 'CODE' => $code,
877 ];
878
879 if (!isset($phraseData[$langId]))
880 {
881 $phraseData[$langId] = [];
882 }
883 $phraseData[$langId][] = [
884 'ID' => $phraseId,
885 'FILE_ID' => $fileId,
886 'PATH_ID' => $pathId,
887 'CODE' => $code,
888 'PHRASE' => $phrase,
889 ];
890 }
891
892 // delete
893 $filter = new Translate\Filter(['fileId' => $fileId]);
894
895 Index\Internals\PhraseIndexTable::purge($filter);
896
897 foreach (Translate\Config::getEnabledLanguages() as $langId)
898 {
899 $ftsClass = Index\Internals\PhraseFts::getFtsEntityClass($langId);
900 $ftsClass::purge($filter);
901 }
902
903 // add
904 if (\count($phraseCodeData) > 0)
905 {
906 Index\Internals\PhraseIndexTable::bulkAdd($phraseCodeData);
907 foreach ($phraseData as $langId => $phraseLangData)
908 {
909 $ftsClass = Index\Internals\PhraseFts::getFtsEntityClass($langId);
910 $ftsClass::bulkAdd($phraseLangData, 'ID');
911 }
912 }
913
914 $this->fileIndex
915 ->setPhraseCount($this->count())
916 ->setIndexed(true)
917 ->setIndexedTime(new Main\Type\DateTime())
918 ->save();
919 }
920
921 return $this->fileIndex;
922 }
923
929 public function deletePhraseIndex(): bool
930 {
931 $this->getFileIndex();
932 if ($this->fileIndex->getId() > 0)
933 {
934 $filter = new Translate\Filter(['id' => $this->fileIndex->getId()]);
935
936 Index\Internals\FileIndexTable::purge($filter);
937
938 foreach (Translate\Config::getEnabledLanguages() as $langId)
939 {
940 $ftsClass = Index\Internals\PhraseFts::getFtsEntityClass($langId);
941 $ftsClass::purge($filter);
942 }
943
944 unset($this->fileIndex);
945 }
946
947 return true;
948 }
949
955 public function getPhraseIndexCollection(): Index\PhraseIndexCollection
956 {
957 $phraseIndexCollection = new Index\PhraseIndexCollection();
958 foreach ($this->messages as $code => $message)
959 {
960 $phraseIndexCollection[] = (new Index\PhraseIndex)
961 ->setLangId($this->getLangId())
962 ->setCode($code)
963 ->setPhrase($message)
964 ;
965 }
966
967 return $phraseIndexCollection;
968 }
969
970 //endregion
971
972 //region ArrayAccess
973
981 public function offsetExists($code): bool
982 {
983 return isset($this->messages[$code]);
984 }
985
993 public function offsetGet($code): ?string
994 {
995 if (isset($this->messages[$code]))
996 {
997 return $this->messages[$code];
998 }
999
1000 return null;
1001 }
1002
1011 public function offsetSet($code, $phrase): void
1012 {
1013 if (!isset($this->messages[$code]))
1014 {
1015 if ($this->messagesCount === null)
1016 {
1017 $this->messagesCount = 1;
1018 }
1019 else
1020 {
1021 $this->messagesCount ++;
1022 }
1023 $this->messageCodes[] = $code;
1024 }
1025 $this->messages[$code] = $phrase;
1026 }
1027
1035 public function offsetUnset($code): void
1036 {
1037 if (isset($this->messages[$code]))
1038 {
1039 unset($this->messages[$code]);
1040 $this->messagesCount --;
1041 if (($i = \array_search($code, $this->messageCodes)) !== false)
1042 {
1043 unset($this->messageCodes[$i]);
1044 }
1045 }
1046 }
1047
1053 public function sortPhrases()
1054 {
1055 \ksort($this->messages, \SORT_NATURAL);
1056 $this->rewind();
1057
1058 return $this;
1059 }
1060
1065 public function getPhrases()
1066 {
1067 return $this->messages;
1068 }
1069
1074 public function getCodes()
1075 {
1076 return \is_array($this->messages) ? \array_keys($this->messages) : [];
1077 }
1078
1084 public function getEnclosure(string $phraseId): string
1085 {
1086 $enclosure = '"';
1087 if (isset($this->messageEnclosure[$phraseId]))
1088 {
1089 $enclosure = $this->messageEnclosure[$phraseId];
1090 }
1091
1092 return $enclosure;
1093 }
1094
1095 //endregion
1096
1097 //region Iterator
1098
1104 public function current(): ?string
1105 {
1106 $code = $this->messageCodes[$this->dataPosition];
1107
1108 if (!isset($this->messages[$code]) || !\is_string($this->messages[$code]) || (empty($this->messages[$code]) && $this->messages[$code] !== '0'))
1109 {
1110 return null;
1111 }
1112
1113 return $this->messages[$code];
1114 }
1115
1121 public function next(): void
1122 {
1123 ++ $this->dataPosition;
1124 }
1125
1131 #[\ReturnTypeWillChange]
1132 public function key()
1133 {
1134 return $this->messageCodes[$this->dataPosition] ?: null;
1135 }
1136
1142 public function valid(): bool
1143 {
1144 return isset($this->messageCodes[$this->dataPosition], $this->messages[$this->messageCodes[$this->dataPosition]]);
1145 }
1146
1152 public function rewind(): void
1153 {
1154 $this->dataPosition = 0;
1155 $this->messageCodes = \array_keys($this->messages);
1156 }
1157
1158 //endregion
1159
1160 //region Countable
1161
1169 public function count($allowDirectFileAccess = false): int
1170 {
1171 if ($this->messagesCount === null)
1172 {
1173 if ($this->messages !== null && \count($this->messages) > 0)
1174 {
1175 $this->messagesCount = \count($this->messages);
1176 }
1177 elseif ($allowDirectFileAccess)
1178 {
1179 $MESS = array();
1180 include $this->getPhysicalPath();
1181
1182 if (\is_array($MESS) && \count($MESS) > 0)
1183 {
1184 $this->messagesCount = \count($MESS);
1185 }
1186 }
1187 }
1188
1189 return $this->messagesCount ?: 0;
1190 }
1191
1192 //endregion
1193
1194 //region Content
1195
1201 public function getContents()
1202 {
1203 $data = parent::getContents();
1204
1205 if (\is_string($data))
1206 {
1207 // encoding
1208 $targetEncoding = $this->getOperatingEncoding();
1209 $sourceEncoding = $this->getSourceEncoding();
1210 if ($targetEncoding != $sourceEncoding)
1211 {
1212 $data = Main\Text\Encoding::convertEncoding($data, $sourceEncoding, $targetEncoding);
1213 }
1214 }
1215
1216 return $data;
1217 }
1218
1229 public function putContents($data, $flags = self::REWRITE)
1230 {
1231 // encoding
1232 $operatingEncoding = $this->getOperatingEncoding();
1233 $sourceEncoding = $this->getSourceEncoding();
1234 if ($operatingEncoding != $sourceEncoding)
1235 {
1236 $data = Main\Text\Encoding::convertEncoding($data, $operatingEncoding, $sourceEncoding);
1237 }
1238
1239 \set_error_handler(
1240 function ($severity, $message, $file, $line)
1241 {
1242 throw new \ErrorException($message, $severity, $severity, $file, $line);
1243 }
1244 );
1245
1246 try
1247 {
1248 $result = parent::putContents($data, $flags);
1249 }
1250 catch (\ErrorException $exception)
1251 {
1252 \restore_error_handler();
1253 throw new Main\IO\IoException($exception->getMessage());
1254 }
1255
1256 \restore_error_handler();
1257
1258 return $result;
1259 }
1260
1261 //endregion
1262
1263 //region Excess & Deficiency
1264
1272 public function countExcess(self $ethalon): int
1273 {
1274 return (int)\count(\array_diff($this->getCodes(), $ethalon->getCodes()));
1275 }
1276
1284 public function countDeficiency(self $ethalon): int
1285 {
1286 return (int)\count(\array_diff($ethalon->getCodes(), $this->getCodes()));
1287 }
1288
1289 //endregion
1290}
isFile()
Definition fileentry.php:38
getExtension()
Definition fileentry.php:12
$path
getPath()
getPhysicalPath()
getDirectory()
lint(string $content='', array $validTokens=[\T_OPEN_TAG, \T_CLOSE_TAG, \T_WHITESPACE, \T_CONSTANT_ENCAPSED_STRING, \T_VARIABLE, \T_COMMENT, \T_DOC_COMMENT], array $validChars=['[', ']', ';', '='])
Definition file.php:206
static instantiateByIoFile(Main\IO\File $fileIn)
Definition file.php:85
setLangId(string $languageId)
Definition file.php:120
setOperatingEncoding(string $encoding)
Definition file.php:186
setSourceEncoding(string $encoding)
Definition file.php:158
static instantiateByPath(string $path)
Definition file.php:50
static instantiateByIndex(Index\FileIndex $fileIndex)
Definition file.php:71
$GLOBALS['____1444769544']
Definition license.php:1
countDeficiency(self $ethalon)
Definition file.php:1284
addError(Main\Error $error)
Definition error.php:22
return $this messagesCount
Definition file.php:1189
countExcess(self $ethalon)
Definition file.php:1272