Completed
Push — master ( fccf07...03890a )
by Filip
04:57
created

TranslationExtension   F

Complexity

Total Complexity 69

Size/Duplication

Total Lines 438
Duplicated Lines 10.5 %

Coupling/Cohesion

Components 1
Dependencies 22

Importance

Changes 0
Metric Value
wmc 69
lcom 1
cbo 22
dl 46
loc 438
rs 1.7098
c 0
b 0
f 0

17 Methods

Rating   Name   Duplication   Size   Complexity  
A afterCompile() 0 7 2
A __construct() 0 4 1
B loadConfiguration() 0 63 5
B loadLocaleResolver() 12 39 6
A loadConsole() 0 10 1
A loadDumpers() 10 10 2
A loadLoaders() 0 13 3
A loadExtractors() 10 10 2
F beforeCompile() 14 92 19
A beforeCompileLogging() 0 20 4
B loadResourcesFromDirs() 0 27 6
C validateResource() 0 32 8
A getConfig() 0 4 1
A isRegisteredConsoleExtension() 0 10 3
A register() 0 6 1
A filterArgs() 0 4 2
A isOfType() 0 4 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like TranslationExtension often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use TranslationExtension, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/**
4
 * This file is part of the Kdyby (http://www.kdyby.org)
5
 *
6
 * Copyright (c) 2008 Filip Procházka ([email protected])
7
 *
8
 * For the full copyright and license information, please view the file license.txt that was distributed with this source code.
9
 */
