Completed
Push — master ( 0619a6...a51eeb )
by Jáchym
21s queued 10s
created

TranslationExtension::getConfigSchema()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 18
rs 9.6666
c 0
b 0
f 0
cc 1
nc 1
nop 0
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\Definitions\FactoryDefinition;
36
use Nette\DI\Helpers;
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\Schema\Expect;
42
use Nette\Schema\Schema;
43
use Nette\Utils\Callback;
44
use Nette\Utils\Finder;
45
use Nette\Utils\Validators;
46
use Symfony\Component\Translation\Extractor\ChainExtractor;
47
use Symfony\Component\Translation\Formatter\IntlFormatter;
48
use Symfony\Component\Translation\Formatter\IntlFormatterInterface;
49
use Symfony\Component\Translation\Formatter\MessageFormatter;
50
use Symfony\Component\Translation\Formatter\MessageFormatterInterface;
51
use Symfony\Component\Translation\Loader\LoaderInterface;
52
use Symfony\Component\Translation\MessageSelector;
53
use Symfony\Component\Translation\Writer\TranslationWriter;
54
use Tracy\Debugger;
55
use Tracy\IBarPanel;
56
57
class TranslationExtension extends \Nette\DI\CompilerExtension
58
{
59
60
	use \Kdyby\StrictObjects\Scream;
61
62
	/** @deprecated */
63
	const LOADER_TAG = self::TAG_LOADER;
64
	/** @deprecated */
65
	const DUMPER_TAG = self::TAG_DUMPER;
66
	/** @deprecated */
67
	const EXTRACTOR_TAG = self::TAG_EXTRACTOR;
68
69
	const TAG_LOADER = 'translation.loader';
70
	const TAG_DUMPER = 'translation.dumper';
71
	const TAG_EXTRACTOR = 'translation.extractor';
72
73
	const RESOLVER_REQUEST = 'request';
74
	const RESOLVER_HEADER = 'header';
75
	const RESOLVER_SESSION = 'session';
76
77
	/**
78
	 * @var mixed[]
79
	 */
80
	public $defaults = [
81
		'whitelist' => NULL, // array('cs', 'en'),
82
		'default' => 'en',
83
		'logging' => NULL, //  TRUE for psr/log, or string for kdyby/monolog channel
84
		// 'fallback' => array('en_US', 'en'), // using custom merge strategy becase Nette's config merger appends lists of values
85
		'dirs' => ['%appDir%/lang', '%appDir%/locale'],
86
		'cache' => PhpFileStorage::class,
87
		'debugger' => '%debugMode%',
88
		'resolvers' => [
89
			self::RESOLVER_SESSION => FALSE,
90
			self::RESOLVER_REQUEST => TRUE,
91
			self::RESOLVER_HEADER => TRUE,
92
		],
93
		'loaders' => [],
94
	];
95
96
	/**
97
	 * @var array
98
	 */
99
	private $loaders;
100
101
	public function getConfigSchema(): Schema
102
	{
103
		return Expect::structure([
104
			'whitelist' => Expect::anyOf(Expect::arrayOf('string'), NULL),
105
			'default' => Expect::string('en'),
106
			'logging' => Expect::anyOf(Expect::string(), Expect::bool()),
107
			'fallback' => Expect::arrayOf('string')->default(['en_US']),
108
			'dirs' => Expect::arrayOf('string')->default(['%appDir%/lang', '%appDir%/locale']),
109
			'cache' => Expect::string(PhpFileStorage::class),
110
			'debugger' => Expect::bool(FALSE),
111
			'resolvers' => Expect::array()->default([
112
				self::RESOLVER_SESSION => FALSE,
113
				self::RESOLVER_REQUEST => TRUE,
114
				self::RESOLVER_HEADER => TRUE,
115
			]),
116
			'loaders' => Expect::array(),
117
		])->castTo('array');
118
	}
119
120
	public function loadConfiguration()
121
	{
122
		$this->loaders = [];
123
124
		$builder = $this->getContainerBuilder();
125
		/** @var array $config */
126
		$config = $this->config;
127
		$config['cache'] = new Statement($config['cache'], [dirname(Helpers::expand('%tempDir%/cache', $builder->parameters))]);
128
129
		$translator = $builder->addDefinition($this->prefix('default'))
130
			->setFactory(KdybyTranslator::class, [$this->prefix('@userLocaleResolver')])
131
			->addSetup('?->setTranslator(?)', [$this->prefix('@userLocaleResolver.param'), '@self'])
132
			->addSetup('setDefaultLocale', [$config['default']])
133
			->addSetup('setLocaleWhitelist', [$config['whitelist']]);
134
135
		Validators::assertField($config, 'fallback', 'list');
136
		$translator->addSetup('setFallbackLocales', [$config['fallback']]);
137
138
		$catalogueCompiler = $builder->addDefinition($this->prefix('catalogueCompiler'))
139
			->setFactory(CatalogueCompiler::class, self::filterArgs($config['cache']));
140
141
		if ($config['debugger'] && interface_exists(IBarPanel::class)) {
142
			$builder->addDefinition($this->prefix('panel'))
143
				->setFactory(Panel::class, [dirname(Helpers::expand('%appDir%', $builder->parameters))])
144
				->addSetup('setLocaleWhitelist', [$config['whitelist']]);
145
146
			$translator->addSetup('?->register(?)', [$this->prefix('@panel'), '@self']);
147
			$catalogueCompiler->addSetup('enableDebugMode');
148
		}
149
150
		$this->loadLocaleResolver($config);
151
152
		$builder->addDefinition($this->prefix('helpers'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\Definitions\ServiceDefinition::setClass() has been deprecated with message: Use setType()

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(TemplateHelpers::class)
154
			->setFactory($this->prefix('@default') . '::createTemplateHelpers');
155
156
		$builder->addDefinition($this->prefix('fallbackResolver'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\Definitions\ServiceDefinition::setClass() has been deprecated with message: Use setType()

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...
157
			->setClass(FallbackResolver::class);
158
159
		$builder->addDefinition($this->prefix('catalogueFactory'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\Definitions\ServiceDefinition::setClass() has been deprecated with message: Use setType()

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...
160
			->setClass(CatalogueFactory::class);
161
162
		$builder->addDefinition($this->prefix('selector'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\Definitions\ServiceDefinition::setClass() has been deprecated with message: Use setType()

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...
163
			->setClass(MessageSelector::class);
164
165
		if (interface_exists(IntlFormatterInterface::class)) {
166
			$builder->addDefinition($this->prefix('intlFormatter'))
167
				->setType(IntlFormatterInterface::class)
168
				->setFactory(IntlFormatter::class);
169
		}
170
171
		$builder->addDefinition($this->prefix('formatter'))
172
			->setType(MessageFormatterInterface::class)
173
			->setFactory(MessageFormatter::class);
174
175
		$builder->addDefinition($this->prefix('extractor'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\Definitions\ServiceDefinition::setClass() has been deprecated with message: Use setType()

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...
176
			->setClass(ChainExtractor::class);
177
178
		$this->loadExtractors();
179
180
		$builder->addDefinition($this->prefix('writer'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\Definitions\ServiceDefinition::setClass() has been deprecated with message: Use setType()

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...
181
			->setClass(TranslationWriter::class);
182
183
		$this->loadDumpers();
184
185
		$builder->addDefinition($this->prefix('loader'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\Definitions\ServiceDefinition::setClass() has been deprecated with message: Use setType()

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...
186
			->setClass(TranslationLoader::class);
187
188
		$loaders = $this->loadFromFile(__DIR__ . '/config/loaders.neon');
189
		$this->loadLoaders($loaders, $config['loaders'] ?: array_keys($loaders));
190
191
		if ($this->isRegisteredConsoleExtension()) {
192
			$this->loadConsole($config);
193
		}
194
	}
195
196
	protected function loadLocaleResolver(array $config)
197
	{
198
		$builder = $this->getContainerBuilder();
199
200
		$builder->addDefinition($this->prefix('userLocaleResolver.param'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\Definitions\ServiceDefinition::setClass() has been deprecated with message: Use setType()

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...
201
			->setClass(LocaleParamResolver::class)
202
			->setAutowired(FALSE);
203
204
		$builder->addDefinition($this->prefix('userLocaleResolver.acceptHeader'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\Definitions\ServiceDefinition::setClass() has been deprecated with message: Use setType()

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...
205
			->setClass(AcceptHeaderResolver::class);
206
207
		$builder->addDefinition($this->prefix('userLocaleResolver.session'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\Definitions\ServiceDefinition::setClass() has been deprecated with message: Use setType()

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...
208
			->setClass(SessionResolver::class);
209
210
		$chain = $builder->addDefinition($this->prefix('userLocaleResolver'))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\Definitions\ServiceDefinition::setClass() has been deprecated with message: Use setType()

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...
211
			->setClass(IUserLocaleResolver::class)
212
			->setFactory(ChainResolver::class);
213
214
		$resolvers = [];
215 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...
216
			$resolvers[] = $this->prefix('@userLocaleResolver.acceptHeader');
217
			$chain->addSetup('addResolver', [$this->prefix('@userLocaleResolver.acceptHeader')]);
218
		}
219
220 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...
221
			$resolvers[] = $this->prefix('@userLocaleResolver.param');
222
			$chain->addSetup('addResolver', [$this->prefix('@userLocaleResolver.param')]);
223
		}
224
225 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...
226
			$resolvers[] = $this->prefix('@userLocaleResolver.session');
227
			$chain->addSetup('addResolver', [$this->prefix('@userLocaleResolver.session')]);
228
		}
229
230
		if ($config['debugger'] && interface_exists(IBarPanel::class)) {
231
			/** @var \Nette\DI\Definitions\ServiceDefinition $panel */
232
			$panel = $builder->getDefinition($this->prefix('panel'));
233
			$panel->addSetup('setLocaleResolvers', [array_reverse($resolvers)]);
234
		}
235
	}
236
237
	protected function loadConsole(array $config)
238
	{
239
		$builder = $this->getContainerBuilder();
240
241
		Validators::assertField($config, 'dirs', 'list');
242
		$builder->addDefinition($this->prefix('console.extract'))
243
			->setFactory(ExtractCommand::class)
244
			->addSetup('$defaultOutputDir', [reset($config['dirs'])])
245
			->addTag(ConsoleExtension::TAG_COMMAND, 'latte');
246
	}
247
248 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...
249
	{
250
		$builder = $this->getContainerBuilder();
251
252
		foreach ($this->loadFromFile(__DIR__ . '/config/dumpers.neon') as $format => $class) {
253
			$builder->addDefinition($this->prefix('dumper.' . $format))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\Definitions\ServiceDefinition::setClass() has been deprecated with message: Use setType()

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...
254
				->setClass($class)
255
				->addTag(self::TAG_DUMPER, $format);
256
		}
257
	}
258
259
	protected function loadLoaders(array $loaders, array $allowed)
260
	{
261
		$builder = $this->getContainerBuilder();
262
263
		foreach ($loaders as $format => $class) {
264
			if (array_search($format, $allowed) === FALSE) {
265
				continue;
266
			}
267
			$builder->addDefinition($this->prefix('loader.' . $format))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\Definitions\ServiceDefinition::setClass() has been deprecated with message: Use setType()

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...
268
				->setClass($class)
269
				->addTag(self::TAG_LOADER, $format);
270
		}
271
	}
272
273 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...
274
	{
275
		$builder = $this->getContainerBuilder();
276
277
		foreach ($this->loadFromFile(__DIR__ . '/config/extractors.neon') as $format => $class) {
278
			$builder->addDefinition($this->prefix('extractor.' . $format))
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\Definitions\ServiceDefinition::setClass() has been deprecated with message: Use setType()

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...
279
				->setClass($class)
280
				->addTag(self::TAG_EXTRACTOR, $format);
281
		}
282
	}
283
284
	public function beforeCompile()
285
	{
286
		$builder = $this->getContainerBuilder();
287
		/** @var array $config */
288
		$config = $this->config;
289
290
		$this->beforeCompileLogging($config);
291
292
		$registerToLatte = function (FactoryDefinition $def) {
293
			$def->getResultDefinition()->addSetup('?->onCompile[] = function($engine) { ?::install($engine->getCompiler()); }', ['@self', new PhpLiteral(TranslateMacros::class)]);
294
295
			$def->getResultDefinition()
296
				->addSetup('addProvider', ['translator', $this->prefix('@default')])
297
				->addSetup('addFilter', ['translate', [$this->prefix('@helpers'), 'translateFilterAware']]);
298
		};
299
300
		$latteFactoryService = $builder->getByType(ILatteFactory::class);
301
		if (!$latteFactoryService || !self::isOfType($builder->getDefinition($latteFactoryService)->getClass(), LatteEngine::class)) {
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\Definitions\Definition::getClass() has been deprecated with message: Use getType()

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...
302
			$latteFactoryService = 'nette.latteFactory';
303
		}
304
305
		if ($builder->hasDefinition($latteFactoryService) && self::isOfType($builder->getDefinition($latteFactoryService)->getClass(), ILatteFactory::class)) {
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\Definitions\Definition::getClass() has been deprecated with message: Use getType()

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...
306
			$registerToLatte($builder->getDefinition($latteFactoryService));
307
		}
308
309
		if ($builder->hasDefinition('nette.latte')) {
310
			$registerToLatte($builder->getDefinition('nette.latte'));
311
		}
312
313
		$applicationService = $builder->getByType(Application::class) ?: 'application';
314
		if ($builder->hasDefinition($applicationService)) {
315
316
			/** @var \Nette\DI\Definitions\ServiceDefinition $applicationServiceDefinition */
317
			$applicationServiceDefinition = $builder->getDefinition($applicationService);
318
			$applicationServiceDefinition
319
				->addSetup('$service->onRequest[] = ?', [[$this->prefix('@userLocaleResolver.param'), 'onRequest']]);
320
321
			if ($config['debugger'] && interface_exists(IBarPanel::class)) {
322
				$applicationServiceDefinition
323
					->addSetup('$self = $this; $service->onStartup[] = function () use ($self) { $self->getService(?); }', [$this->prefix('default')])
324
					->addSetup('$service->onRequest[] = ?', [[$this->prefix('@panel'), 'onRequest']]);
325
			}
326
		}
327
328
		if (class_exists(Debugger::class)) {
329
			Panel::registerBluescreen();
330
		}
331
332
		/** @var \Nette\DI\Definitions\ServiceDefinition $extractor */
333
		$extractor = $builder->getDefinition($this->prefix('extractor'));
334 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...
335
			Validators::assert($meta, 'string:2..');
336
337
			$extractor->addSetup('addExtractor', [$meta, '@' . $extractorId]);
338
339
			$builder->getDefinition($extractorId)->setAutowired(FALSE);
340
		}
341
342
		/** @var \Nette\DI\Definitions\ServiceDefinition $writer */
343
		$writer = $builder->getDefinition($this->prefix('writer'));
344 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...
345
			Validators::assert($meta, 'string:2..');
346
347
			$writer->addSetup('addDumper', [$meta, '@' . $dumperId]);
348
349
			$builder->getDefinition($dumperId)->setAutowired(FALSE);
350
		}
351
352
		$this->loaders = [];
353
		foreach ($builder->findByTag(self::TAG_LOADER) as $loaderId => $meta) {
354
			Validators::assert($meta, 'string:2..');
355
			$builder->getDefinition($loaderId)->setAutowired(FALSE);
356
			$this->loaders[$meta] = $loaderId;
357
		}
358
359
		/** @var \Nette\DI\Definitions\ServiceDefinition $loaderDefinition */
360
		$loaderDefinition = $builder->getDefinition($this->prefix('loader'));
361
		$loaderDefinition->addSetup('injectServiceIds', [$this->loaders]);
362
363
		foreach ($this->compiler->getExtensions() as $extension) {
364
			if (!$extension instanceof ITranslationProvider) {
365
				continue;
366
			}
367
368
			$config['dirs'] = array_merge($config['dirs'], array_values($extension->getTranslationResources()));
369
		}
370
371
		$config['dirs'] = array_map(function ($dir) use ($builder) {
372
			return str_replace((DIRECTORY_SEPARATOR === '/') ? '\\' : '/', DIRECTORY_SEPARATOR, Helpers::expand($dir, $builder->parameters));
373
		}, $config['dirs']);
374
375
		$dirs = array_values(array_filter($config['dirs'], Callback::closure('is_dir')));
0 ignored issues
show
Deprecated Code introduced by
The method Nette\Utils\Callback::closure() has been deprecated with message: use Closure::fromCallable()

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...
376
		if (count($dirs) > 0) {
377
			foreach ($dirs as $dir) {
378
				$builder->addDependency($dir);
379
			}
380
381
			$this->loadResourcesFromDirs($dirs);
382
		}
383
	}
384
385
	protected function beforeCompileLogging(array $config)
386
	{
387
		$builder = $this->getContainerBuilder();
388
		/** @var \Nette\DI\Definitions\ServiceDefinition $translator */
389
		$translator = $builder->getDefinition($this->prefix('default'));
390
391
		if ($config['logging'] === TRUE) {
392
			$translator->addSetup('injectPsrLogger');
393
394
		} elseif (is_string($config['logging'])) { // channel for kdyby/monolog
395
			$translator->addSetup('injectPsrLogger', [
396
				new Statement(sprintf('@%s::channel', KdybyLogger::class), [$config['logging']]),
397
			]);
398
399
		} elseif ($config['logging'] !== NULL) {
400
			throw new \Kdyby\Translation\InvalidArgumentException(sprintf(
401
				'Invalid config option for logger. Valid are TRUE for general psr/log or string for kdyby/monolog channel, but %s was given',
402
				$config['logging']
403
			));
404
		}
405
	}
406
407
	protected function loadResourcesFromDirs($dirs)
408
	{
409
		$builder = $this->getContainerBuilder();
410
		$config = $this->config;
411
412
		$whitelistRegexp = KdybyTranslator::buildWhitelistRegexp($config['whitelist']);
413
		/** @var \Nette\DI\Definitions\ServiceDefinition $translator */
414
		$translator = $builder->getDefinition($this->prefix('default'));
415
416
		$mask = array_map(function ($value) {
417
			return '*.*.' . $value;
418
		}, array_keys($this->loaders));
419
420
		foreach (Finder::findFiles($mask)->from($dirs) as $file) {
421
			/** @var \SplFileInfo $file */
422
			if (!preg_match('~^(?P<domain>.*?)\.(?P<locale>[^\.]+)\.(?P<format>[^\.]+)$~', $file->getFilename(), $m)) {
423
				continue;
424
			}
425
426
			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...
427
				continue; // ignore in production mode, there is no need to pass the ignored resources
428
			}
429
430
			$this->validateResource($m['format'], $file->getPathname(), $m['locale'], $m['domain']);
431
			$translator->addSetup('addResource', [$m['format'], $file->getPathname(), $m['locale'], $m['domain']]);
432
			$builder->addDependency($file->getPathname());
433
		}
434
	}
435
436
	/**
437
	 * @param string $format
438
	 * @param string $file
439
	 * @param string $locale
440
	 * @param string $domain
441
	 */
442
	protected function validateResource($format, $file, $locale, $domain)
443
	{
444
		$builder = $this->getContainerBuilder();
445
446
		if (!isset($this->loaders[$format])) {
447
			return;
448
		}
449
450
		try {
451
			/** @var \Nette\DI\Definitions\ServiceDefinition $def */
452
			$def = $builder->getDefinition($this->loaders[$format]);
453
			$refl = ReflectionClassType::from($def->getEntity() ?: $def->getClass());
0 ignored issues
show
Deprecated Code introduced by
The method Nette\DI\Definitions\Definition::getClass() has been deprecated with message: Use getType()

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...
454
			$method = $refl->getConstructor();
455
			if ($method !== NULL && $method->getNumberOfRequiredParameters() > 1) {
456
				return;
457
			}
458
459
			$loader = $refl->newInstance();
460
			if (!$loader instanceof LoaderInterface) {
461
				return;
462
			}
463
464
		} catch (\ReflectionException $e) {
465
			return;
466
		}
467
468
		try {
469
			$loader->load($file, $locale, $domain);
470
471
		} catch (\Exception $e) {
472
			throw new \Kdyby\Translation\InvalidResourceException(sprintf('Resource %s is not valid and cannot be loaded.', $file), 0, $e);
473
		}
474
	}
475
476
	public function afterCompile(ClassTypeGenerator $class)
477
	{
478
		$initialize = $class->getMethod('initialize');
479
		if (class_exists(Debugger::class)) {
480
			$initialize->addBody('?::registerBluescreen();', [new PhpLiteral(Panel::class)]);
481
		}
482
	}
483
484
	private function isRegisteredConsoleExtension()
485
	{
486
		foreach ($this->compiler->getExtensions() as $extension) {
487
			if ($extension instanceof ConsoleExtension) {
488
				return TRUE;
489
			}
490
		}
491
492
		return FALSE;
493
	}
494
495
	/**
496
	 * @param \Nette\Configurator $configurator
497
	 */
498
	public static function register(Configurator $configurator)
499
	{
500
		$configurator->onCompile[] = function ($config, Compiler $compiler) {
501
			$compiler->addExtension('translation', new TranslationExtension());
502
		};
503
	}
504
505
	/**
506
	 * @param string|\stdClass $statement
507
	 * @return \Nette\DI\Statement[]
508
	 */
509
	protected static function filterArgs($statement)
510
	{
511
		return Helpers::filterArguments([is_string($statement) ? new Statement($statement) : $statement]);
512
	}
513
514
	/**
515
	 * @param string|NULL $class
516
	 * @param string $type
517
	 * @return bool
518
	 */
519
	private static function isOfType($class, $type)
520
	{
521
		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...
522
	}
523
524
}
525