Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 96
0.00% covered (danger)
0.00%
0 / 19
CRAP
0.00% covered (danger)
0.00%
0 / 1
Bootstrap
0.00% covered (danger)
0.00%
0 / 96
0.00% covered (danger)
0.00%
0 / 19
1482
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 15
0.00% covered (danger)
0.00%
0 / 1
20
 isStarted
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isDispatched
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 isReady
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 start
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 dispatchHttp
0.00% covered (danger)
0.00%
0 / 10
0.00% covered (danger)
0.00%
0 / 1
30
 displayMaintenancePage
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
6
 dispatchCli
0.00% covered (danger)
0.00%
0 / 9
0.00% covered (danger)
0.00%
0 / 1
6
 dispatch
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
12
 catchError
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 session
0.00% covered (danger)
0.00%
0 / 8
0.00% covered (danger)
0.00%
0 / 1
20
 configureSessionHandler
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
6
 registerErrorhandler
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 setDefaultTimezone
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
12
 mvc
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
2
 scripts
0.00% covered (danger)
0.00%
0 / 11
0.00% covered (danger)
0.00%
0 / 1
6
 getMaintenanceService
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getSessionCookieService
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 buildRequest
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
1<?php
2
3/**
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License
6 * as published by the Free Software Foundation; under version 2
7 * of the License (non-upgradable).
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
17 *
18 * Copyright (c) 2002-2008 (original work) Public Research Centre Henri Tudor & University of Luxembourg
19 *                         (under the project TAO & TAO2);
20 *               2009-2012 (update and modification) Public Research Centre Henri Tudor
21 *                         (under the project TAO-SUSTAIN & TAO-DEV);
22 *               2013-2020 (update and modification) Open Assessment Technologies SA;
23 *
24 */
25
26namespace oat\tao\model\mvc;
27
28use common_exception_PreConditionFailure as PreConditionException;
29use common_exception_SystemUnderMaintenance as MaintenanceException;
30use common_ext_ExtensionsManager;
31use common_Logger;
32use common_report_Report as Report;
33use common_session_SessionManager as SessionManager;
34use Exception;
35use GuzzleHttp\Psr7\Response;
36use GuzzleHttp\Psr7\ServerRequest;
37use helpers_Report as ReportHelper;
38use oat\oatbox\event\EventAggregator;
39use oat\oatbox\service\ServiceConfigDriver;
40use oat\oatbox\service\ServiceManager;
41use oat\oatbox\service\ServiceManagerAwareInterface;
42use oat\oatbox\service\ServiceManagerAwareTrait;
43use oat\tao\helpers\Template;
44use oat\tao\model\asset\AssetService;
45use oat\tao\model\http\RequestRebuilder;
46use oat\tao\model\maintenance\Maintenance;
47use oat\tao\model\mvc\error\ExceptionInterpreterService;
48use oat\tao\model\routing\CliController;
49use oat\tao\model\routing\TaoFrontController;
50use oat\tao\model\session\Business\Contract\SessionCookieServiceInterface;
51use Psr\Http\Message\RequestInterface;
52use Request;
53use tao_helpers_Context;
54use tao_helpers_Request;
55use tao_helpers_Scriptloader as ScriptLoader;
56
57/**
58 * The Bootstrap Class enables you to drive the application flow for a given extenstion.
59 * A bootstrap instance initialize the context and starts all the services:
60 *  - session
61 *  - database
62 *  - user
63 *
64 * And it's used to disptach the Control Loop
65 *  - control the platform status (redirect to the maintenance page if it is required)
66 *  - dispatch to the convenient action
67 *  - control code exceptions
68 *
69 * @author Bertrand CHEVRIER <bertrand.chevrier@tudor.lu>
70 * @package tao
71 * @example
72 * <code>
73 *  $bootStrap = new BootStrap('tao');  //create the Bootstrap instance
74 *  $bootStrap->start();                //start all the services
75 *  $bootStrap->dispatch();             //dispatch the http request into the control loop
76 * </code>
77 */
78class Bootstrap implements ServiceManagerAwareInterface
79{
80    use ServiceManagerAwareTrait;
81
82    public const CONFIG_SESSION_HANDLER = 'session';
83
84    /**
85     * @var boolean if the context has been started
86     */
87    protected static $isStarted = false;
88
89    /**
90     * @var boolean if the context has been dispatched
91     */
92    protected static $isDispatched = false;
93
94    /**
95     * Bootstrap constructor.
96     *
97     * Initialize the context
98     *
99     * @param $configuration
100     * @throws \common_Exception If config file is not readable
101     */
102    public function __construct($configuration)
103    {
104        new DotEnvReader();
105
106        if (! is_string($configuration) || ! is_readable($configuration)) {
107            throw new PreConditionException('TAO platform seems to be not installed.');
108        }
109
110        require_once $configuration;
111        $serviceManager = new ServiceManager(
112            (new ServiceConfigDriver())->connect('config', [
113                'dir' => dirname($configuration),
114                'humanReadable' => true
115            ])
116        );
117
118        $this->setServiceLocator($serviceManager);
119        // To be removed when getServiceManager will disappear
120        ServiceManager::setServiceManager($serviceManager);
121        if (PHP_SAPI === 'cli') {
122            tao_helpers_Context::load('SCRIPT_MODE');
123        } else {
124            tao_helpers_Context::load('APP_MODE');
125        }
126    }
127
128    /**
129     * Check if the current context has been started
130     * @return boolean
131     */
132    public static function isStarted()
133    {
134        return self::$isStarted;
135    }
136
137    /**
138     * Check if the current context has been dispatched
139     * @return boolean
140     */
141    public static function isDispatched()
142    {
143        return self::$isDispatched;
144    }
145
146    /**
147     * Check if the platform is ready
148     *
149     * @return boolean Return true if the application is ready
150     */
151    protected function isReady()
152    {
153        return $this->getMaintenanceService()->isPlatformReady();
154    }
155
156    /**
157     * Start all the services:
158     *  1. Start the session
159     *  2. Update the include path
160     *  3. Include the global helpers
161     *  4. Connect the current user to the generis API
162     *  5. Initialize the internationalization
163     *  6. Check the application' state
164     */
165    public function start()
166    {
167        if (!self::$isStarted) {
168            $this->session();
169            $this->setDefaultTimezone();
170            $this->registerErrorhandler();
171            self::$isStarted = true;
172        }
173    }
174
175    protected function dispatchHttp()
176    {
177        $isAjax = tao_helpers_Request::isAjax();
178
179        if (tao_helpers_Context::check('APP_MODE')) {
180            if (!$isAjax) {
181                $this->scripts();
182            }
183        }
184
185        //Catch all exceptions
186        try {
187            //the app is ready, process mvc
188            if ($this->isReady()) {
189                $this->mvc();
190            } else { //the app is not ready, put platform on maintenance
191                $this->displayMaintenancePage();
192            }
193        } catch (Exception $e) {
194            $this->catchError($e);
195        }
196
197        // explicitly close session
198        session_write_close();
199    }
200
201    /**
202     * Put the platform on maintenance
203     * Redirect to maintenance page if http call is not ajax
204     * Otherwise throw common_exception_SystemUnderMaintenance
205     *
206     * @throws MaintenanceException
207     */
208    protected function displayMaintenancePage()
209    {
210        //the request is not an ajax request, redirect the user to the maintenance page
211        if (! tao_helpers_Request::isAjax()) {
212            require_once Template::getTemplate('error/maintenance.tpl', 'tao');
213        //else throw an exception, this exception will be send to the client properly
214        } else {
215            throw new MaintenanceException();
216        }
217    }
218
219
220    protected function dispatchCli()
221    {
222        $params = $_SERVER['argv'];
223        array_shift($params);
224
225        if (count($params) < 1) {
226            $report = new Report(Report::TYPE_ERROR, __('No action specified'));
227        } else {
228            $actionIdentifier = array_shift($params);
229            $cliController = new CliController();
230            $this->propagate($cliController);
231            $report = $cliController->runAction($actionIdentifier, $params);
232        }
233
234        echo ReportHelper::renderToCommandline($report);
235    }
236
237    /**
238     * Dispatch the current http request into the control loop:
239     *  1. Load the ressources
240     *  2. Start the MVC Loop from the ClearFW
241     *  manage Exception:
242     */
243    public function dispatch()
244    {
245        if (!self::$isDispatched) {
246            if (PHP_SAPI === 'cli') {
247                $this->dispatchCli();
248            } else {
249                $this->dispatchHttp();
250            }
251
252            $this->getServiceManager()->get(EventAggregator::SERVICE_ID)->triggerAggregatedEvents();
253
254            self::$isDispatched = true;
255        }
256    }
257
258    /**
259     * Catch any errors
260     * return a http response in function of client accepted mime type
261     *
262     * @param Exception $exception
263     */
264    protected function catchError(Exception $exception)
265    {
266        $exceptionInterpreterService = $this->getServiceLocator()->get(ExceptionInterpreterService::SERVICE_ID);
267        $interpretor = $exceptionInterpreterService->getExceptionInterpreter($exception);
268        $interpretor->getResponse()->send();
269    }
270
271    /**
272     * Start the session
273     */
274    protected function session()
275    {
276        if (PHP_SAPI === 'cli') {
277            return;
278        }
279
280        if (tao_helpers_Context::check('APP_MODE')) {
281            // Set a specific ID to the session.
282            $request = new Request();
283            if ($request->hasParameter('session_id')) {
284                session_id($request->getParameter('session_id'));
285            }
286        }
287
288        // set the session cookie to HTTP only.
289
290        $this->configureSessionHandler();
291
292        $this->getSessionCookieService()->initializeSessionCookie();
293    }
294
295    private function configureSessionHandler()
296    {
297        $sessionHandler = common_ext_ExtensionsManager::singleton()
298            ->getExtensionById('tao')
299            ->getConfig(self::CONFIG_SESSION_HANDLER);
300
301        if ($sessionHandler !== false) {
302            session_set_save_handler(
303                [$sessionHandler, 'open'],
304                [$sessionHandler, 'close'],
305                [$sessionHandler, 'read'],
306                [$sessionHandler, 'write'],
307                [$sessionHandler, 'destroy'],
308                [$sessionHandler, 'gc']
309            );
310        }
311    }
312
313    /**
314     * register a custom Errorhandler
315     */
316    protected function registerErrorhandler()
317    {
318        // register the logger as erorhandler
319        common_Logger::singleton()->register();
320    }
321
322    /**
323     * Set Timezone quickfix
324     */
325    protected function setDefaultTimezone()
326    {
327        if (function_exists("date_default_timezone_set") && defined('TIME_ZONE')) {
328            date_default_timezone_set(TIME_ZONE);
329        }
330    }
331
332    /**
333     *  Start the MVC Loop from the ClearFW
334     *  @throws \ActionEnforcingException in case of wrong module or action
335     *  @throws \tao_models_classes_UserException when a request try to acces a protected area
336     */
337    protected function mvc()
338    {
339        $request = $this->buildRequest();
340        $response = new Response();
341        $frontController = $this->propagate(new TaoFrontController());
342        $frontController($request, $response);
343    }
344
345    /**
346     * Load external resources for the current context
347     * @see \tao_helpers_Scriptloader
348     */
349    protected function scripts()
350    {
351        $assetService = $this->getServiceLocator()->get(AssetService::SERVICE_ID);
352        $cssFiles = [
353            $assetService->getAsset('css/layout.css', 'tao'),
354            $assetService->getAsset('css/tao-main-style.css', 'tao'),
355            $assetService->getAsset('css/tao-3.css', 'tao')
356        ];
357
358        //stylesheets to load
359        ScriptLoader::addCssFiles($cssFiles);
360
361        if (SessionManager::isAnonymous()) {
362            ScriptLoader::addCssFile(
363                $assetService->getAsset('css/portal.css', 'tao')
364            );
365        }
366    }
367
368    protected function getMaintenanceService(): Maintenance
369    {
370        /** @noinspection PhpIncompatibleReturnTypeInspection */
371        return $this->getServiceLocator()->get(Maintenance::SERVICE_ID);
372    }
373
374    private function getSessionCookieService(): SessionCookieServiceInterface
375    {
376        /** @noinspection PhpIncompatibleReturnTypeInspection */
377        return $this->getServiceLocator()->get(SessionCookieServiceInterface::class);
378    }
379
380    private function buildRequest(): RequestInterface
381    {
382        $request = ServerRequest::fromGlobals();
383        return (new RequestRebuilder())->rebuild($request);
384    }
385}