xref: /webtrees/index.php (revision b0c01d0c28a4ee291803e9f36bcb20e3f916c3da)
1<?php
2/**
3 * webtrees: online genealogy
4 * Copyright (C) 2019 webtrees development team
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
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 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 */
16declare(strict_types=1);
17
18use Fisharebest\Webtrees\Database;
19use Fisharebest\Webtrees\DebugBar;
20use Fisharebest\Webtrees\Exceptions\Handler;
21use Fisharebest\Webtrees\Http\Controllers\SetupController;
22use Fisharebest\Webtrees\Http\Middleware\BootModules;
23use Fisharebest\Webtrees\Http\Middleware\CheckCsrf;
24use Fisharebest\Webtrees\Http\Middleware\CheckForMaintenanceMode;
25use Fisharebest\Webtrees\Http\Middleware\DebugBarData;
26use Fisharebest\Webtrees\Http\Middleware\Housekeeping;
27use Fisharebest\Webtrees\Http\Middleware\MiddlewareInterface;
28use Fisharebest\Webtrees\Http\Middleware\UseFilesystem;
29use Fisharebest\Webtrees\Http\Middleware\UseLocale;
30use Fisharebest\Webtrees\Http\Middleware\UseSession;
31use Fisharebest\Webtrees\Http\Middleware\UseTheme;
32use Fisharebest\Webtrees\Http\Middleware\UseTransaction;
33use Fisharebest\Webtrees\Http\Middleware\UseTree;
34use Fisharebest\Webtrees\Services\MigrationService;
35use Fisharebest\Webtrees\Services\ModuleService;
36use Fisharebest\Webtrees\Services\TimeoutService;
37use Fisharebest\Webtrees\Webtrees;
38use Illuminate\Cache\ArrayStore;
39use Illuminate\Cache\Repository;
40use Symfony\Component\HttpFoundation\Request;
41use Symfony\Component\HttpFoundation\Response;
42
43require __DIR__ . '/vendor/autoload.php';
44
45const WT_ROOT = __DIR__ . DIRECTORY_SEPARATOR;
46
47Webtrees::init();
48
49// Initialise the DebugBar for development.
50// Use `composer install --dev` on a development build to enable.
51// Note that you may need to increase the size of the fcgi buffers on nginx.
52// e.g. add these lines to your fastcgi_params file:
53// fastcgi_buffers 16 16m;
54// fastcgi_buffer_size 32m;
55DebugBar::init(class_exists('\\DebugBar\\StandardDebugBar'));
56
57// Use an array cache for database calls, etc.
58app()->instance('cache.array', new Repository(new ArrayStore()));
59
60// Start the timer.
61app()->instance(TimeoutService::class, new TimeoutService(microtime(true)));
62
63// Extract the request parameters.
64$request = Request::createFromGlobals();
65app()->instance(Request::class, $request);
66
67// Calculate the base URL, so we can generate absolute URLs.
68$request_uri = $request->getSchemeAndHttpHost() . $request->getRequestUri();
69
70// Remove any PHP script name and parameters.
71$base_uri = preg_replace('/[^\/]+\.php(\?.*)?$/', '', $request_uri);
72define('WT_BASE_URL', $base_uri);
73
74try {
75    // No config file? Run the setup wizard
76    if (!file_exists(Webtrees::CONFIG_FILE)) {
77        define('WT_DATA_DIR', 'data/');
78
79        /** @var SetupController $controller */
80        $controller = app(SetupController::class);
81        $response   = $controller->setup($request);
82        $response->prepare($request)->send();
83
84        return;
85    }
86
87    $database_config = parse_ini_file(Webtrees::CONFIG_FILE);
88
89    if ($database_config === false) {
90        throw new Exception('Invalid config file: ' . Webtrees::CONFIG_FILE);
91    }
92
93    // Read the connection settings and create the database
94    Database::connect($database_config);
95
96    // Update the database schema, if necessary.
97    app(MigrationService::class)->updateSchema('\Fisharebest\Webtrees\Schema', 'WT_SCHEMA_VERSION', Webtrees::SCHEMA_VERSION);
98
99    // Middleware allows code to intercept the request before it reaches the controller, and to
100    // intercept the response afterwards.
101    //
102    //                   +----------------------------------+
103    //                   |           Middleware1            |
104    //                   | +------------------------------+ |
105    //                   | |         Middleware2          | |
106    //                   | | +--------------------------+ | |
107    //                   | | |                          | | |
108    //       Request ----|-|-|-> Controller::action() --|-|-|---> Response
109    //                   | | |                          | | |
110    //                   | | +--------------------------+ | |
111    //                   | |                              | |
112    //                   | +------------------------------+ |
113    //                   |                                  |
114    //                   +----------------------------------+
115
116    $middleware_stack = [
117        CheckForMaintenanceMode::class,
118        UseFilesystem::class,
119        UseSession::class,
120        UseTree::class,
121        UseLocale::class,
122        UseTheme::class,
123        BootModules::class,
124    ];
125
126    if (class_exists(DebugBar::class)) {
127        $middleware_stack[] = DebugBarData::class;
128    }
129
130    if ($request->getMethod() === Request::METHOD_GET) {
131        $middleware_stack[] = Housekeeping::class;
132    }
133
134    if ($request->getMethod() === Request::METHOD_POST) {
135        $middleware_stack[] = UseTransaction::class;
136        $middleware_stack[] = CheckCsrf::class;
137    }
138
139    // Allow modules to provide middleware.
140    foreach (app(ModuleService::class)->findByInterface(MiddlewareInterface::class) as $middleware) {
141        $middleware_stack[] = $middleware;
142    }
143
144    // We build the pipeline from controller outwards, so process the last middleware first.
145
146    $middleware_stack = array_reverse($middleware_stack);
147
148    // Construct the core middleware *after* loading the modules, to reduce dependencies.
149
150    $middleware_stack = array_map(function ($middleware): MiddlewareInterface {
151        return $middleware instanceof MiddlewareInterface ? $middleware : app($middleware);
152    }, $middleware_stack);
153
154    // Create a pipleline, which applies the middleware as a nested function call.
155    //
156    // Response = Middleware1(Middleware2(Controller::action(Request)))
157
158    $pipeline = array_reduce($middleware_stack, function (Closure $next, MiddlewareInterface $middleware): Closure {
159        // Create a closure to apply the middleware.
160        return function (Request $request) use ($middleware, $next): Response {
161            return $middleware->handle($request, $next);
162        };
163    }, function (Request $request): Response {
164        // Load the route and routing table.
165        $route  = $request->get('route');
166        $routes = require 'routes/web.php';
167
168        // Find the controller and action for the selected route
169        $controller_action = $routes[$request->getMethod() . ':' . $route] ?? 'ErrorController@noRouteFound';
170        [$controller_name, $action] = explode('@', $controller_action);
171        $controller_class = '\\Fisharebest\\Webtrees\\Http\\Controllers\\' . $controller_name;
172
173        $controller = app($controller_class);
174
175        return app()->dispatch($controller, $action);
176    });
177
178    $response = $pipeline($request);
179} catch (Throwable $exception) {
180    $response = (new Handler())->render($request, $exception);
181}
182
183// Send response
184$response->prepare($request)->send();
185