Completed
Pull Request — master (#154)
by Michal
02:01
created

src/DI/TranslationExtension.php (33 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

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\Formatter\MessageFormatter;
46
use Symfony\Component\Translation\Loader\LoaderInterface;
47
use Symfony\Component\Translation\MessageSelector;
48
use Symfony\Component\Translation\Writer\TranslationWriter;
49
use Tracy\Debugger;
50
use Tracy\IBarPanel;
51
52
class TranslationExtension extends \Nette\DI\CompilerExtension
53
{
54
55
	use \Kdyby\StrictObjects\Scream;
56
57
	/** @deprecated */
58
	const LOADER_TAG = self::TAG_LOADER;
59
	/** @deprecated */
60
	const DUMPER_TAG = self::TAG_DUMPER;
61
	/** @deprecated */
62
	const EXTRACTOR_TAG = self::TAG_EXTRACTOR;
63
64
	const TAG_LOADER = 'translation.loader';
65
	const TAG_DUMPER = 'translation.dumper';
66
	const TAG_EXTRACTOR = 'translation.extractor';
67
68
	const RESOLVER_REQUEST = 'request';
69
	const RESOLVER_HEADER = 'header';
70
	const RESOLVER_SESSION = 'session';
71
72
	/**
73
	 * @var mixed[]
74
	 */
75
	public $defaults = [
76
		'whitelist' => NULL, // array('cs', 'en'),
77
		'default' => 'en',
78
		'logging' => NULL, //  TRUE for psr/log, or string for kdyby/monolog channel
79
		// 'fallback' => array('en_US', 'en'), // using custom merge strategy becase Nette's config merger appends lists of values
80
		'dirs' => ['%appDir%/lang', '%appDir%/locale'],
81
		'cache' => PhpFileStorage::class,
82
		'debugger' => '%debugMode%',
83
		'resolvers' => [
84
			self::RESOLVER_SESSION => FALSE,
85
			self::RESOLVER_REQUEST => TRUE,
86
			self::RESOLVER_HEADER => TRUE,
87
		],
88
		'loaders' => [],
89
	];
90
91
	/**
92
	 * @var array
93
	 */
94
	private $loaders;
95
96
	public function __construct()
97
	{
98
		$this->defaults['cache'] = new Statement($this->defaults['cache'], ['%tempDir%/cache']);
99
	}
100
101
	public function loadConfiguration()
102
	{
103
		$this->loaders = [];
104
105
		$builder = $this->getContainerBuilder();
106
		$config = $this->getConfig();
107
108
		$translator = $builder->addDefinition($this->prefix('default'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated with message: Use setType() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
109
			->setClass(KdybyTranslator::class, [$this->prefix('@userLocaleResolver')])
110
			->addSetup('?->setTranslator(?)', [$this->prefix('@userLocaleResolver.param'), '@self'])
111
			->addSetup('setDefaultLocale', [$config['default']])
112
			->addSetup('setLocaleWhitelist', [$config['whitelist']]);
113
114
		Validators::assertField($config, 'fallback', 'list');
115
		$translator->addSetup('setFallbackLocales', [$config['fallback']]);
116
117
		$catalogueCompiler = $builder->addDefinition($this->prefix('catalogueCompiler'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated with message: Use setType() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
118
			->setClass(CatalogueCompiler::class, self::filterArgs($config['cache']));
119
120
		if ($config['debugger'] && interface_exists(IBarPanel::class)) {
121
			$builder->addDefinition($this->prefix('panel'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated with message: Use setType() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
122
				->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...
123
				->addSetup('setLocaleWhitelist', [$config['whitelist']]);
124
125
			$translator->addSetup('?->register(?)', [$this->prefix('@panel'), '@self']);
126
			$catalogueCompiler->addSetup('enableDebugMode');
127
		}
128
129
		$this->loadLocaleResolver($config);
130
131
		$builder->addDefinition($this->prefix('helpers'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated with message: Use setType() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
132
			->setClass(TemplateHelpers::class)
133
			->setFactory($this->prefix('@default') . '::createTemplateHelpers');
134
135
		$builder->addDefinition($this->prefix('fallbackResolver'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated with message: Use setType() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
136
			->setClass(FallbackResolver::class);
137
138
		$builder->addDefinition($this->prefix('catalogueFactory'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated with message: Use setType() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
139
			->setClass(CatalogueFactory::class);
140
141
		$builder->addDefinition($this->prefix('selector'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated with message: Use setType() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
142
			->setClass(MessageSelector::class);
143
144
		$builder->addDefinition($this->prefix('formatter'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated with message: Use setType() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
145
			->setClass(MessageFormatter::class);
146
147
		$builder->addDefinition($this->prefix('extractor'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated with message: Use setType() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
148
			->setClass(ChainExtractor::class);
149
150
		$this->loadExtractors();
151
152
		$builder->addDefinition($this->prefix('writer'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated with message: Use setType() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
153
			->setClass(TranslationWriter::class);
154
155
		$this->loadDumpers();
156
157
		$builder->addDefinition($this->prefix('loader'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated with message: Use setType() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
158
			->setClass(TranslationLoader::class);
159
160
		$loaders = $this->loadFromFile(__DIR__ . '/config/loaders.neon');
161
		$this->loadLoaders($loaders, $config['loaders'] ?: array_keys($loaders));
0 ignored issues
show
It seems like $loaders defined by $this->loadFromFile(__DI...'/config/loaders.neon') on line 160 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...
162
163
		if ($this->isRegisteredConsoleExtension()) {
164
			$this->loadConsole($config);
165
		}
166
	}
167
168
	protected function loadLocaleResolver(array $config)
169
	{
170
		$builder = $this->getContainerBuilder();
171
172
		$builder->addDefinition($this->prefix('userLocaleResolver.param'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated with message: Use setType() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
173
			->setClass(LocaleParamResolver::class)
174
			->setAutowired(FALSE);
175
176
		$builder->addDefinition($this->prefix('userLocaleResolver.acceptHeader'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated with message: Use setType() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
177
			->setClass(AcceptHeaderResolver::class);
178
179
		$builder->addDefinition($this->prefix('userLocaleResolver.session'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated with message: Use setType() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
180
			->setClass(SessionResolver::class);
181
182
		$chain = $builder->addDefinition($this->prefix('userLocaleResolver'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated with message: Use setType() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
183
			->setClass(IUserLocaleResolver::class)
184
			->setFactory(ChainResolver::class);
185
186
		$resolvers = [];
187 View Code Duplication
		if ($config['resolvers'][self::RESOLVER_HEADER]) {
188
			$resolvers[] = $this->prefix('@userLocaleResolver.acceptHeader');
189
			$chain->addSetup('addResolver', [$this->prefix('@userLocaleResolver.acceptHeader')]);
190
		}
191
192 View Code Duplication
		if ($config['resolvers'][self::RESOLVER_REQUEST]) {
193
			$resolvers[] = $this->prefix('@userLocaleResolver.param');
194
			$chain->addSetup('addResolver', [$this->prefix('@userLocaleResolver.param')]);
195
		}
196
197 View Code Duplication
		if ($config['resolvers'][self::RESOLVER_SESSION]) {
198
			$resolvers[] = $this->prefix('@userLocaleResolver.session');
199
			$chain->addSetup('addResolver', [$this->prefix('@userLocaleResolver.session')]);
200
		}
201
202
		if ($config['debugger'] && interface_exists(IBarPanel::class)) {
203
			$builder->getDefinition($this->prefix('panel'))
204
				->addSetup('setLocaleResolvers', [array_reverse($resolvers)]);
205
		}
206
	}
207
208
	protected function loadConsole(array $config)
209
	{
210
		$builder = $this->getContainerBuilder();
211
212
		Validators::assertField($config, 'dirs', 'list');
213
		$builder->addDefinition($this->prefix('console.extract'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated with message: Use setType() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
214
			->setClass(ExtractCommand::class)
215
			->addSetup('$defaultOutputDir', [reset($config['dirs'])])
216
			->addTag(ConsoleExtension::TAG_COMMAND, 'latte');
0 ignored issues
show
'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...
217
	}
218
219 View Code Duplication
	protected function loadDumpers()
220
	{
221
		$builder = $this->getContainerBuilder();
222
223
		foreach ($this->loadFromFile(__DIR__ . '/config/dumpers.neon') as $format => $class) {
0 ignored issues
show
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...
224
			$builder->addDefinition($this->prefix('dumper.' . $format))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated with message: Use setType() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
225
				->setClass($class)
226
				->addTag(self::TAG_DUMPER, $format);
0 ignored issues
show
$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...
227
		}
228
	}
229
230
	protected function loadLoaders(array $loaders, array $allowed)
231
	{
232
		$builder = $this->getContainerBuilder();
233
234
		foreach ($loaders as $format => $class) {
235
			if (array_search($format, $allowed) === FALSE) {
236
				continue;
237
			}
238
			$builder->addDefinition($this->prefix('loader.' . $format))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated with message: Use setType() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
239
				->setClass($class)
240
				->addTag(self::TAG_LOADER, $format);
0 ignored issues
show
$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...
241
		}
242
	}
243
244 View Code Duplication
	protected function loadExtractors()
245
	{
246
		$builder = $this->getContainerBuilder();
247
248
		foreach ($this->loadFromFile(__DIR__ . '/config/extractors.neon') as $format => $class) {
0 ignored issues
show
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...
249
			$builder->addDefinition($this->prefix('extractor.' . $format))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::setClass() has been deprecated with message: Use setType() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
250
				->setClass($class)
251
				->addTag(self::TAG_EXTRACTOR, $format);
0 ignored issues
show
$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...
252
		}
253
	}
254
255
	public function beforeCompile()
256
	{
257
		$builder = $this->getContainerBuilder();
258
		$config = $this->getConfig();
259
260
		$this->beforeCompileLogging($config);
261
262
		$registerToLatte = function (ServiceDefinition $def) {
263
			$def->addSetup('?->onCompile[] = function($engine) { ?::install($engine->getCompiler()); }', ['@self', new PhpLiteral(TranslateMacros::class)]);
264
265
			$def->addSetup('addProvider', ['translator', $this->prefix('@default')])
266
				->addSetup('addFilter', ['translate', [$this->prefix('@helpers'), 'translateFilterAware']]);
267
		};
268
269
		$latteFactoryService = $builder->getByType(ILatteFactory::class);
270
		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...
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::getClass() has been deprecated with message: Use getType() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
271
			$latteFactoryService = 'nette.latteFactory';
272
		}
273
274
		if ($builder->hasDefinition($latteFactoryService) && self::isOfType($builder->getDefinition($latteFactoryService)->getClass(), LatteEngine::class)) {
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::getClass() has been deprecated with message: Use getType() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
275
			$registerToLatte($builder->getDefinition($latteFactoryService));
276
		}
277
278
		if ($builder->hasDefinition('nette.latte')) {
279
			$registerToLatte($builder->getDefinition('nette.latte'));
280
		}
281
282
		$applicationService = $builder->getByType(Application::class) ?: 'application';
283
		if ($builder->hasDefinition($applicationService)) {
284
			$builder->getDefinition($applicationService)
285
				->addSetup('$service->onRequest[] = ?', [[$this->prefix('@userLocaleResolver.param'), 'onRequest']]);
286
287
			if ($config['debugger'] && interface_exists(IBarPanel::class)) {
288
				$builder->getDefinition($applicationService)
289
					->addSetup('$self = $this; $service->onStartup[] = function () use ($self) { $self->getService(?); }', [$this->prefix('default')])
290
					->addSetup('$service->onRequest[] = ?', [[$this->prefix('@panel'), 'onRequest']]);
291
			}
292
		}
293
294
		if (class_exists(Debugger::class)) {
295
			Panel::registerBluescreen();
296
		}
297
298
		$extractor = $builder->getDefinition($this->prefix('extractor'));
299 View Code Duplication
		foreach ($builder->findByTag(self::TAG_EXTRACTOR) as $extractorId => $meta) {
300
			Validators::assert($meta, 'string:2..');
301
302
			$extractor->addSetup('addExtractor', [$meta, '@' . $extractorId]);
303
304
			$builder->getDefinition($extractorId)->setAutowired(FALSE);
305
		}
306
307
		$writer = $builder->getDefinition($this->prefix('writer'));
308 View Code Duplication
		foreach ($builder->findByTag(self::TAG_DUMPER) as $dumperId => $meta) {
309
			Validators::assert($meta, 'string:2..');
310
311
			$writer->addSetup('addDumper', [$meta, '@' . $dumperId]);
312
313
			$builder->getDefinition($dumperId)->setAutowired(FALSE);
314
		}
315
316
		$this->loaders = [];
317
		foreach ($builder->findByTag(self::TAG_LOADER) as $loaderId => $meta) {
318
			Validators::assert($meta, 'string:2..');
319
			$builder->getDefinition($loaderId)->setAutowired(FALSE);
320
			$this->loaders[$meta] = $loaderId;
321
		}
322
323
		$builder->getDefinition($this->prefix('loader'))
324
			->addSetup('injectServiceIds', [$this->loaders]);
325
326
		foreach ($this->compiler->getExtensions() as $extension) {
327
			if (!$extension instanceof ITranslationProvider) {
328
				continue;
329
			}
330
331
			$config['dirs'] = array_merge($config['dirs'], array_values($extension->getTranslationResources()));
332
		}
333
334
		$config['dirs'] = array_map(function ($dir) {
335
			return str_replace((DIRECTORY_SEPARATOR === '/') ? '\\' : '/', DIRECTORY_SEPARATOR, $dir);
336
		}, $config['dirs']);
337
338
		$dirs = array_values(array_filter($config['dirs'], Callback::closure('is_dir')));
339
		if (count($dirs) > 0) {
340
			foreach ($dirs as $dir) {
341
				$builder->addDependency($dir);
342
			}
343
344
			$this->loadResourcesFromDirs($dirs);
345
		}
346
	}
347
348
	protected function beforeCompileLogging(array $config)
349
	{
350
		$builder = $this->getContainerBuilder();
351
		$translator = $builder->getDefinition($this->prefix('default'));
352
353
		if ($config['logging'] === TRUE) {
354
			$translator->addSetup('injectPsrLogger');
355
356
		} elseif (is_string($config['logging'])) { // channel for kdyby/monolog
357
			$translator->addSetup('injectPsrLogger', [
358
				new Statement(sprintf('@%s::channel', KdybyLogger::class), [$config['logging']]),
359
			]);
360
361
		} elseif ($config['logging'] !== NULL) {
362
			throw new \Kdyby\Translation\InvalidArgumentException(sprintf(
363
				'Invalid config option for logger. Valid are TRUE for general psr/log or string for kdyby/monolog channel, but %s was given',
364
				$config['logging']
365
			));
366
		}
367
	}
368
369
	protected function loadResourcesFromDirs($dirs)
370
	{
371
		$builder = $this->getContainerBuilder();
372
		$config = $this->getConfig();
373
374
		$whitelistRegexp = KdybyTranslator::buildWhitelistRegexp($config['whitelist']);
375
		$translator = $builder->getDefinition($this->prefix('default'));
376
377
		$mask = array_map(function ($value) {
378
			return '*.*.' . $value;
379
		}, array_keys($this->loaders));
380
381
		foreach (Finder::findFiles($mask)->from($dirs) as $file) {
382
			/** @var \SplFileInfo $file */
383
			if (!preg_match('~^(?P<domain>.*?)\.(?P<locale>[^\.]+)\.(?P<format>[^\.]+)$~', $file->getFilename(), $m)) {
384
				continue;
385
			}
386
387
			if ($whitelistRegexp && !preg_match($whitelistRegexp, $m['locale']) && $builder->parameters['productionMode']) {
388
				continue; // ignore in production mode, there is no need to pass the ignored resources
389
			}
390
391
			$this->validateResource($m['format'], $file->getPathname(), $m['locale'], $m['domain']);
392
			$translator->addSetup('addResource', [$m['format'], $file->getPathname(), $m['locale'], $m['domain']]);
393
			$builder->addDependency($file->getPathname());
394
		}
395
	}
396
397
	/**
398
	 * @param string $format
399
	 * @param string $file
400
	 * @param string $locale
401
	 * @param string $domain
402
	 */
403
	protected function validateResource($format, $file, $locale, $domain)
404
	{
405
		$builder = $this->getContainerBuilder();
406
407
		if (!isset($this->loaders[$format])) {
408
			return;
409
		}
410
411
		try {
412
			$def = $builder->getDefinition($this->loaders[$format]);
413
			$refl = ReflectionClassType::from($def->getEntity() ?: $def->getClass());
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\ServiceDefinition::getClass() has been deprecated with message: Use getType() instead.

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
414
			$method = $refl->getConstructor();
415
			if ($method !== NULL && $method->getNumberOfRequiredParameters() > 1) {
416
				return;
417
			}
418
419
			$loader = $refl->newInstance();
420
			if (!$loader instanceof LoaderInterface) {
421
				return;
422
			}
423
424
		} catch (\ReflectionException $e) {
425
			return;
426
		}
427
428
		try {
429
			$loader->load($file, $locale, $domain);
430
431
		} catch (\Exception $e) {
432
			throw new \Kdyby\Translation\InvalidResourceException(sprintf('Resource %s is not valid and cannot be loaded.', $file), 0, $e);
433
		}
434
	}
435
436
	public function afterCompile(ClassTypeGenerator $class)
437
	{
438
		$initialize = $class->getMethod('initialize');
439
		if (class_exists(Debugger::class)) {
440
			$initialize->addBody('?::registerBluescreen();', [new PhpLiteral(Panel::class)]);
441
		}
442
	}
443
444
	/**
445
	 * {@inheritdoc}
446
	 */
447
	public function getConfig(array $defaults = NULL, $expand = TRUE)
0 ignored issues
show
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...
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...
448
	{
449
		return parent::getConfig($this->defaults) + ['fallback' => ['en_US']];
450
	}
451
452
	private function isRegisteredConsoleExtension()
453
	{
454
		foreach ($this->compiler->getExtensions() as $extension) {
455
			if ($extension instanceof ConsoleExtension) {
456
				return TRUE;
457
			}
458
		}
459
460
		return FALSE;
461
	}
462
463
	/**
464
	 * @param \Nette\Configurator $configurator
465
	 */
466
	public static function register(Configurator $configurator)
467
	{
468
		$configurator->onCompile[] = function ($config, Compiler $compiler) {
469
			$compiler->addExtension('translation', new TranslationExtension());
470
		};
471
	}
472
473
	/**
474
	 * @param string|\stdClass $statement
475
	 * @return \Nette\DI\Statement[]
476
	 */
477
	protected static function filterArgs($statement)
478
	{
479
		return Helpers::filterArguments([is_string($statement) ? new Statement($statement) : $statement]);
480
	}
481
482
	/**
483
	 * @param string|NULL $class
484
	 * @param string $type
485
	 * @return bool
486
	 */
487
	private static function isOfType($class, $type)
488
	{
489
		return $class !== NULL && ($class === $type || is_subclass_of($class, $type));
490
	}
491
492
}
493