10
11
namespace Kdyby\Translation\DI;
12
13
use Kdyby\Console\DI\ConsoleExtension;
14
use Kdyby\Monolog\Logger as KdybyLogger;
15
use Kdyby\Translation\Caching\PhpFileStorage;
16
use Kdyby\Translation\CatalogueCompiler;
17
use Kdyby\Translation\CatalogueFactory;
18
use Kdyby\Translation\Console\ExtractCommand;
19
use Kdyby\Translation\Diagnostics\Panel;
20
use Kdyby\Translation\FallbackResolver;
21
use Kdyby\Translation\IUserLocaleResolver;
22
use Kdyby\Translation\Latte\TranslateMacros;
23
use Kdyby\Translation\LocaleResolver\AcceptHeaderResolver;
24
use Kdyby\Translation\LocaleResolver\ChainResolver;
25
use Kdyby\Translation\LocaleResolver\LocaleParamResolver;
26
use Kdyby\Translation\LocaleResolver\SessionResolver;
27
use Kdyby\Translation\TemplateHelpers;
28
use Kdyby\Translation\TranslationLoader;
29
use Kdyby\Translation\Translator as KdybyTranslator;
30
use Latte\Engine as LatteEngine;
31
use Nette\Application\Application;
32
use Nette\Bridges\ApplicationLatte\ILatteFactory;
33
use Nette\Configurator;
34
use Nette\DI\Compiler;
35
use Nette\DI\Helpers;
36
use Nette\DI\ServiceDefinition;
37
use Nette\DI\Statement;
38
use Nette\PhpGenerator\ClassType as ClassTypeGenerator;
39
use Nette\PhpGenerator\PhpLiteral;
40
use Nette\Reflection\ClassType as ReflectionClassType;
41
use Nette\Utils\Callback;
42
use Nette\Utils\Finder;
43
use Nette\Utils\Validators;
44
use Symfony\Component\Translation\Extractor\ChainExtractor;
45
use Symfony\Component\Translation\Loader\LoaderInterface;
46
use Symfony\Component\Translation\MessageSelector;
47
use Symfony\Component\Translation\Writer\TranslationWriter;
48
use Tracy\Debugger;
49
use Tracy\IBarPanel;
50
51
class TranslationExtension extends \Nette\DI\CompilerExtension
52
{
53
54
	use \Kdyby\StrictObjects\Scream;
55
56
	/** @deprecated */
57
	const LOADER_TAG = self::TAG_LOADER;
58
	/** @deprecated */
59
	const DUMPER_TAG = self::TAG_DUMPER;
60
	/** @deprecated */
61
	const EXTRACTOR_TAG = self::TAG_EXTRACTOR;
62
63
	const TAG_LOADER = 'translation.loader';
64
	const TAG_DUMPER = 'translation.dumper';
65
	const TAG_EXTRACTOR = 'translation.extractor';
66
67
	const RESOLVER_REQUEST = 'request';
68
	const RESOLVER_HEADER = 'header';
69
	const RESOLVER_SESSION = 'session';
70
71
	/**
72
	 * @var mixed[]
73
	 */
74
	public $defaults = [
75
		'whitelist' => NULL, // array('cs', 'en'),
0 ignored issues
show
Unused Code Comprehensibility introduced by
78% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
76
		'default' => 'en',
77
		'logging' => NULL, //  TRUE for psr/log, or string for kdyby/monolog channel
78
		// 'fallback' => array('en_US', 'en'), // using custom merge strategy becase Nette's config merger appends lists of values
0 ignored issues
show
Unused Code Comprehensibility introduced by
58% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
79
		'dirs' => ['%appDir%/lang', '%appDir%/locale'],
80
		'cache' => PhpFileStorage::class,
81
		'debugger' => '%debugMode%',
82
		'resolvers' => [
83
			self::RESOLVER_SESSION => FALSE,
84
			self::RESOLVER_REQUEST => TRUE,
85
			self::RESOLVER_HEADER => TRUE,
86
		],
87
		'loaders' => [],
88
	];
89
90
	/**
91
	 * @var array
92
	 */
93
	private $loaders;
94
95
	public function __construct()
96
	{
97
		$this->defaults['cache'] = new Statement($this->defaults['cache'], ['%tempDir%/cache']);
98
	}
99
100
	public function loadConfiguration()
101
	{
102
		$this->loaders = [];
103
104
		$builder = $this->getContainerBuilder();
105
		$config = $this->getConfig();
106
107
		$translator = $builder->addDefinition($this->prefix('default'))
108
			->setClass(KdybyTranslator::class, [$this->prefix('@userLocaleResolver')])
109
			->addSetup('?->setTranslator(?)', [$this->prefix('@userLocaleResolver.param'), '@self'])
110
			->addSetup('setDefaultLocale', [$config['default']])
111
			->addSetup('setLocaleWhitelist', [$config['whitelist']]);
112
113
		Validators::assertField($config, 'fallback', 'list');
114
		$translator->addSetup('setFallbackLocales', [$config['fallback']]);
115
116
		$catalogueCompiler = $builder->addDefinition($this->prefix('catalogueCompiler'))
117
			->setClass(CatalogueCompiler::class, self::filterArgs($config['cache']));
118
119
		if ($config['debugger'] && interface_exists(IBarPanel::class)) {
120
			$builder->addDefinition($this->prefix('panel'))
121
				->setClass(Panel::class, [dirname($builder->expand('%appDir%'))])
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ContainerBuilder::expand() has been deprecated.

This method has been deprecated.

Loading history...
122
				->addSetup('setLocaleWhitelist', [$config['whitelist']]);
123
124
			$translator->addSetup('?->register(?)', [$this->prefix('@panel'), '@self']);
125
			$catalogueCompiler->addSetup('enableDebugMode');
126
		}
127
128
		$this->loadLocaleResolver($config);
129
130
		$builder->addDefinition($this->prefix('helpers'))
131
			->setClass(TemplateHelpers::class)
132
			->setFactory($this->prefix('@default') . '::createTemplateHelpers');
133
134
		$builder->addDefinition($this->prefix('fallbackResolver'))
135
			->setClass(FallbackResolver::class);
136
137
		$builder->addDefinition($this->prefix('catalogueFactory'))
138
			->setClass(CatalogueFactory::class);
139
140
		$builder->addDefinition($this->prefix('selector'))
141
			->setClass(MessageSelector::class);
142
143
		$builder->addDefinition($this->prefix('extractor'))
144
			->setClass(ChainExtractor::class);
145
146
		$this->loadExtractors();
147
148
		$builder->addDefinition($this->prefix('writer'))
149
			->setClass(TranslationWriter::class);
150
151
		$this->loadDumpers();
152
153
		$builder->addDefinition($this->prefix('loader'))
154
			->setClass(TranslationLoader::class);
155
156
		$loaders = $this->loadFromFile(__DIR__ . '/config/loaders.neon');
157
		$this->loadLoaders($loaders, $config['loaders'] ?: array_keys($loaders));
0 ignored issues
show
Bug introduced by
It seems like $loaders defined by $this->loadFromFile(__DI...'/config/loaders.neon') on line 156 can also be of type string; however, Kdyby\Translation\DI\Tra...xtension::loadLoaders() does only seem to accept array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
158
159
		if ($this->isRegisteredConsoleExtension()) {
160
			$this->loadConsole($config);
161
		}
162
	}
163
164
	protected function loadLocaleResolver(array $config)
165
	{
166
		$builder = $this->getContainerBuilder();
167
168
		$builder->addDefinition($this->prefix('userLocaleResolver.param'))
169
			->setClass(LocaleParamResolver::class)
170
			->setAutowired(FALSE);
171
172
		$builder->addDefinition($this->prefix('userLocaleResolver.acceptHeader'))
173
			->setClass(AcceptHeaderResolver::class);
174
175
		$builder->addDefinition($this->prefix('userLocaleResolver.session'))
176
			->setClass(SessionResolver::class);
177
178
		$chain = $builder->addDefinition($this->prefix('userLocaleResolver'))
179
			->setClass(IUserLocaleResolver::class)
180
			->setFactory(ChainResolver::class);
181
182
		$resolvers = [];
183 View Code Duplication
		if ($config['resolvers'][self::RESOLVER_HEADER]) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
184
			$resolvers[] = $this->prefix('@userLocaleResolver.acceptHeader');
185
			$chain->addSetup('addResolver', [$this->prefix('@userLocaleResolver.acceptHeader')]);
186
		}
187
188 View Code Duplication
		if ($config['resolvers'][self::RESOLVER_REQUEST]) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
189
			$resolvers[] = $this->prefix('@userLocaleResolver.param');
190
			$chain->addSetup('addResolver', [$this->prefix('@userLocaleResolver.param')]);
191
		}
