1: <?php
2:
3: 4: 5: 6:
7:
8: namespace Nette\DI\Extensions;
9:
10: use Nette;
11: use Nette\DI\ContainerBuilder;
12: use Nette\Utils\Validators;
13:
14:
15: 16: 17: 18: 19:
20: class NetteExtension extends Nette\DI\CompilerExtension
21: {
22: public $defaults = array(
23: 'http' => array(
24: 'proxy' => array(),
25: ),
26: 'session' => array(
27: 'debugger' => FALSE,
28: 'autoStart' => 'smart',
29: 'expiration' => NULL,
30: ),
31: 'application' => array(
32: 'debugger' => TRUE,
33: 'errorPresenter' => 'Nette:Error',
34: 'catchExceptions' => '%productionMode%',
35: 'mapping' => NULL
36: ),
37: 'routing' => array(
38: 'debugger' => TRUE,
39: 'routes' => array(),
40: ),
41: 'security' => array(
42: 'debugger' => TRUE,
43: 'frames' => 'SAMEORIGIN',
44: 'users' => array(),
45: 'roles' => array(),
46: 'resources' => array(),
47: ),
48: 'mailer' => array(
49: 'smtp' => FALSE,
50: 'host' => NULL,
51: 'port' => NULL,
52: 'username' => NULL,
53: 'password' => NULL,
54: 'secure' => NULL,
55: 'timeout' => NULL,
56: ),
57: 'database' => array(),
58: 'forms' => array(
59: 'messages' => array(),
60: ),
61: 'latte' => array(
62: 'xhtml' => FALSE,
63: 'macros' => array(),
64: ),
65: 'container' => array(
66: 'debugger' => FALSE,
67: 'accessors' => TRUE,
68: ),
69: 'debugger' => array(
70: 'email' => NULL,
71: 'editor' => NULL,
72: 'browser' => NULL,
73: 'strictMode' => NULL,
74: 'maxLen' => NULL,
75: 'maxDepth' => NULL,
76: 'showLocation' => NULL,
77: 'scream' => NULL,
78: 'bar' => array(),
79: 'blueScreen' => array(),
80: ),
81: );
82:
83: public $databaseDefaults = array(
84: 'dsn' => NULL,
85: 'user' => NULL,
86: 'password' => NULL,
87: 'options' => NULL,
88: 'debugger' => TRUE,
89: 'explain' => TRUE,
90: 'reflection' => 'Nette\Database\Reflection\DiscoveredReflection',
91: 'autowired' => NULL,
92: );
93:
94:
95: public function loadConfiguration()
96: {
97: $container = $this->getContainerBuilder();
98: $config = $this->getConfig($this->defaults);
99:
100: if (isset($config['xhtml'])) {
101: $config['latte']['xhtml'] = $config['xhtml'];
102: unset($config['xhtml']);
103: }
104: $container->addDefinition('nette')->setClass('Nette\DI\Extensions\NetteAccessor', array('@container'));
105:
106: $this->validate($config, $this->defaults, 'nette');
107:
108: $this->setupCache($container);
109: $this->setupHttp($container, $config['http']);
110: $this->setupSession($container, $config['session']);
111: $this->setupSecurity($container, $config['security']);
112: $this->setupApplication($container, $config['application']);
113: $this->setupRouting($container, $config['routing']);
114: $this->setupMailer($container, $config['mailer']);
115: $this->setupForms($container);
116: $this->setupLatte($container, $config['latte']);
117: $this->setupDatabase($container, $config['database']);
118: $this->setupContainer($container, $config['container']);
119: }
120:
121:
122: private function setupCache(ContainerBuilder $container)
123: {
124: $container->addDefinition($this->prefix('cacheJournal'))
125: ->setClass('Nette\Caching\Storages\FileJournal', array($container->expand('%tempDir%')));
126:
127: $container->addDefinition('cacheStorage')
128: ->setClass('Nette\Caching\Storages\FileStorage', array($container->expand('%tempDir%/cache')));
129:
130: $container->addDefinition($this->prefix('templateCacheStorage'))
131: ->setClass('Nette\Caching\Storages\PhpFileStorage', array($container->expand('%tempDir%/cache')))
132: ->setAutowired(FALSE);
133:
134: $container->addDefinition($this->prefix('cache'))
135: ->setClass('Nette\Caching\Cache', array(1 => $container::literal('$namespace')))
136: ->addSetup('::trigger_error', array('Service cache is deprecated.', E_USER_DEPRECATED))
137: ->setParameters(array('namespace' => NULL));
138: }
139:
140:
141: private function setupHttp(ContainerBuilder $container, array $config)
142: {
143: $this->validate($config, $this->defaults['http'], 'nette.http');
144:
145: $container->addDefinition($this->prefix('httpRequestFactory'))
146: ->setClass('Nette\Http\RequestFactory')
147: ->addSetup('setProxy', array($config['proxy']));
148:
149: $container->addDefinition('httpRequest')
150: ->setClass('Nette\Http\Request')
151: ->setFactory('@Nette\Http\RequestFactory::createHttpRequest');
152:
153: $container->addDefinition('httpResponse')
154: ->setClass('Nette\Http\Response');
155:
156: $container->addDefinition($this->prefix('httpContext'))
157: ->setClass('Nette\Http\Context');
158: }
159:
160:
161: private function setupSession(ContainerBuilder $container, array $config)
162: {
163: $session = $container->addDefinition('session')
164: ->setClass('Nette\Http\Session');
165:
166: if (isset($config['expiration'])) {
167: $session->addSetup('setExpiration', array($config['expiration']));
168: }
169:
170: if ($container->parameters['debugMode'] && $config['debugger']) {
171: $session->addSetup('Nette\Diagnostics\Debugger::getBar()->addPanel(?)', array(
172: new Nette\DI\Statement('Nette\Http\Diagnostics\SessionPanel')
173: ));
174: }
175:
176: unset($config['expiration'], $config['autoStart'], $config['debugger']);
177: if (!empty($config)) {
178: $session->addSetup('setOptions', array($config));
179: }
180: }
181:
182:
183: private function setupSecurity(ContainerBuilder $container, array $config)
184: {
185: $this->validate($config, $this->defaults['security'], 'nette.security');
186:
187: $container->addDefinition($this->prefix('userStorage'))
188: ->setClass('Nette\Http\UserStorage');
189:
190: $user = $container->addDefinition('user')
191: ->setClass('Nette\Security\User');
192:
193: if ($container->parameters['debugMode'] && $config['debugger']) {
194: $user->addSetup('Nette\Diagnostics\Debugger::getBar()->addPanel(?)', array(
195: new Nette\DI\Statement('Nette\Security\Diagnostics\UserPanel')
196: ));
197: }
198:
199: if ($config['users']) {
200: $container->addDefinition($this->prefix('authenticator'))
201: ->setClass('Nette\Security\SimpleAuthenticator', array($config['users']));
202: }
203:
204: if ($config['roles'] || $config['resources']) {
205: $authorizator = $container->addDefinition($this->prefix('authorizator'))
206: ->setClass('Nette\Security\Permission');
207: foreach ($config['roles'] as $role => $parents) {
208: $authorizator->addSetup('addRole', array($role, $parents));
209: }
210: foreach ($config['resources'] as $resource => $parents) {
211: $authorizator->addSetup('addResource', array($resource, $parents));
212: }
213: }
214: }
215:
216:
217: private function setupApplication(ContainerBuilder $container, array $config)
218: {
219: $this->validate($config, $this->defaults['application'], 'nette.application');
220:
221: $application = $container->addDefinition('application')
222: ->setClass('Nette\Application\Application')
223: ->addSetup('$catchExceptions', array($config['catchExceptions']))
224: ->addSetup('$errorPresenter', array($config['errorPresenter']));
225:
226: if ($config['debugger']) {
227: $application->addSetup('Nette\Application\Diagnostics\RoutingPanel::initializePanel');
228: }
229:
230: $presenterFactory = $container->addDefinition($this->prefix('presenterFactory'))
231: ->setClass('Nette\Application\PresenterFactory', array(
232: isset($container->parameters['appDir']) ? $container->parameters['appDir'] : NULL
233: ));
234: if ($config['mapping']) {
235: $presenterFactory->addSetup('setMapping', array($config['mapping']));
236: }
237: }
238:
239:
240: private function setupRouting(ContainerBuilder $container, array $config)
241: {
242: $this->validate($config, $this->defaults['routing'], 'nette.routing');
243:
244: $router = $container->addDefinition('router')
245: ->setClass('Nette\Application\Routers\RouteList');
246:
247: foreach ($config['routes'] as $mask => $action) {
248: $router->addSetup('$service[] = new Nette\Application\Routers\Route(?, ?);', array($mask, $action));
249: }
250:
251: if ($container->parameters['debugMode'] && $config['debugger']) {
252: $container->getDefinition('application')->addSetup('Nette\Diagnostics\Debugger::getBar()->addPanel(?)', array(
253: new Nette\DI\Statement('Nette\Application\Diagnostics\RoutingPanel')
254: ));
255: }
256: }
257:
258:
259: private function setupMailer(ContainerBuilder $container, array $config)
260: {
261: $this->validate($config, $this->defaults['mailer'], 'nette.mailer');
262:
263: if (empty($config['smtp'])) {
264: $container->addDefinition($this->prefix('mailer'))
265: ->setClass('Nette\Mail\SendmailMailer');
266: } else {
267: $container->addDefinition($this->prefix('mailer'))
268: ->setClass('Nette\Mail\SmtpMailer', array($config));
269: }
270:
271: $container->addDefinition($this->prefix('mail'))
272: ->setClass('Nette\Mail\Message')
273: ->addSetup('::trigger_error', array('Service nette.mail is deprecated.', E_USER_DEPRECATED))
274: ->addSetup('setMailer')
275: ->setAutowired(FALSE);
276: }
277:
278:
279: private function setupForms(ContainerBuilder $container)
280: {
281: $container->addDefinition($this->prefix('basicForm'))
282: ->setClass('Nette\Forms\Form')
283: ->addSetup('::trigger_error', array('Service nette.basicForm is deprecated.', E_USER_DEPRECATED))
284: ->setAutowired(FALSE);
285: }
286:
287:
288: private function setupLatte(ContainerBuilder $container, array $config)
289: {
290: $this->validate($config, $this->defaults['latte'], 'nette.latte');
291:
292: $latte = $container->addDefinition($this->prefix('latte'))
293: ->setClass('Nette\Latte\Engine')
294: ->setAutowired(FALSE);
295:
296: if ($config['xhtml']) {
297: $latte->addSetup('$service->getCompiler()->defaultContentType = ?', array(Nette\Latte\Compiler::CONTENT_XHTML));
298: }
299:
300: $container->addDefinition($this->prefix('template'))
301: ->setClass('Nette\Templating\FileTemplate')
302: ->addSetup('registerFilter', array($latte))
303: ->addSetup('registerHelperLoader', array('Nette\Templating\Helpers::loader'))
304: ->setAutowired(FALSE);
305:
306: foreach ($config['macros'] as $macro) {
307: if (strpos($macro, '::') === FALSE && class_exists($macro)) {
308: $macro .= '::install';
309:
310: } else {
311: Validators::assert($macro, 'callable');
312: }
313:
314: $latte->addSetup($macro . '(?->compiler)', array('@self'));
315: }
316: }
317:
318:
319: private function setupDatabase(ContainerBuilder $container, array $config)
320: {
321: if (isset($config['dsn'])) {
322: $config = array('default' => $config);
323: }
324:
325: $autowired = TRUE;
326: foreach ((array) $config as $name => $info) {
327: if (!is_array($info)) {
328: continue;
329: }
330: $this->validate($info, $this->databaseDefaults, 'nette.database');
331:
332: $info += array('autowired' => $autowired) + $this->databaseDefaults;
333: $autowired = FALSE;
334:
335: foreach ((array) $info['options'] as $key => $value) {
336: if (preg_match('#^PDO::\w+\z#', $key)) {
337: unset($info['options'][$key]);
338: $info['options'][constant($key)] = $value;
339: }
340: }
341:
342: if (!$info['reflection']) {
343: $reflection = NULL;
344: } elseif (is_string($info['reflection'])) {
345: $reflection = new Nette\DI\Statement(preg_match('#^[a-z]+\z#', $info['reflection'])
346: ? 'Nette\Database\Reflection\\' . ucfirst($info['reflection']) . 'Reflection'
347: : $info['reflection'], strtolower($info['reflection']) === 'discovered' ? array('@self') : array());
348: } else {
349: $tmp = Nette\DI\Compiler::filterArguments(array($info['reflection']));
350: $reflection = reset($tmp);
351: }
352:
353: $connection = $container->addDefinition($this->prefix("database.$name"))
354: ->setClass('Nette\Database\Connection', array($info['dsn'], $info['user'], $info['password'], $info['options']))
355: ->setAutowired($info['autowired'])
356: ->addSetup('setContext', array(
357: new Nette\DI\Statement('Nette\Database\Context', array('@self', $reflection)),
358: ))
359: ->addSetup('Nette\Diagnostics\Debugger::getBlueScreen()->addPanel(?)', array(
360: 'Nette\Database\Diagnostics\ConnectionPanel::renderException'
361: ));
362:
363: $container->addDefinition($this->prefix("database.$name.context"))
364: ->setClass('Nette\Database\Context')
365: ->setFactory(array($connection, 'getContext'))
366: ->setAutowired($info['autowired']);
367:
368: if ($container->parameters['debugMode'] && $info['debugger']) {
369: $connection->addSetup('Nette\Database\Helpers::createDebugPanel', array($connection, !empty($info['explain']), $name));
370: }
371: }
372: }
373:
374:
375: private function setupContainer(ContainerBuilder $container, array $config)
376: {
377: $this->validate($config, $this->defaults['container'], 'nette.container');
378:
379: if ($config['accessors']) {
380: $container->parameters['container']['accessors'] = TRUE;
381: }
382: }
383:
384:
385: public function afterCompile(Nette\PhpGenerator\ClassType $class)
386: {
387: $initialize = $class->methods['initialize'];
388: $container = $this->getContainerBuilder();
389: $config = $this->getConfig($this->defaults);
390:
391:
392: foreach (array('email', 'editor', 'browser', 'strictMode', 'maxLen', 'maxDepth', 'showLocation', 'scream') as $key) {
393: if (isset($config['debugger'][$key])) {
394: $initialize->addBody('Nette\Diagnostics\Debugger::$? = ?;', array($key, $config['debugger'][$key]));
395: }
396: }
397:
398: if ($container->parameters['debugMode']) {
399: if ($config['container']['debugger']) {
400: $config['debugger']['bar'][] = 'Nette\DI\Diagnostics\ContainerPanel';
401: }
402:
403: foreach ((array) $config['debugger']['bar'] as $item) {
404: $initialize->addBody($container->formatPhp(
405: 'Nette\Diagnostics\Debugger::getBar()->addPanel(?);',
406: Nette\DI\Compiler::filterArguments(array(is_string($item) ? new Nette\DI\Statement($item) : $item))
407: ));
408: }
409: }
410:
411: foreach ((array) $config['debugger']['blueScreen'] as $item) {
412: $initialize->addBody($container->formatPhp(
413: 'Nette\Diagnostics\Debugger::getBlueScreen()->addPanel(?);',
414: Nette\DI\Compiler::filterArguments(array($item))
415: ));
416: }
417:
418: if (!empty($container->parameters['tempDir'])) {
419: $initialize->addBody('Nette\Caching\Storages\FileStorage::$useDirectories = ?;', array($this->checkTempDir($container->expand('%tempDir%/cache'))));
420: }
421:
422: foreach ((array) $config['forms']['messages'] as $name => $text) {
423: $initialize->addBody('Nette\Forms\Rules::$defaultMessages[Nette\Forms\Form::?] = ?;', array($name, $text));
424: }
425:
426: if ($config['session']['autoStart'] === 'smart') {
427: $initialize->addBody('$this->getByType("Nette\Http\Session")->exists() && $this->getByType("Nette\Http\Session")->start();');
428: } elseif ($config['session']['autoStart']) {
429: $initialize->addBody('$this->getByType("Nette\Http\Session")->start();');
430: }
431:
432: if ($config['latte']['xhtml']) {
433: $initialize->addBody('Nette\Utils\Html::$xhtml = ?;', array(TRUE));
434: }
435:
436: if (isset($config['security']['frames']) && $config['security']['frames'] !== TRUE) {
437: $frames = $config['security']['frames'];
438: if ($frames === FALSE) {
439: $frames = 'DENY';
440: } elseif (preg_match('#^https?:#', $frames)) {
441: $frames = "ALLOW-FROM $frames";
442: }
443: $initialize->addBody('header(?);', array("X-Frame-Options: $frames"));
444: }
445:
446: foreach ($container->findByTag('run') as $name => $on) {
447: if ($on) {
448: $initialize->addBody('$this->getService(?);', array($name));
449: }
450: }
451:
452: if (!empty($config['container']['accessors'])) {
453: $definitions = $container->definitions;
454: ksort($definitions);
455: foreach ($definitions as $name => $def) {
456: if (Nette\PhpGenerator\Helpers::isIdentifier($name)) {
457: $type = $def->implement ?: $def->class;
458: $class->addDocument("@property $type \$$name");
459: }
460: }
461: }
462:
463: $initialize->addBody("@header('X-Powered-By: Nette Framework');");
464: $initialize->addBody("@header('Content-Type: text/html; charset=utf-8');");
465: $initialize->addBody('Nette\Utils\SafeStream::register();');
466: }
467:
468:
469: private function checkTempDir($dir)
470: {
471:
472: $uniq = uniqid('_', TRUE);
473: if (!@mkdir("$dir/$uniq")) {
474: throw new Nette\InvalidStateException("Unable to write to directory '$dir'. Make this directory writable.");
475: }
476:
477:
478: $isWritable = @file_put_contents("$dir/$uniq/_", '') !== FALSE;
479: if ($isWritable) {
480: unlink("$dir/$uniq/_");
481: }
482: rmdir("$dir/$uniq");
483: return $isWritable;
484: }
485:
486:
487: private function validate(array $config, array $expected, $name)
488: {
489: if ($extra = array_diff_key($config, $expected)) {
490: $extra = implode(", $name.", array_keys($extra));
491: throw new Nette\InvalidStateException("Unknown option $name.$extra.");
492: }
493: }
494:
495: }
496: