Current Path : /var/www/www-root/data/www/info.monolith-realty.ru/bitrix/modules/translate/lib/ |
Current File : /var/www/www-root/data/www/info.monolith-realty.ru/bitrix/modules/translate/lib/file.php |
<?php namespace Bitrix\Translate; use Bitrix\Main; use Bitrix\Translate; use Bitrix\Translate\Index; use Bitrix\Translate\Text\StringHelper; class File extends Translate\IO\File implements \Iterator, \Countable, \ArrayAccess { /** @var string */ protected $languageId; /** @var string */ protected $sourceEncoding; /** @var string */ protected $operatingEncoding; /** @var string[] */ protected $messages = null; /** @var int */ protected $messagesCount = null; /** @var array */ protected $messageCodes = []; /** @var array */ protected $messageEnclosure = []; /** @var int */ protected $dataPosition = 0; /** @var Index\FileIndex */ protected $fileIndex; //region Fabric /** * Constructs instance by path. * * @param string $path Path to language file. * * @return Translate\File * @throws Main\ArgumentException */ public static function instantiateByPath(string $path): self { if (empty($path) || !Translate\IO\Path::isPhpFile($path) || !\preg_match("#.+/lang/[a-z0-9]{2}/.+\.php$#", $path)) { throw new Main\ArgumentException("Parameter 'path' has a wrong value"); } $file = (new static($path)) ->setLangId(Translate\IO\Path::extractLangId($path)); return $file; } /** * Constructs instance by file index. * * @param Index\FileIndex $fileIndex Language file index. * * @return Translate\File */ public static function instantiateByIndex(Index\FileIndex $fileIndex): self { return (new static($fileIndex->getFullPath()))->setLangId($fileIndex->getLangId()); } /** * Constructs instance by io file. * * @param Main\IO\File $fileIn Language file. * * @return Translate\File * @throws Main\ArgumentException */ public static function instantiateByIoFile(Main\IO\File $fileIn): self { if ($fileIn->getExtension() !== 'php') { throw new Main\ArgumentException(); } return (new static($fileIn->getPath()))->setLangId(Translate\IO\Path::extractLangId($fileIn->getPath())); } //endregion //region Language & Encoding /** * Returns language code of the file. If it is empty tries to detect it. * @return string */ public function getLangId(): string { if (empty($this->languageId)) { $this->languageId = Translate\IO\Path::extractLangId($this->getPath()); } return $this->languageId; } /** * Sets language code of the file. * * @param string $languageId Lang code. * * @return self */ public function setLangId(string $languageId): self { $this->languageId = $languageId; return $this; } /** * Returns source encoding of the file. * @return string */ public function getSourceEncoding(): string { static $encodingCache = []; if (empty($this->sourceEncoding)) { $language = $this->getLangId(); if (isset($encodingCache[$language])) { $this->sourceEncoding = $encodingCache[$language]; } else { $this->sourceEncoding = Main\Localization\Translation::getSourceEncoding($language); $encodingCache[$language] = $this->sourceEncoding; } } return $this->sourceEncoding; } /** * Sets source encoding of the file. * * @param string $encoding Encoding code. * * @return self */ public function setSourceEncoding(string $encoding): self { $this->sourceEncoding = $encoding; return $this; } /** * Returns operating encoding. * @return string */ public function getOperatingEncoding(): string { if (empty($this->operatingEncoding)) { $this->operatingEncoding = Main\Localization\Translation::getCurrentEncoding(); } return $this->operatingEncoding; } /** * Sets operating encoding. * * @param string $encoding Encoding code. * * @return self */ public function setOperatingEncoding(string $encoding): self { $this->operatingEncoding = $encoding; return $this; } // endregion //region Validators /** * Lints php code. * * @param string $content Content to validate either content of the current file will be taken. * @param int[] $validTokens Allowed php tokens. * @param string[] $validChars Allowed statement characters. * * @return bool */ public function 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 = ['[', ']', ';', '='] ): bool { $isValid = false; if (empty($content)) { if ($this->isExists()) { $content = $this->getContents(); } } if (empty($content) || !\is_string($content)) { $this->addError(new Main\Error("Parse Error: Empty content")); return $isValid; } $tokens = \token_get_all($content); $line = $tokens[0][2] || 1; if (!is_array($tokens[0]) || $tokens[0][0] !== \T_OPEN_TAG) { $this->addError(new Main\Error("Parse Error: Wrong open tag ".\token_name($tokens[0][0])." '{$tokens[0][1]}' at line {$line}")); } else { $isValid = true; foreach ($tokens as $token) { if (\is_array($token)) { $line = $token[2]; if ( !\in_array($token[0], $validTokens) || ($token[0] === \T_VARIABLE && $token[1] != '$MESS') ) { $this->addError(new Main\Error("Parse Error: Wrong token ". \token_name($token[0]). " '{$token[1]}' at line {$line}")); $isValid = false; break; } } elseif (\is_string($token)) { if (!\in_array($token, $validChars)) { $line ++; $this->addError(new Main\Error("Parse Error: Expected character '{$token}' at line {$line}")); $isValid = false; break; } } } } return $isValid; } // endregion //region Load /** * Loads language file for operate. * * @return bool * @throws \ParseError */ public function load(): bool { $this->messages = []; $this->messageCodes = []; $this->messagesCount = 0; if (!$this->isExists() || !$this->isFile() || ($this->getExtension() !== 'php')) { return false; } // language id $langId = $this->getLangId(); if (empty($langId)) { $this->addError(new Main\Error('Language Id must be filled')); return false; } $content = $this->getContents(); if ( empty($content) || !\is_string($content) || $content === '<?' || $content === '<?php' ) { $this->addError(new Main\Error('Empty content', 'EMPTY_CONTENT')); return false; } // encoding $targetEncoding = $this->getOperatingEncoding(); $sourceEncoding = $this->getSourceEncoding(); $convertEncoding = (\mb_strtolower($targetEncoding) != \mb_strtolower($sourceEncoding)); if ($convertEncoding) { $path = Main\Localization\Translation::convertLangPath($this->getPhysicalPath(), $this->getLangId()); if (Main\Localization\Translation::getDeveloperRepositoryPath() !== null) { $convertEncoding = (\stripos($path, Main\Localization\Translation::getDeveloperRepositoryPath()) === 0); } if (!$convertEncoding && Main\Localization\Translation::useTranslationRepository()) { $convertEncoding = (\stripos($path, Main\Localization\Translation::getTranslationRepositoryPath()) === 0); } } $messages = (function(){ if (isset($GLOBALS['MESS'])) { unset($GLOBALS['MESS']); } $MESS = []; \ob_start(); include $this->getPhysicalPath(); \ob_end_clean(); return $MESS; })(); if (\is_array($messages) && \count($messages) > 0) { foreach ($messages as $phraseId => $phrase) { if ($convertEncoding) { $phrase = Main\Text\Encoding::convertEncoding($phrase, $sourceEncoding, $targetEncoding); } $this->messages[$phraseId] = $phrase; $this->messageCodes[] = $phraseId; $this->messagesCount ++; } } return true; } /** * Lints php code. * * @return bool */ public function loadTokens(): bool { $this->messages = []; $this->messageCodes = []; $this->messageEnclosure = []; $this->messagesCount = 0; if (!$this->isExists() || !$this->isFile() || ($this->getExtension() !== 'php')) { return false; } // language id $langId = $this->getLangId(); if (empty($langId)) { $this->addError(new Main\Error('Language Id must be filled')); return false; } $content = $this->getContents(); if ( empty($content) || !\is_string($content) || $content === '<?' || $content === '<?php' ) { $this->addError(new Main\Error('Empty content', 'EMPTY_CONTENT')); return false; } $is = function ($token, $type, $value = null) { if (\is_string($token)) { return $token === $type; } if (\is_array($token)) { if ($token[0] === $type) { if ($value !== null) { return $token[1] === $value; } return true; } } return false; }; $tokens = \token_get_all($content); $hasPhraseDefinition = false; foreach ($tokens as $inx => $token) { if ($is($token, \T_WHITESPACE)) { unset($tokens[$inx]); continue; } if (!$hasPhraseDefinition && $is($token, \T_VARIABLE, '$MESS')) { $hasPhraseDefinition = true; } //if (is_array($token))$tokens[$inx][] = \token_name($token[0]); } if (!$hasPhraseDefinition) { $this->addError(new Main\Error("There are no phrase definitions")); return false; } \array_splice($tokens, 0, 0); $addPhrase = function ($phraseId, $phraseParts, $isHeredoc = false) { if ($phraseId != '') { $len = \mb_strlen($phraseId, $this->getOperatingEncoding()); $phraseId = \mb_substr($phraseId, 1, $len - 2, $this->getOperatingEncoding());// strip trailing quotes $phraseId = \str_replace("\\\\", "\\", $phraseId);// strip slashes in code $enclosure = $isHeredoc ? '<<<' : \mb_substr($phraseParts[0], 0, 1);// what quote $phrase = ''; if ($isHeredoc) { $part = $phraseParts[0]; $len = \mb_strlen($part, $this->getOperatingEncoding()); $phrase = \mb_substr($part, 0, $len - 1, $this->getOperatingEncoding());// strip final \n } else { foreach ($phraseParts as $part) { $enclosure = \mb_substr($part, 0, 1);// what quote // strip trailing quotes if ($enclosure === '"' || $enclosure === "'") { $len = \mb_strlen($part, $this->getOperatingEncoding()); $part = \mb_substr($part, 1, $len - 2, $this->getOperatingEncoding()); } //$part = StringHelper::unescapePhp($part, $enclosure); $phrase .= $part; } } $this->messages[$phraseId] = $phrase; $this->messageCodes[] = $phraseId; $this->messageEnclosure[$phraseId] = $enclosure; $this->messagesCount++; } }; $startPhrase = false; $endPhrase = false; $inPhrase = false; $inCode = false; $isHeredoc = false; $phraseId = ''; $phrase = []; $whereIsPhrase = []; foreach ($tokens as $inx => &$token) { if (!$startPhrase && $is($token, \T_VARIABLE, '$MESS')) { $startPhrase = true; } if ($startPhrase) { if ($is($token, '[')) { $inCode = true; } elseif ($is($token, ']')) { $inCode = false; } elseif ($is($token, '=')) { $inPhrase = true; } elseif ($is($token, ';')) { $endPhrase = true; } elseif ($is($token, \T_CLOSE_TAG)) { $endPhrase = true; } elseif ($is($token, \T_START_HEREDOC)) { $isHeredoc = true; } if ( $inPhrase && $is($token, \T_VARIABLE, '$MESS') && $is($tokens[$inx + 1], '[') && $is($tokens[$inx + 2], \T_CONSTANT_ENCAPSED_STRING) ) { $clonePhraseId = $tokens[$inx + 2][1]; $cloneInx = $whereIsPhrase[$clonePhraseId]; $phrase[] = $tokens[$cloneInx][1]; $endPhrase = true; } if ($is($token, \T_CONSTANT_ENCAPSED_STRING) || $is($token, \T_ENCAPSED_AND_WHITESPACE)) { if ($inPhrase) { $phrase[] = $token[1]; $whereIsPhrase[$phraseId] = $inx; } if ($inCode) { $phraseId = $token[1]; } } if ($endPhrase) { $addPhrase($phraseId, $phrase, $isHeredoc); $phrase = []; $phraseId = ''; $startPhrase = false; $endPhrase = false; $inPhrase = false; $inCode = false; $isHeredoc = false; } } // todo: Handle here developer's comment from file // \T_COMMENT T_DOC_COMMENT } if ($startPhrase) { $addPhrase($phraseId, $phrase, $isHeredoc); } return true; } //endregion //region Save /** * Save changes or create new file. * * @return bool * @throws Main\IO\IoException * @throws Main\SystemException */ public function save(): bool { // language id $langId = $this->getLangId(); if (empty($langId)) { throw new Main\SystemException("Language Id must be filled"); } // encoding $operatingEncoding = $this->getOperatingEncoding(); $sourceEncoding = $this->getSourceEncoding(); $convertEncoding = (\mb_strtolower($operatingEncoding) != \mb_strtolower($sourceEncoding)); if ($convertEncoding) { $path = Main\Localization\Translation::convertLangPath($this->getPhysicalPath(), $this->getLangId()); if (Main\Localization\Translation::getDeveloperRepositoryPath() !== null) { $convertEncoding = (\stripos($path, Main\Localization\Translation::getDeveloperRepositoryPath()) === 0); } if (!$convertEncoding && Main\Localization\Translation::useTranslationRepository()) { $convertEncoding = (\stripos($path, Main\Localization\Translation::getTranslationRepositoryPath()) === 0); } } $content = ''; foreach ($this->messages as $phraseId => $phrase) { if (empty($phrase) && $phrase !== '0') { // remove empty continue; } $phrase = \str_replace(["\r\n", "\r"], ["\n", ''], $phrase); if ($convertEncoding) { $phrase = Main\Text\Encoding::convertEncoding($phrase, $operatingEncoding, $sourceEncoding); } $enclosure = '"'; if (isset($this->messageEnclosure[$phraseId])) { $enclosure = $this->messageEnclosure[$phraseId];// preserve origin quote } $phraseId = StringHelper::escapePhp($phraseId, '"', "\\\\"); if (StringHelper::hasPhpTokens($phraseId, '"')) { $this->addError(new Main\Error("Phrase code contains php tokens", 'ERROR_PHP_TOKEN_CODE', ['phraseId' => $phraseId])); return false; } $phrase = StringHelper::escapePhp($phrase, $enclosure); if (StringHelper::hasPhpTokens($phrase, $enclosure)) { $this->addError(new Main\Error("Phrase contains php tokens", 'ERROR_PHP_TOKEN_PHRASE', ['phraseId' => $phraseId])); return false; } $row = '$MESS["'. $phraseId. '"] = '; if ($enclosure === '<<<') { $row .= "<<<HTML\n". $phrase. "\nHTML"; } else { $row .= $enclosure. $phrase. $enclosure; } $content .= "\n". $row. ';'; } unset($phraseId, $phrase, $row); if ($content <> '') { \set_error_handler( function ($severity, $message, $file, $line) { throw new \ErrorException($message, $severity, $severity, $file, $line); } ); try { $result = parent::putContents('<?php'. $content. "\n"); } catch (\ErrorException $exception) { \restore_error_handler(); throw new Main\IO\IoException($exception->getMessage()); } \restore_error_handler(); if ($result === false) { $filePath = $this->getPath(); throw new Main\IO\IoException("Couldn't write language file '{$filePath}'"); } } else { // todo: Add module setting that will allow / disallow drop empty lang files. if ($this->isExists()) { $this->markWritable(); $this->delete(); } } return true; } /** * Removes empty parent chain up to "lang". * * @return bool */ public function removeEmptyParents(): bool { // todo: Add module setting that will allow / disallow drop empty lang folders. $ret = true; $parentFolder = $this->getDirectory(); while (true) { if ($parentFolder->isExists() && \count($parentFolder->getChildren()) > 0) { $ret = false; break; } if ($parentFolder->isExists()) { if ($parentFolder->delete() !== true) { $ret = false; break; } } if ($parentFolder->getName() === 'lang') { break; } $parentFolder = $parentFolder->getDirectory(); } return $ret; } /** * Performs backup action. * * @return bool */ public function backup(): bool { if (!$this->isExists()) { return true; } $langId = $this->getLangId(); $fullPath = $langFile = $this->getPhysicalPath(); if (Main\Localization\Translation::useTranslationRepository() && in_array($langId, Translate\Config::getTranslationRepositoryLanguages())) { if (\mb_strpos($langFile, Main\Localization\Translation::getTranslationRepositoryPath()) === 0) { $langFile = \str_replace( Main\Localization\Translation::getTranslationRepositoryPath(). '/', '', $langFile ); } } if (Main\Localization\Translation::getDeveloperRepositoryPath() !== null) { if (\mb_strpos($langFile, Main\Localization\Translation::getDeveloperRepositoryPath()) === 0) { $langFile = \str_replace( Main\Localization\Translation::getDeveloperRepositoryPath(). '/', '', $langFile ); } } if (\mb_strpos($langFile, Main\Application::getDocumentRoot()) === 0) { $langFile = \str_replace( Main\Application::getDocumentRoot(). '/', '', $langFile ); } $backupFolder = Translate\Config::getBackupFolder(). '/'. \dirname($langFile). '/'; if (!Translate\IO\Path::checkCreatePath($backupFolder)) { $this->addError(new Main\Error("Couldn't create backup path '{$backupFolder}'")); return false; } $sourceFilename = \basename($langFile); $prefix = \date('YmdHi'); $endpointBackupFilename = $prefix. '_'. $sourceFilename; if (\file_exists($backupFolder. $endpointBackupFilename)) { $i = 1; while (\file_exists($backupFolder. '/'. $endpointBackupFilename)) { $i ++; $endpointBackupFilename = $prefix. '_'. $i. '_'. $sourceFilename; } } $isSuccessfull = (bool) @\copy($fullPath, $backupFolder. '/'. $endpointBackupFilename); @\chmod($backupFolder. '/'. $endpointBackupFilename, \BX_FILE_PERMISSIONS); if (!$isSuccessfull) { $this->addError(new Main\Error("Couldn't backup file '{$fullPath}'")); } return $isSuccessfull; } //endregion //region Index /** * Returns or creates file index instance. * * @return Index\FileIndex */ public function getFileIndex(): Index\FileIndex { if (!$this->fileIndex instanceof Index\FileIndex) { $indexFileRes = Index\Internals\FileIndexTable::getList([ 'filter' => [ '=LANG_ID' => $this->getLangId(), '=FULL_PATH' => $this->getPath(), ], 'limit' => 1 ]); $this->fileIndex = $indexFileRes->fetchObject(); } if (!$this->fileIndex instanceof Index\FileIndex) { $this->fileIndex = (new Index\FileIndex()) ->setFullPath($this->getPath()) ->setLangId($this->getLangId()); } return $this->fileIndex; } /** * Updates phrase index. * * @return Index\FileIndex */ public function updatePhraseIndex(): Index\FileIndex { $this->getFileIndex(); $fileId = $this->fileIndex->getId(); if ($fileId > 0) { $phraseId = Index\Internals\PhraseIndexTable::query() ->registerRuntimeField(new Main\ORM\Fields\ExpressionField('MAXID', 'MAX(%s)', ['ID'])) ->addSelect('MAXID') ->exec() ->fetch()['MAXID']; $pathId = $this->fileIndex->getPathId(); $phraseData = []; $phraseCodeData = []; foreach ($this as $code => $phrase) { $phraseId ++; $langId = $this->getLangId(); $phraseCodeData[] = [ 'ID' => $phraseId, 'FILE_ID' => $fileId, 'PATH_ID' => $pathId, 'LANG_ID' => $langId, 'CODE' => $code, ]; if (!isset($phraseData[$langId])) { $phraseData[$langId] = []; } $phraseData[$langId][] = [ 'ID' => $phraseId, 'FILE_ID' => $fileId, 'PATH_ID' => $pathId, 'CODE' => $code, 'PHRASE' => $phrase, ]; } // delete $filter = new Translate\Filter(['fileId' => $fileId]); Index\Internals\PhraseIndexTable::purge($filter); foreach (Translate\Config::getEnabledLanguages() as $langId) { $ftsClass = Index\Internals\PhraseFts::getFtsEntityClass($langId); $ftsClass::purge($filter); } // add if (\count($phraseCodeData) > 0) { Index\Internals\PhraseIndexTable::bulkAdd($phraseCodeData); foreach ($phraseData as $langId => $phraseLangData) { $ftsClass = Index\Internals\PhraseFts::getFtsEntityClass($langId); $ftsClass::bulkAdd($phraseLangData, 'ID'); } } $this->fileIndex ->setPhraseCount($this->count()) ->setIndexed(true) ->setIndexedTime(new Main\Type\DateTime()) ->save(); } return $this->fileIndex; } /** * Drops phrase index. * * @return bool */ public function deletePhraseIndex(): bool { $this->getFileIndex(); if ($this->fileIndex->getId() > 0) { $filter = new Translate\Filter(['id' => $this->fileIndex->getId()]); Index\Internals\FileIndexTable::purge($filter); foreach (Translate\Config::getEnabledLanguages() as $langId) { $ftsClass = Index\Internals\PhraseFts::getFtsEntityClass($langId); $ftsClass::purge($filter); } unset($this->fileIndex); } return true; } /** * Returns ORM\Collection object. * * @return Index\PhraseIndexCollection */ public function getPhraseIndexCollection(): Index\PhraseIndexCollection { $phraseIndexCollection = new Index\PhraseIndexCollection(); foreach ($this->messages as $code => $message) { $phraseIndexCollection[] = (new Index\PhraseIndex) ->setLangId($this->getLangId()) ->setCode($code) ->setPhrase($message) ; } return $phraseIndexCollection; } //endregion //region ArrayAccess /** * Checks existence of the phrase by its code. * * @param string $code Phrase code. * * @return boolean */ public function offsetExists($code): bool { return isset($this->messages[$code]); } /** * Returns phrase by its code. * * @param string $code Phrase code. * * @return string|null */ public function offsetGet($code): ?string { if (isset($this->messages[$code])) { return $this->messages[$code]; } return null; } /** * Offset to set * * @param string $code Phrase code. * @param string $phrase Phrase. * * @return void */ public function offsetSet($code, $phrase): void { if (!isset($this->messages[$code])) { if ($this->messagesCount === null) { $this->messagesCount = 1; } else { $this->messagesCount ++; } $this->messageCodes[] = $code; } $this->messages[$code] = $phrase; } /** * Unset phrase by code. * * @param string $code Phrase code. * * @return void */ public function offsetUnset($code): void { if (isset($this->messages[$code])) { unset($this->messages[$code]); $this->messagesCount --; if (($i = \array_search($code, $this->messageCodes)) !== false) { unset($this->messageCodes[$i]); } } } /** * Sorts phrases by key, except russian. * * @return self */ public function sortPhrases() { \ksort($this->messages, \SORT_NATURAL); $this->rewind(); return $this; } /** * Returns all phrases from the language file with theirs codes. * @return array */ public function getPhrases() { return $this->messages; } /** * Returns all phrase codes from the language file. * @return string[] */ public function getCodes() { return \is_array($this->messages) ? \array_keys($this->messages) : []; } /** * Returns preserved origin quote. * @param string $phraseId * @return string */ public function getEnclosure(string $phraseId): string { $enclosure = '"'; if (isset($this->messageEnclosure[$phraseId])) { $enclosure = $this->messageEnclosure[$phraseId]; } return $enclosure; } //endregion //region Iterator /** * Return the current phrase element. * * @return string|null */ public function current(): ?string { $code = $this->messageCodes[$this->dataPosition]; if (!isset($this->messages[$code]) || !\is_string($this->messages[$code]) || (empty($this->messages[$code]) && $this->messages[$code] !== '0')) { return null; } return $this->messages[$code]; } /** * Move forward to next phrase element. * * @return void */ public function next(): void { ++ $this->dataPosition; } /** * Return the key of the current phrase element. * * @return int|null */ #[\ReturnTypeWillChange] public function key() { return $this->messageCodes[$this->dataPosition] ?: null; } /** * Checks if current position is valid. * * @return bool */ public function valid(): bool { return isset($this->messageCodes[$this->dataPosition], $this->messages[$this->messageCodes[$this->dataPosition]]); } /** * Rewind the Iterator to the first element. * * @return void */ public function rewind(): void { $this->dataPosition = 0; $this->messageCodes = \array_keys($this->messages); } //endregion //region Countable /** * Returns amount phrases in the language file. * * @param bool $allowDirectFileAccess Allow include file to count phrases. * * @return int */ public function count($allowDirectFileAccess = false): int { if ($this->messagesCount === null) { if ($this->messages !== null && \count($this->messages) > 0) { $this->messagesCount = \count($this->messages); } elseif ($allowDirectFileAccess) { $MESS = array(); include $this->getPhysicalPath(); if (\is_array($MESS) && \count($MESS) > 0) { $this->messagesCount = \count($MESS); } } } return $this->messagesCount ?: 0; } //endregion //region Content /** * Returns string fiile content. * * @return string|bool */ public function getContents() { $data = parent::getContents(); if (\is_string($data)) { // encoding $targetEncoding = $this->getOperatingEncoding(); $sourceEncoding = $this->getSourceEncoding(); if ($targetEncoding != $sourceEncoding) { $data = Main\Text\Encoding::convertEncoding($data, $sourceEncoding, $targetEncoding); } } return $data; } /** * Puts data sting into file. * * @param string $data Data to save. * @param int $flags Flag to operate previous content @see Main\IO\File::REWRITE | Main\IO\File::APPEND. * * @return bool|int * @throws Main\IO\FileNotFoundException * @throws Main\IO\IoException */ public function putContents($data, $flags = self::REWRITE) { // encoding $operatingEncoding = $this->getOperatingEncoding(); $sourceEncoding = $this->getSourceEncoding(); if ($operatingEncoding != $sourceEncoding) { $data = Main\Text\Encoding::convertEncoding($data, $operatingEncoding, $sourceEncoding); } \set_error_handler( function ($severity, $message, $file, $line) { throw new \ErrorException($message, $severity, $severity, $file, $line); } ); try { $result = parent::putContents($data, $flags); } catch (\ErrorException $exception) { \restore_error_handler(); throw new Main\IO\IoException($exception->getMessage()); } \restore_error_handler(); return $result; } //endregion //region Excess & Deficiency /** * Compares two files and returns excess amount of phrases. * * @param self $ethalon File to compare. * * @return int */ public function countExcess(self $ethalon): int { return (int)\count(\array_diff($this->getCodes(), $ethalon->getCodes())); } /** * Compares two files and returns deficiency amount of phrases. * * @param self $ethalon File to compare. * * @return int */ public function countDeficiency(self $ethalon): int { return (int)\count(\array_diff($ethalon->getCodes(), $this->getCodes())); } //endregion }