Completed
Push — master ( 704662...efcdfa )
by Fabio
10:26
created

MessageSource_PHP::delete()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 28
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 14
nc 5
nop 2
dl 0
loc 28
rs 8.439
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * MessageSource_PHP class file.
5
 *
6
 * This program is free software; you can redistribute it and/or modify
7
 * it under the terms of the BSD License.
8
 *
9
 * To contact the author write to {@link mailto:[email protected] Qiang Xue}
10
 * The latest version of PRADO can be obtained from:
11
 * {@link http://prado.sourceforge.net/}
12
 *
13
 * @author Fabio Bas <ctrlaltca[at]gmail[dot]com>
14
 * @since 4.0
15
 * @package Prado\I18N\core
16
 */
17
18
namespace Prado\I18N\core;
19
20
/**
21
 * Get the MessageSource class file.
22
 */
23
use Prado\Exceptions\TException;
24
use Prado\Exceptions\TIOException;
25
26
/**
27
 * MessageSource_PHP class.
28
 *
29
 * Using PHP arrays as the message source for translation.
30
 *
31
 * See the MessageSource::factory() method to instantiate this class.
32
 *
33
 * @author Fabio Bas <ctrlaltca[at]gmail[dot]com>
34
 * @since 4.0
35
 * @package Prado\I18N\core
36
 */
37
class MessageSource_PHP extends MessageSource
0 ignored issues
show
Coding Style introduced by
This class is not in CamelCase format.

Classes in PHP are usually named in CamelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. The whole name starts with a capital letter as well.

Thus the name database provider becomes DatabaseProvider.

Loading history...
38
{
39
	/**
40
	 * Message data filename extension.
41
	 * @var string
42
	 */
43
	protected $dataExt = '.php';
44
45
	/**
46
	 * Separator between culture name and source.
47
	 * @var string
48
	 */
49
	protected $dataSeparator = '.';
50
51
	/**
52
	 * Constructor.
53
	 * @param string the directory where the messages are stored.
54
	 * @see MessageSource::factory();
55
	 */
56
	function __construct($source)
57
	{
58
		$this->source = (string)$source;
59
	}
60
61
	/**
62
	 * Load the messages from a PHP file.
63
	 * @param string PHP file.
64
	 * @return array of messages.
0 ignored issues
show
Documentation introduced by
Should the return type not be false|array? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
65
	 */
66
	protected function &loadData($filename)
67
	{
68
		//load it.
69
		if(false === ($php = include($filename))) {
70
			return false;
71
		}
72
73
		$translationUnit = $php['trans-unit'];
74
75
		$translations = array();
76
77
		foreach($translationUnit as $k => $unit)
78
		{
79
			$source = (string)$unit['source'];
80
			$translations[$source][] = (string)$unit['target'];
81
			$translations[$source][] = (string)$k;
82
			$translations[$source][] = array_key_exists('note', $unit) ? (string)$unit['note'] : '';
83
		}
84
85
		return $translations;
86
	}
87
88
	/**
89
	 * Get the last modified unix-time for this particular catalogue+variant.
90
	 * Just use the file modified time.
91
	 * @param string catalogue+variant
92
	 * @return int last modified in unix-time format.
93
	 */
94
	protected function getLastModified($source)
95
	{
96
		return is_file($source) ? filemtime($source) : 0;
97
	}
98
99
	/**
100
	 * Get the PHP file for a specific message catalogue and cultural
101
	 * variant.
102
	 * @param string message catalogue
103
	 * @return string full path to the PHP file.
104
	 */
105
	protected function getSource($variant)
106
	{
107
		return $this->source.'/'.$variant;
108
	}
109
110
	/**
111
	 * Determin if the PHP file source is valid.
112
	 * @param string PHP file
113
	 * @return boolean true if valid, false otherwise.
114
	 */
115
	protected function isValidSource($source)
116
	{
117
		return is_file($source);
118
	}
119
120
	/**
121
	 * Get all the variants of a particular catalogue.
122
	 * @param string catalogue name
123
	 * @return array list of all variants for this catalogue.
124
	 */
125
	protected function getCatalogueList($catalogue)
126
	{
127
		$variants = explode('_',$this->culture);
128
		$source = $catalogue.$this->dataExt;
129
		$catalogues = array($source);
130
		$variant = null;
131
132
		for($i = 0, $k = count($variants); $i < $k; ++$i)
133
		{
134
			if(isset($variants[$i]{0}))
135
			{
136
				$variant .= ($variant)?'_'.$variants[$i]:$variants[$i];
137
				$catalogues[] = $catalogue.$this->dataSeparator.$variant.$this->dataExt;
138
			}
139
		}
140
141
		$byDir = $this->getCatalogueByDir($catalogue);
142
		return array_merge($byDir,array_reverse($catalogues));
143
	}
144
145
	/**
146
	 * Traverse through the directory structure to find the catalogues.
147
	 * This should only be called by getCatalogueList()
148
	 * @param string a particular catalogue.
149
	 * @return array a list of catalogues.
150
	 * @see getCatalogueList()
151
	 */
152
	private function getCatalogueByDir($catalogue)
153
	{
154
		$variants = explode('_',$this->culture);
155
		$catalogues = array();
156
		$variant = null;
157
158
		for($i = 0, $k = count($variants); $i < $k; ++$i)
159
		{
160
			if(isset($variants[$i]{0}))
161
			{
162
				$variant .= ($variant)?'_'.$variants[$i]:$variants[$i];
163
				$catalogues[] = $variant.'/'.$catalogue.$this->dataExt;
164
			}
165
		}
166
167
		return array_reverse($catalogues);
168
	}
169
170
	/**
171
	 * Returns a list of catalogue and its culture ID.
172
	 * E.g. array('messages','en_AU')
173
	 * @return array list of catalogues
174
	 * @see getCatalogues()
175
	 */
176
	public function catalogues()
177
	{
178
		return $this->getCatalogues();
179
	}
180
181
	/**
182
	 * Returns a list of catalogue and its culture ID. This takes care
183
	 * of directory structures.
184
	 * E.g. array('messages','en_AU')
185
	 * @return array list of catalogues
186
	 */
187
	protected function getCatalogues($dir=null,$variant=null)
188
	{
189
		$dir = $dir?$dir:$this->source;
190
		$files = scandir($dir);
191
		$catalogue = array();
192
193
		foreach($files as $file)
194
		{
195
			if(is_dir($dir.'/'.$file) && preg_match('/^[a-z]{2}(_[A-Z]{2,3})?$/',$file)) {
196
				$catalogue = array_merge(
197
					$catalogue,
198
					$this->getCatalogues($dir.'/'.$file, $file)
199
				);
200
			}
201
202
			$pos = strpos($file,$this->dataExt);
203
			if($pos >0 && substr($file, -1*strlen($this->dataExt)) == $this->dataExt)
204
			{
205
				$name = substr($file,0,$pos);
206
				$dot = strrpos($name,$this->dataSeparator);
207
				$culture = $variant;
208
				$cat = $name;
209
210
				if(is_int($dot))
211
				{
212
					$culture = substr($name, $dot+1, strlen($name));
213
					$cat = substr($name, 0, $dot);
214
				}
215
216
				$details[0] = $cat;
0 ignored issues
show
Coding Style Comprehensibility introduced by
$details was never initialized. Although not strictly required by PHP, it is generally a good practice to add $details = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
217
				$details[1] = $culture;
218
				$catalogue[] = $details;
219
			}
220
		}
221
		sort($catalogue);
222
		return $catalogue;
223
	}
224
225
	/**
226
	 * Get the variant for a catalogue depending on the current culture.
227
	 * @param string catalogue
228
	 * @return string the variant.
229
	 * @see save()
230
	 * @see update()
231
	 * @see delete()
232
	 */
233
	private function getVariants($catalogue='messages')
234
	{
235
		if($catalogue === null) {
236
			$catalogue = 'messages';
237
		}
238
239
		foreach($this->getCatalogueList($catalogue) as $variant)
240
		{
241
			$file = $this->getSource($variant);
242
			if(is_file($file)) {
243
				return array($variant, $file);
244
			}
245
		}
246
		return false;
247
	}
248
249
	protected function internalSaveFile($php, $filename)
250
	{
251
		$php['info']['date'] = @date('Y-m-d\TH:i:s\Z');
252
253
		//save it and clear the cache for this variant
254
		if(false === file_put_contents($filename, "<?php\nreturn " . var_export($php, true) . ';'))
255
			return false;
256
257
		if(!empty($this->cache))
258
			$this->cache->clean($variant, $this->culture);
0 ignored issues
show
Bug introduced by
The variable $variant does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
259
260
		return true;
261
	}
262
263
	/**
264
	 * Save the list of untranslated blocks to the translation source.
265
	 * If the translation was not found, you should add those
266
	 * strings to the translation source via the <b>append()</b> method.
267
	 * @param string the catalogue to add to
268
	 * @return boolean true if saved successfuly, false otherwise.
269
	 */
270
	public function save($catalogue='messages')
271
	{
272
		$messages = $this->untranslated;
273
		if(count($messages) <= 0) {
274
			return false;
275
		}
276
277
		$variants = $this->getVariants($catalogue);
278
279
		if($variants) {
280
			list($variant, $filename) = $variants;
281
		} else {
282
			list($variant, $filename) = $this->createMessageTemplate($catalogue);
283
		}
284
285
		if(is_writable($filename) == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
286
			throw new TIOException("Unable to save to file {$filename}, file must be writable.");
287
		}
288
289
		//import the existing php file
290
		$php = include($filename);
291
292
		//for each message add it to the XML file using DOM
293
		foreach($messages as $message)
294
		{
295
			$php['trans-unit'][]= array(
296
				'source' => $message,
297
				'target' => '',
298
			);
299
		}
300
301
		return $this->internalSaveFile($php, $filename);
302
	}
303
304
	/**
305
	 * Update the translation.
306
	 * @param string the source string.
307
	 * @param string the new translation string.
308
	 * @param string comments
309
	 * @param string the catalogue to save to.
310
	 * @return boolean true if translation was updated, false otherwise.
311
	 */
312
	public function update($text, $target, $comments, $catalogue='messages')
313
	{
314
		$variants = $this->getVariants($catalogue);
315
316
		if($variants) {
317
			list($variant, $filename) = $variants;
318
		} else {
319
			return false;
320
		}
321
322
		if(is_writable($filename) == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
323
			throw new TIOException("Unable to update file {$filename}, file must be writable.");
324
		}
325
326
		//import the existing php file
327
		$php = include($filename);
328
329
		//for each of the existin units
330
		foreach($php['trans-unit'] as $k => $unit)
331
		{
332
			if($unit['source'] == $text)
333
			{
334
				$php['trans-unit'][$k]['target'] = $target;
335
				if(!empty($comments))
336
					$php['trans-unit'][$k]['note'] = $comments;
337
338
				break;
339
			}
340
		}
341
342
		return $this->internalSaveFile($php, $filename);
343
	}
344
345
	/**
346
	 * Delete a particular message from the specified catalogue.
347
	 * @param string the source message to delete.
348
	 * @param string the catalogue to delete from.
349
	 * @return boolean true if deleted, false otherwise.
350
	 */
351
	public function delete($message, $catalogue='messages')
352
	{
353
		$variants = $this->getVariants($catalogue);
354
		if($variants) {
355
			list($variant, $filename) = $variants;
356
		} else {
357
			return false;
358
		}
359
360
		if(is_writable($filename) == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
361
			throw new TIOException("Unable to modify file {$filename}, file must be writable.");
362
		}
363
364
		//import the existing php file
365
		$php = include($filename);
366
367
		//for each of the existin units
368
		foreach($php['trans-unit'] as $k => $unit)
369
		{
370
			if($unit['source'] == $message)
371
			{
372
				unset($php['trans-unit'][$k]);
373
				return $this->internalSaveFile($php, $filename);
374
			}
375
		}
376
377
		return false;
378
	}
379
380
	protected function createMessageTemplate($catalogue)
381
	{
382
		if($catalogue === null) {
383
			$catalogue = 'messages';
384
		}
385
		
386
		$variants = $this->getCatalogueList($catalogue);
387
		$variant = array_shift($variants);
388
		$file = $this->getSource($variant);
389
		$dir = dirname($file);
390
391
		if(!is_dir($dir)) {
392
			@mkdir($dir);
393
			@chmod($dir,PRADO_CHMOD);
394
		}
395
396
		if(!is_dir($dir)) {
397
			throw new TException("Unable to create directory $dir");
398
		}
399
		
400
		file_put_contents($file, $this->getTemplate($catalogue));
401
		chmod($file, PRADO_CHMOD);
402
403
		return array($variant, $file);
404
	}
405
406
	protected function getTemplate($catalogue)
407
	{
408
		$date = @date('Y-m-d\TH:i:s\Z');
409
		$php = <<<EOD
410
<?php
411
return array(
412
	'info' => array(
413
		'source-language' => 'EN',
414
		'target-language' => '{$this->culture}',
415
		'original' => '$catalogue',
416
		'date' => '$date'
417
	),
418
	'trans-unit' => array(
419
	)
420
);
421
EOD;
422
		return $php;
423
	}
424
}
425