192
193 View Code Duplication
		if ($config['resolvers'][self::RESOLVER_SESSION]) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
194
			$resolvers[] = $this->prefix('@userLocaleResolver.session');
195
			$chain->addSetup('addResolver', [$this->prefix('@userLocaleResolver.session')]);
196
		}
197
198
		if ($config['debugger'] && interface_exists(IBarPanel::class)) {
199
			$builder->getDefinition($this->prefix('panel'))
200
				->addSetup('setLocaleResolvers', [array_reverse($resolvers)]);
201
		}
202
	}
203
204
	protected function loadConsole(array $config)
205
	{
206
		$builder = $this->getContainerBuilder();
207
208
		Validators::assertField($config, 'dirs', 'list');
209
		$builder->addDefinition($this->prefix('console.extract'))
210
			->setClass(ExtractCommand::class)
211
			->addSetup('$defaultOutputDir', [reset($config['dirs'])])
212
			->addTag(ConsoleExtension::TAG_COMMAND, 'latte');
0 ignored issues
show
Documentation introduced by
'latte' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
213
	}
214
215 View Code Duplication
	protected function loadDumpers()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
216
	{
217
		$builder = $this->getContainerBuilder();
218
219
		foreach ($this->loadFromFile(__DIR__ . '/config/dumpers.neon') as $format => $class) {
0 ignored issues
show
Bug introduced by
The expression $this->loadFromFile(__DI...'/config/dumpers.neon') of type array|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
220
			$builder->addDefinition($this->prefix('dumper.' . $format))
221
				->setClass($class)
222
				->addTag(self::TAG_DUMPER, $format);
0 ignored issues
show
Documentation introduced by
$format is of type integer|string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
223
		}
224
	}
225
226
	protected function loadLoaders(array $loaders, array $allowed)
227
	{
228
		$builder = $this->getContainerBuilder();
229
230
		foreach ($loaders as $format => $class) {
231
			if (array_search($format, $allowed) === FALSE) {
232
				continue;
233
			}
234
			$builder->addDefinition($this->prefix('loader.' . $format))
235
				->setClass($class)
236
				->addTag(self::TAG_LOADER, $format);
0 ignored issues
show
Documentation introduced by
$format is of type integer|string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
237
		}
238
	}
239
240 View Code Duplication
	protected function loadExtractors()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
