Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
80.56% covered (warning)
80.56%
87 / 108
57.14% covered (warning)
57.14%
8 / 14
CRAP
0.00% covered (danger)
0.00%
0 / 1
Bootstrap
80.56% covered (warning)
80.56%
87 / 108
57.14% covered (warning)
57.14%
8 / 14
33.76
0.00% covered (danger)
0.00%
0 / 1
 init
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 getInstance
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 configureAppStatics
50.00% covered (danger)
50.00%
1 / 2
0.00% covered (danger)
0.00%
0 / 1
2.50
 configureLocale
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 parseDebugLevel
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 configureLogger
42.86% covered (danger)
42.86%
9 / 21
0.00% covered (danger)
0.00%
0 / 1
4.68
 configureSlim
95.83% covered (success)
95.83%
23 / 24
0.00% covered (danger)
0.00%
0 / 1
1
 getTwigView
84.62% covered (warning)
84.62%
11 / 13
0.00% covered (danger)
0.00%
0 / 1
4.06
 readCacheDir
81.82% covered (warning)
81.82%
9 / 11
0.00% covered (danger)
0.00%
0 / 1
4.10
 addTwigExtension
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 addTwigFilter
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 addTwigTemplateDirectory
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 loadRouting
66.67% covered (warning)
66.67%
6 / 9
0.00% covered (danger)
0.00%
0 / 1
3.33
 buildContainer
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2
3namespace BO\Slim;
4
5use App;
6use Monolog\Formatter\JsonFormatter;
7use Monolog\Handler\StreamHandler;
8use Monolog\Logger;
9use Slim\HttpCache\CacheProvider;
10use BO\Slim\Factory\ResponseFactory;
11use BO\Slim\Factory\ServerRequestFactory;
12use Slim\Views\Twig;
13use Twig\Extension\DebugExtension;
14use Twig\Loader\FilesystemLoader;
15use Psr\Log\LoggerInterface;
16
17/**
18 * @SuppressWarnings(Coupling)
19 * Bootstrapping connects the classes, so coupling should be ignored
20 *
21 */
22
23class Bootstrap
24{
25    protected static $instance = null;
26
27    public static function init()
28    {
29        Profiler::init();
30        $bootstrap = self::getInstance();
31        $bootstrap->configureAppStatics();
32        $bootstrap->configureLogger(App::DEBUGLEVEL, App::IDENTIFIER);
33        $bootstrap->configureSlim();
34        $bootstrap->configureLocale();
35        Profiler::add("Init");
36    }
37
38    public static function getInstance()
39    {
40        self::$instance = (self::$instance instanceof Bootstrap) ? self::$instance : new self();
41        return self::$instance;
42    }
43
44    protected function configureAppStatics()
45    {
46        if (getenv('ZMS_URL_SIGNATURE_KEY') !== false) {
47            App::$urlSignatureSecret = getenv('ZMS_URL_SIGNATURE_KEY');
48        }
49    }
50
51    protected function configureLocale(
52        $charset = App::CHARSET,
53        $timezone = App::TIMEZONE
54    ) {
55        ini_set('default_charset', $charset);
56        date_default_timezone_set($timezone);
57        mb_internal_encoding($charset);
58        App::$now = (! App::$now) ? new \DateTimeImmutable() : App::$now;
59    }
60
61    protected static $debuglevels = array(
62        'DEBUG'     => Logger::DEBUG,
63        'INFO'      => Logger::INFO,
64        'NOTICE'    => Logger::NOTICE,
65        'WARNING'   => Logger::WARNING,
66        'ERROR'     => Logger::ERROR,
67        'CRITICAL'  => Logger::CRITICAL,
68        'ALERT'     => Logger::ALERT,
69        'EMERGENCY' => Logger::EMERGENCY,
70    );
71
72    protected function parseDebugLevel($level)
73    {
74        return isset(static::$debuglevels[$level]) ? static::$debuglevels[$level] : static::$debuglevels['DEBUG'];
75    }
76
77    protected function configureLogger(string $level, string $identifier): void
78    {
79        App::$log = new Logger($identifier);
80        $level = $this->parseDebugLevel($level);
81        $handler = new StreamHandler('php://stderr', $level);
82
83        $formatter = new JsonFormatter();
84
85        // Add processor to format time_local first
86        App::$log->pushProcessor(function ($record) {
87            return array(
88                'time_local' => (new \DateTime())->format('Y-m-d\TH:i:sP'),
89                'client_ip' => $_SERVER['REMOTE_ADDR'] ?? '',
90                'remote_addr' => $_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? '',
91                'remote_user' => '',
92                'application' => defined('\\App::IDENTIFIER') ? App::IDENTIFIER : 'zms',
93                'module' => defined('\\App::MODULE_NAME') ? App::MODULE_NAME : 'zmsslim',
94                'message' => $record['message'],
95                'level' => $record['level_name'],
96                'context' => $record['context'],
97                'extra' => $record['extra']
98            );
99        });
100
101        $handler->setFormatter($formatter);
102        App::$log->pushHandler($handler);
103
104        App::$log = App::$log;
105    }
106
107    protected function configureSlim()
108    {
109        $container = $this->buildContainer();
110
111        // instantiate slim
112        App::$slim = new SlimApp(
113            new ResponseFactory(),
114            $container
115        );
116        App::$slim->determineBasePath();
117
118        $container->set('router', App::$slim->getRouteCollector());
119
120        // Configure caching
121        App::$slim->add(new \Slim\HttpCache\Cache('public', 300));
122        App::$slim->add(new Middleware\Validator());
123        App::$slim->add('BO\Slim\Middleware\Route:getInfo');
124        App::$slim->addRoutingMiddleware();
125        App::$slim->add(new Middleware\Profiler());
126        App::$slim->add(new Middleware\IpAddress(true, true));
127        App::$slim->add(new Middleware\ZmsSlimRequest());
128        App::$slim->add(new Middleware\TrailingSlash());
129
130        $errorMiddleware = App::$slim->addErrorMiddleware(App::DEBUG, App::LOG_ERRORS, App::LOG_DETAILS, App::$log);
131        $container->set('errorMiddleware', $errorMiddleware);
132
133        self::addTwigExtension(new TwigExtensionsAndFilter(
134            $container
135        ));
136        self::addTwigExtension(new DebugExtension());
137
138        App::$slim->get('__noroute', function () {
139            throw new \Exception('Route missing');
140        })->setName('noroute');
141    }
142
143    public static function getTwigView(): Twig
144    {
145        $customTemplatesPath = 'custom_templates/';
146        $templatePaths = (is_array(App::TEMPLATE_PATH)) ? App::TEMPLATE_PATH : [App::APP_PATH  . App::TEMPLATE_PATH];
147
148
149        if (getenv("ZMS_CUSTOM_TEMPLATES_PATH")) {
150            $customTemplatesPath = getenv("ZMS_CUSTOM_TEMPLATES_PATH");
151        }
152
153        if (is_dir($customTemplatesPath)) {
154            array_unshift($templatePaths, $customTemplatesPath);
155        }
156
157        return new Twig(
158            new FilesystemLoader($templatePaths),
159            [
160                'cache' => self::readCacheDir(),
161                'debug' => App::DEBUG,
162            ]
163        );
164    }
165
166    public static function readCacheDir()
167    {
168        $path = false;
169        if (App::TWIG_CACHE) {
170            $path = App::APP_PATH . App::TWIG_CACHE;
171            $userinfo = posix_getpwuid(posix_getuid());
172            $user = $userinfo['name'];
173            $githead = Git::readCurrentHash();
174            $path .= ($githead) ? '/' . $user . $githead . '/' : '/' . $user . '/';
175            if (!is_dir($path)) {
176                mkdir($path);
177                chmod($path, 0777);
178            }
179        }
180        return $path;
181    }
182
183    public static function addTwigExtension($extension)
184    {
185        /** @var Twig $twig */
186        $twig = App::$slim->getContainer()->get('view');
187        $twig->addExtension($extension);
188    }
189
190    public static function addTwigFilter($filter)
191    {
192        $twig = App::$slim->getContainer()->get('view');
193        $twig->getEnvironment()->addFilter($filter);
194    }
195
196    public static function addTwigTemplateDirectory($namespace, $path)
197    {
198        $twig = App::$slim->getContainer()->get('view');
199        $loader = $twig->getLoader();
200        $loader->addPath($path, $namespace);
201    }
202
203    public static function loadRouting($filename)
204    {
205        $container = App::$slim->getContainer();
206        $cacheFile = static::readCacheDir();
207        if ($cacheFile) {
208            $cacheFile = $cacheFile . '/routing.cache';
209            try {
210                $container['router']->setCacheFile($cacheFile);
211            } catch (\Exception $exception) {
212                error_log("Could not write Router-Cache-File: $cacheFile");
213                throw $exception;
214            }
215        }
216        require($filename);
217    }
218
219    /**
220     * @return Container
221     */
222    protected function buildContainer(): Container
223    {
224        $container = new Container();
225        $container->set('debug', App::DEBUG);
226        $container->set('cache', new CacheProvider());
227        $container->set('settings', []);
228
229        // configure slim views with twig
230        $container->set('view', self::getTwigView());
231
232        $container->set('request', ServerRequestFactory::createFromGlobals());
233
234        return $container;
235    }
236}