1<?php 2// (c) Copyright by authors of the Tiki Wiki CMS Groupware Project 3// 4// All Rights Reserved. See copyright.txt for details and a complete list of authors. 5// Licensed under the GNU LESSER GENERAL PUBLIC LICENSE. See license.txt for details. 6// $Id$ 7 8/** 9 * @package Tiki 10 * @subpackage Language 11 */ 12 13class Language_GetStrings 14{ 15 /** 16 * Array of file types objects. 17 * 18 * @var array 19 */ 20 protected $fileTypes = []; 21 22 /** 23 * Array of valid extensions. Extracted 24 * from $this->fileTypes. 25 * 26 * @var array 27 */ 28 protected $extensions = []; 29 30 /** 31 * List of languages whose language.php 32 * files will be updated. If empty all 33 * language.php files are updated. 34 * 35 * @var array 36 */ 37 protected $languages = []; 38 39 /** 40 * Name of the file that contain the 41 * translations. 42 * @var string 43 */ 44 protected $fileName = 'language.php'; 45 46 /** 47 * @var Language_CollectFiles 48 */ 49 public $collectFiles; 50 51 /** 52 * @var Language_WriteFile_Factory 53 */ 54 public $writeFileFactory; 55 56 /** 57 * Whether file paths where the string was found 58 * is included or not in langauge.php files. Default 59 * is false. 60 * 61 * @var bool 62 */ 63 protected $outputFiles = false; 64 65 /** 66 * Directory used as base to search for strings 67 * and to construct paths to language.php files. 68 * @var string 69 */ 70 protected $baseDir; 71 72 /** 73 * Class construct. 74 * 75 * The following are valid $options: 76 * - 'outputFiles' => true: will write to language.php file the path 77 * to the files where the string was found. Default is false. 78 * - 'lang' => 'langCode' or 'lang' => array(list of lang codes): 79 * language code or list of language codes whose language.php will be 80 * updated. If empty, all language.php files are updated. 81 * 82 * @param Language_CollectFiles $collectFiles 83 * @param Language_WriteFile_Factory $writeFileFactory factory to create Language_WriteFile objects 84 * @param array $options list of options to control object behavior (see above) 85 * @return null 86 */ 87 public function __construct(Language_CollectFiles $collectFiles, Language_WriteFile_Factory $writeFileFactory, array $options = null) 88 { 89 $this->collectFiles = $collectFiles; 90 $this->writeFileFactory = $writeFileFactory; 91 92 if (isset($options['outputFiles'])) { 93 $this->outputFiles = true; 94 } 95 96 if (isset($options['baseDir'])) { 97 if (! is_dir($options['baseDir'])) { 98 throw new Language_Exception("Invalid directory {$options['baseDir']}."); 99 } 100 101 $this->baseDir = $options['baseDir']; 102 } else { 103 $this->baseDir = getcwd(); 104 } 105 106 if (isset($options['fileName'])) { 107 $this->fileName = $options['fileName']; 108 } 109 110 if (isset($options['lang'])) { 111 $this->setLanguages($options['lang']); 112 } else { 113 $this->setLanguages(); 114 } 115 } 116 117 /** 118 * Getter for $this->extensions 119 * @return array 120 */ 121 public function getExtensions() 122 { 123 return $this->extensions; 124 } 125 126 /** 127 * Getter for $this->fileTypes 128 * @return array 129 */ 130 public function getFileTypes() 131 { 132 return $this->fileTypes; 133 } 134 135 /** 136 * Add a file type object to $this->fileTypes 137 * and update $this->extensions. 138 * 139 * @param $fileType Language_FileType 140 * @return null 141 * @throws Language_Exception if type being added already exists 142 */ 143 public function addFileType(Language_FileType $fileType) 144 { 145 if (in_array($fileType, $this->fileTypes)) { 146 $className = get_class($fileType); 147 throw new Language_Exception("Type $className already added."); 148 } 149 150 $this->fileTypes[] = $fileType; 151 $this->extensions = array_merge($this->extensions, $fileType->getExtensions()); 152 } 153 154 /** 155 * Setter method $this->languages 156 * property. 157 * 158 * @param array|string $languages 159 * @return null 160 */ 161 public function setLanguages($languages = null) 162 { 163 if (is_null($languages)) { 164 $languages = $this->getAllLanguages(); 165 } else { 166 if (is_string($languages)) { 167 $languages = [$languages]; 168 } 169 170 foreach ($languages as $lang) { 171 if (! file_exists($this->baseDir . '/lang/' . $lang)) { 172 throw new Language_Exception('Invalid language code.'); 173 } 174 } 175 } 176 177 $this->languages = $languages; 178 } 179 180 /** 181 * Getter method for $this->languages. 182 * 183 * @return array 184 */ 185 public function getLanguages() 186 { 187 return $this->languages; 188 } 189 190 /** 191 * Get English strings from a given file. 192 * 193 * @param string $filePath path to file 194 * @return array collected strings 195 */ 196 public function collectStrings($filePath) 197 { 198 if (empty($this->fileTypes)) { 199 throw new Language_Exception('No Language_FileType found.'); 200 } 201 202 $strings = []; 203 $fileExtension = strrchr($filePath, '.'); 204 205 if (! $fileExtension || $fileExtension == '.') { 206 throw new Language_Exception('Could not determine file extension.'); 207 } 208 209 foreach ($this->fileTypes as $fileType) { 210 if (in_array($fileExtension, $fileType->getExtensions())) { 211 $file = file_get_contents($filePath); 212 213 foreach ($fileType->getCleanupRegexes() as $regex => $replacement) { 214 $file = preg_replace($regex, $replacement, $file); 215 } 216 217 foreach ($fileType->getRegexes() as $postProcess => $regex) { 218 $matches = []; 219 preg_match_all($regex, $file, $matches); 220 $newStrings = $matches[1]; 221 222 // $postProcess can be used to call a file type specific method for each regular expression 223 // used for PHP file type to perform different clean up for single quoted and double quoted strings 224 if (method_exists($fileType, $postProcess)) { 225 $newStrings = $fileType->$postProcess($newStrings); 226 } 227 228 $strings = array_merge($strings, $newStrings); 229 } 230 231 break; 232 } 233 } 234 235 return array_values(array_unique($strings)); 236 } 237 238 /** 239 * Loop through a list of files and 240 * calls $this->collectStrings() for each 241 * file. Return a list of translatable strings 242 * found. 243 * 244 * @param array $files 245 * @return array $strings translatable strings found in scanned files 246 */ 247 public function scanFiles($files) 248 { 249 $strings = []; 250 251 // strings collected per file 252 $filesStrings = []; 253 254 if (! empty($files)) { 255 foreach ($files as $file) { 256 $filesStrings[$file] = $this->collectStrings($file); 257 } 258 } 259 260 // join strings collected per file into a single array 261 // and remove duplicated strings 262 foreach ($filesStrings as $file => $fileStrings) { 263 foreach ($fileStrings as $str) { 264 if (! isset($strings[$str])) { 265 $string = ['name' => $str]; 266 267 if ($this->outputFiles) { 268 // $string['files'] is an array with all the files where the string was found 269 $string['files'] = [$file]; 270 } 271 272 $strings[$str] = $string; 273 } else { 274 if ($this->outputFiles) { 275 $strings[$str]['files'][] = $file; 276 } 277 } 278 } 279 } 280 281 return $strings; 282 } 283 284 public function writeToFiles($strings) 285 { 286 foreach ($this->languages as $lang) { 287 $filePath = $this->baseDir . '/lang/' . $lang . '/' . $this->fileName; 288 $writeFile = $this->writeFileFactory->factory($filePath); 289 $writeFile->writeStringsToFile($strings, $this->outputFiles); 290 } 291 } 292 293 /** 294 * Return all available languages (check for the 295 * existence of a language file). 296 * @return array all language codes 297 */ 298 protected function getAllLanguages() 299 { 300 $dirs = dir($this->baseDir . '/lang'); 301 302 while (false !== ($entry = $dirs->read())) { 303 if ($entry == '.' || $entry == '..') { 304 continue; 305 } 306 307 $path = $dirs->path . '/' . $entry; 308 if (is_dir($path) && file_exists($path . '/' . $this->fileName)) { 309 $languages[] = $entry; 310 } 311 } 312 313 return $languages; 314 } 315 316 public function run() 317 { 318 if (empty($this->fileTypes)) { 319 throw new Language_Exception('No Language_FileType found.'); 320 } 321 322 $this->collectFiles->setExtensions($this->extensions); 323 $files = $this->collectFiles->run($this->baseDir); 324 $strings = $this->scanFiles($files); 325 $this->writeToFiles($strings); 326 } 327} 328