241
	{
242
		$builder = $this->getContainerBuilder();
243
244
		foreach ($this->loadFromFile(__DIR__ . '/config/extractors.neon') as $format => $class) {
0 ignored issues
show
Bug introduced by
The expression $this->loadFromFile(__DI...onfig/extractors.neon') of type array|string is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
245
			$builder->addDefinition($this->prefix('extractor.' . $format))
246
				->setClass($class)
247
				->addTag(self::TAG_EXTRACTOR, $format);
0 ignored issues
show
Documentation introduced by
$format is of type integer|string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
248
		}
249
	}
250
251
	public function beforeCompile()
252
	{
253
		$builder = $this->getContainerBuilder();
254
		$config = $this->getConfig();
255
256
		$this->beforeCompileLogging($config);
257
258
		$registerToLatte = function (ServiceDefinition $def) {
259
			$def->addSetup('?->onCompile[] = function($engine) { ?::install($engine->getCompiler()); }', ['@self', new PhpLiteral(TranslateMacros::class)]);
260
261
			$def->addSetup('addProvider', ['translator', $this->prefix('@default')])
262
				->addSetup('addFilter', ['translate', [$this->prefix('@helpers'), 'translateFilterAware']]);
263
		};
264
265
		$latteFactoryService = $builder->getByType(ILatteFactory::class);
266
		if (!$latteFactoryService || !self::isOfType($builder->getDefinition($latteFactoryService)->getClass(), LatteEngine::class)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $latteFactoryService of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
267
			$latteFactoryService = 'nette.latteFactory';
268
		}
269
270
		if ($builder->hasDefinition($latteFactoryService) && self::isOfType($builder->getDefinition($latteFactoryService)->getClass(), LatteEngine::class)) {
271
			$registerToLatte($builder->getDefinition($latteFactoryService));
272
		}
273
274
		if ($builder->hasDefinition('nette.latte')) {
275
			$registerToLatte($builder->getDefinition('nette.latte'));
276
		}
277
278
		$applicationService = $builder->getByType(Application::class) ?: 'application';
279
		if ($builder->hasDefinition($applicationService)) {
280
			$builder->getDefinition($applicationService)
281
				->addSetup('$service->onRequest[] = ?', [[$this->prefix('@userLocaleResolver.param'), 'onRequest']]);
282
283
			if ($config['debugger'] && interface_exists(IBarPanel::class)) {
284
				$builder->getDefinition($applicationService)
285
					->addSetup('$self = $this; $service->onStartup[] = function () use ($self) { $self->getService(?); }', [$this->prefix('default')])
286
					->addSetup('$service->onRequest[] = ?', [[$this->prefix('@panel'), 'onRequest']]);
287
			}
288
		}
289
290
		if (class_exists(Debugger::class)) {
291
			Panel::registerBluescreen();
292
		}
293
294
		$extractor = $builder->getDefinition($this->prefix('extractor'));
295 View Code Duplication
		foreach ($builder->findByTag(self::TAG_EXTRACTOR) as $extractorId => $meta) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
296
			Validators::assert($meta, 'string:2..');
297
298
			$extractor->addSetup('addExtractor', [$meta, '@' . $extractorId]);
299
300
			$builder->getDefinition($extractorId)->setAutowired(FALSE);
301
		}
302
303
		$writer = $builder->getDefinition($this->prefix('writer'));
304 View Code Duplication
		foreach ($builder->findByTag(self::TAG_DUMPER) as $dumperId => $meta) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
305
			Validators::assert($meta, 'string:2..');
306
307
			$writer->addSetup('addDumper', [$meta, '@' . $dumperId]);
308
309
			$builder->getDefinition($dumperId)->setAutowired(FALSE);
310
		}
311
312
		$this->loaders = [];
313
		foreach ($builder->findByTag(self::TAG_LOADER) as $loaderId => $meta) {
314
			Validators::assert($meta, 'string:2..');
315
			$builder->getDefinition($loaderId)->setAutowired(FALSE);
316
			$this->loaders[$meta] = $loaderId;
317
		}
318
319
		$builder->getDefinition($this->prefix('loader'))
320
			->addSetup('injectServiceIds', [$this->loaders]);
321
322
		foreach ($this->compiler->getExtensions() as $extension) {
323
			if (!$extension instanceof ITranslationProvider) {
324
				continue;
325
			}
326
327
			$config['dirs'] = array_merge($config['dirs'], array_values($extension->getTranslationResources()));
328
		}
329
330
		$config['dirs'] = array_map(function ($dir) {
331
			return str_replace((DIRECTORY_SEPARATOR === '/') ? '\\' : '/', DIRECTORY_SEPARATOR, $dir);
332
		}, $config['dirs']);
333
334
		$dirs = array_values(array_filter($config['dirs'], Callback::closure('is_dir')));
335
		if (count($dirs) > 0) {
336
			foreach ($dirs as $dir) {
337
				$builder->addDependency($dir);
338
			}
339
340
			$this->loadResourcesFromDirs($dirs);
341
		}
342
	}
343
344
	protected function beforeCompileLogging(array $config)
345
	{
346
		$builder = $this->getContainerBuilder();
347
		$translator = $builder->getDefinition($this->prefix('default'));
348
349
		if ($config['logging'] === TRUE) {
350
			$translator->addSetup('injectPsrLogger');
351
352
		} elseif (is_string($config['logging'])) { // channel for kdyby/monolog
353
			$translator->addSetup('injectPsrLogger', [
354
				new Statement(sprintf('@%s::channel', KdybyLogger::class), [$config['logging']]),
355
			]);
356
357
		} elseif ($config['logging'] !== NULL) {
358
			throw new \Kdyby\Translation\InvalidArgumentException(sprintf(
359
				'Invalid config option for logger. Valid are TRUE for general psr/log or string for kdyby/monolog channel, but %s was given',
360
				$config['logging']
361
			));
362
		}
363
	}
