<?php
// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project
//
// All Rights Reserved. See copyright.txt for details and a complete list of authors.
// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details.
// $Id: GetStrings.php 66109 2018-04-19 18:20:12Z luciash $

/**
 * @package   Tiki
 * @subpackage    Language
 */

class Language_GetStrings
{
	/**
	 * Array of file types objects.
	 *
	 * @var array
	 */
	protected $fileTypes = [];

	/**
	 * Array of valid extensions. Extracted
	 * from $this->fileTypes.
	 *
	 * @var array
	 */
	protected $extensions = [];

	/**
	 * List of languages whose language.php
	 * files will be updated. If empty all
	 * language.php files are updated.
	 *
	 * @var array
	 */
	protected $languages = [];

	/**
	 * Name of the file that contain the
	 * translations.
	 * @var string
	 */
	protected $fileName = 'language.php';

	/**
	 * @var Language_CollectFiles
	 */
	public $collectFiles;

	/**
	 * @var Language_WriteFile_Factory
	 */
	public $writeFileFactory;

	/**
	 * Whether file paths where the string was found
	 * is included or not in langauge.php files. Default
	 * is false.
	 *
	 * @var bool
	 */
	protected $outputFiles = false;

	/**
	 * Directory used as base to search for strings
	 * and to construct paths to language.php files.
	 * @var string
	 */
	protected $baseDir;

	/**
	 * Class construct.
	 *
	 * The following are valid $options:
	 *   - 'outputFiles' => true: will write to language.php file the path
	 *     to the files where the string was found. Default is false.
	 *   - 'lang' => 'langCode' or 'lang' => array(list of lang codes):
	 *     language code or list of language codes whose language.php will be
	 *     updated. If empty, all language.php files are updated.
	 *
	 * @param Language_CollectFiles $collectFiles
	 * @param Language_WriteFile_Factory $writeFileFactory factory to create Language_WriteFile objects
	 * @param array $options list of options to control object behavior (see above)
	 * @return null
	 */
	public function __construct(Language_CollectFiles $collectFiles, Language_WriteFile_Factory $writeFileFactory, array $options = null)
	{
		$this->collectFiles = $collectFiles;
		$this->writeFileFactory = $writeFileFactory;

		if (isset($options['outputFiles'])) {
			$this->outputFiles = true;
		}

		if (isset($options['baseDir'])) {
			if (! is_dir($options['baseDir'])) {
				throw new Language_Exception("Invalid directory {$options['baseDir']}.");
			}

			$this->baseDir = $options['baseDir'];
		} else {
			$this->baseDir = getcwd();
		}

		if (isset($options['fileName'])) {
			$this->fileName = $options['fileName'];
		}

		if (isset($options['lang'])) {
			$this->setLanguages($options['lang']);
		} else {
			$this->setLanguages();
		}
	}

	/**
	 * Getter for $this->extensions
	 * @return array
	 */
	public function getExtensions()
	{
		return $this->extensions;
	}

	/**
	 * Getter for $this->fileTypes
	 * @return array
	 */
	public function getFileTypes()
	{
		return $this->fileTypes;
	}

	/**
	 * Add a file type object to $this->fileTypes
	 * and update $this->extensions.
	 *
	 * @param $fileType Language_FileType
	 * @return null
	 * @throws Language_Exception if type being added already exists
	 */
	public function addFileType(Language_FileType $fileType)
	{
		if (in_array($fileType, $this->fileTypes)) {
			$className = get_class($fileType);
			throw new Language_Exception("Type $className already added.");
		}

		$this->fileTypes[] = $fileType;
		$this->extensions = array_merge($this->extensions, $fileType->getExtensions());
	}

	/**
	 * Setter method $this->languages
	 * property.
	 *
	 * @param array|string $languages
	 * @return null
	 */
	public function setLanguages($languages = null)
	{
		if (is_null($languages)) {
			$languages = $this->getAllLanguages();
		} else {
			if (is_string($languages)) {
				$languages = [$languages];
			}

			foreach ($languages as $lang) {
				if (! file_exists($this->baseDir . '/lang/' . $lang)) {
					throw new Language_Exception('Invalid language code.');
				}
			}
		}

		$this->languages = $languages;
	}

