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