Your IP : 3.138.136.144
<?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"));
return false;
}
$phrase = StringHelper::escapePhp($phrase, $enclosure);
if (StringHelper::hasPhpTokens($phrase, $enclosure))
{
$this->addError(new Main\Error("Phrase contains php tokens"));
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
}