364
365
	protected function loadResourcesFromDirs($dirs)
366
	{
367
		$builder = $this->getContainerBuilder();
368
		$config = $this->getConfig();
369
370
		$whitelistRegexp = KdybyTranslator::buildWhitelistRegexp($config['whitelist']);
371
		$translator = $builder->getDefinition($this->prefix('default'));
372
373
		$mask = array_map(function ($value) {
374
			return '*.*.' . $value;
375
		}, array_keys($this->loaders));
376
377
		foreach (Finder::findFiles($mask)->from($dirs) as $file) {
378
			/** @var \SplFileInfo $file */
379
			if (!preg_match('~^(?P<domain>.*?)\.(?P<locale>[^\.]+)\.(?P<format>[^\.]+)$~', $file->getFilename(), $m)) {
380
				continue;
381
			}
382
383
			if ($whitelistRegexp && !preg_match($whitelistRegexp, $m['locale']) && $builder->parameters['productionMode']) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $whitelistRegexp of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
384
				continue; // ignore in production mode, there is no need to pass the ignored resources
385
			}
386
387
			$this->validateResource($m['format'], $file->getPathname(), $m['locale'], $m['domain']);
388
			$translator->addSetup('addResource', [$m['format'], $file->getPathname(), $m['locale'], $m['domain']]);
389
			$builder->addDependency($file->getPathname());
390
		}
391
	}
392
393
	/**
394
	 * @param string $format
395
	 * @param string $file
396
	 * @param string $locale
397
	 * @param string $domain
398
	 */
399
	protected function validateResource($format, $file, $locale, $domain)
400
	{
401
		$builder = $this->getContainerBuilder();
402
403
		if (!isset($this->loaders[$format])) {
404
			return;
405
		}
406
407
		try {
408
			$def = $builder->getDefinition($this->loaders[$format]);
409
			$refl = ReflectionClassType::from($def->getEntity() ?: $def->getClass());
410
			$method = $refl->getConstructor();
411
			if ($method !== NULL && $method->getNumberOfRequiredParameters() > 1) {
412
				return;
413
			}
414
415
			$loader = $refl->newInstance();
416
			if (!$loader instanceof LoaderInterface) {
417
				return;
418
			}
419
420
		} catch (\ReflectionException $e) {
421
			return;
422
		}
423
424
		try {
425
			$loader->load($file, $locale, $domain);
426
427
		} catch (\Exception $e) {
428
			throw new \Kdyby\Translation\InvalidResourceException(sprintf('Resource %s is not valid and cannot be loaded.', $file), 0, $e);
429
		}
430
	}
431
432
	public function afterCompile(ClassTypeGenerator $class)
433
	{
434
		$initialize = $class->getMethod('initialize');
435
		if (class_exists(Debugger::class)) {
436
			$initialize->addBody('?::registerBluescreen();', [new PhpLiteral(Panel::class)]);
437
		}
438
	}
439
440
	/**
441
	 * {@inheritdoc}
442
	 */
443
	public function getConfig(array $defaults = NULL, $expand = TRUE)
0 ignored issues
show
Unused Code introduced by
The parameter $defaults is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Unused Code introduced by
The parameter $expand is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
444
	{
445
		return parent::getConfig($this->defaults) + ['fallback' => ['en_US']];
446
	}
447
448
	private function isRegisteredConsoleExtension()
449
	{
450
		foreach ($this->compiler->getExtensions() as $extension) {
451
			if ($extension instanceof ConsoleExtension) {
452
				return TRUE;
453
			}
454
		}
455
456
		return FALSE;
457
	}
458
459
	/**
460
	 * @param \Nette\Configurator $configurator
461
	 */
462
	public static function register(Configurator $configurator)
463
	{
464
		$configurator->onCompile[] = function ($config, Compiler $compiler) {
465
			$compiler->addExtension('translation', new TranslationExtension());
466
		};
467
	}
468
469
	/**
470
	 * @param string|\stdClass $statement
471
	 * @return \Nette\DI\Statement[]
472
	 */
473
	protected static function filterArgs($statement)
474
	{
475
		return Helpers::filterArguments([is_string($statement) ? new Statement($statement) : $statement]);
476
	}
477
478
	/**
479
	 * @param string|NULL $class
480
	 * @param string $type
481
	 * @return bool
482
	 */
483
	private static function isOfType($class, $type)
484
	{
485
		return $class !== NULL && ($class === $type || is_subclass_of($class, $type));
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if $type can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
486
	}
487
488
}
489