	/**
	 * Getter method for $this->languages.
	 *
	 * @return array
	 */
	public function getLanguages()
	{
		return $this->languages;
	}

	/**
	 * Get English strings from a given file.
	 *
	 * @param string $filePath path to file
	 * @return array collected strings
	 */
	public function collectStrings($filePath)
	{
		if (empty($this->fileTypes)) {
			throw new Language_Exception('No Language_FileType found.');
		}

		$strings = [];
		$fileExtension = strrchr($filePath, '.');

		if (! $fileExtension || $fileExtension == '.') {
			throw new Language_Exception('Could not determine file extension.');
		}

		foreach ($this->fileTypes as $fileType) {
			if (in_array($fileExtension, $fileType->getExtensions())) {
				$file = file_get_contents($filePath);

				foreach ($fileType->getCleanupRegexes() as $regex => $replacement) {
					$file = preg_replace($regex, $replacement, $file);
				}

				foreach ($fileType->getRegexes() as $postProcess => $regex) {
					$matches = [];
					preg_match_all($regex, $file, $matches);
					$newStrings = $matches[1];

					// $postProcess can be used to call a file type specific method for each regular expression
					// used for PHP file type to perform different clean up for single quoted and double quoted strings
					if (method_exists($fileType, $postProcess)) {
						$newStrings = $fileType->$postProcess($newStrings);
					}

					$strings = array_merge($strings, $newStrings);
				}

				break;
			}
		}

		return array_values(array_unique($strings));
	}

	/**
	 * Loop through a list of files and
	 * calls $this->collectStrings() for each
	 * file. Return a list of translatable strings
	 * found.
	 *
	 * @param array $files
	 * @return array $strings translatable strings found in scanned files
	 */
	public function scanFiles($files)
	{
		$strings = [];

		// strings collected per file
		$filesStrings = [];

		if (! empty($files)) {
			foreach ($files as $file) {
				$filesStrings[$file] = $this->collectStrings($file);
			}
		}

		// join strings collected per file into a single array
		// and remove duplicated strings
		foreach ($filesStrings as $file => $fileStrings) {
			foreach ($fileStrings as $str) {
				if (! isset($strings[$str])) {
					$string = ['name' => $str];

					if ($this->outputFiles) {
						// $string['files'] is an array with all the files where the string was found
						$string['files'] = [$file];
					}

					$strings[$str] = $string;
				} else {
					if ($this->outputFiles) {
						$strings[$str]['files'][] = $file;
					}
				}
			}
		}

		return $strings;
	}

	public function writeToFiles($strings)
	{
		foreach ($this->languages as $lang) {
			$filePath = $this->baseDir . '/lang/' . $lang . '/' . $this->fileName;
			$writeFile = $this->writeFileFactory->factory($filePath);
			$writeFile->writeStringsToFile($strings, $this->outputFiles);
		}
	}

	/**
	 * Return all available languages (check for the
	 * existence of a language file).
	 * @return array all language codes
	 */
	protected function getAllLanguages()
	{
		$dirs = dir($this->baseDir . '/lang');

		while (false !== ($entry = $dirs->read())) {
			if ($entry == '.' || $entry == '..') {
				continue;
			}

			$path = $dirs->path . '/' . $entry;
			if (is_dir($path) && file_exists($path . '/' . $this->fileName)) {
				$languages[] = $entry;
			}
		}

		return $languages;
	}

	public function run()
	{
		if (empty($this->fileTypes)) {
			throw new Language_Exception('No Language_FileType found.');
		}

		$this->collectFiles->setExtensions($this->extensions);
		$files = $this->collectFiles->run($this->baseDir);
		$strings = $this->scanFiles($files);
		$this->writeToFiles($strings);
	}
}
