xref: /webtrees/index.php (revision b5979037de52280b1023c9eb58518a3089e7d267)
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\Auth;
19use Fisharebest\Webtrees\Contracts\UserInterface;
20use Fisharebest\Webtrees\Database;
21use Fisharebest\Webtrees\DebugBar;
22use Fisharebest\Webtrees\Exceptions\Handler;
23use Fisharebest\Webtrees\Http\Controllers\SetupController;
24use Fisharebest\Webtrees\Http\Middleware\CheckCsrf;
25use Fisharebest\Webtrees\Http\Middleware\CheckForMaintenanceMode;
26use Fisharebest\Webtrees\Http\Middleware\DebugBarData;
27use Fisharebest\Webtrees\Http\Middleware\Housekeeping;
28use Fisharebest\Webtrees\Http\Middleware\MiddlewareInterface;
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\I18N;
34use Fisharebest\Webtrees\Services\MigrationService;
35use Fisharebest\Webtrees\Services\ModuleService;
36use Fisharebest\Webtrees\Services\TimeoutService;
37use Fisharebest\Webtrees\Site;
38use Fisharebest\Webtrees\Tree;
39use Fisharebest\Webtrees\View;
40use Fisharebest\Webtrees\Webtrees;
41use Illuminate\Cache\ArrayStore;
42use Illuminate\Cache\Repository;
43use League\Flysystem\Adapter\Local;
44use League\Flysystem\Cached\CachedAdapter;
45use League\Flysystem\Cached\Storage\Memory;
46use League\Flysystem\Filesystem;
47use Symfony\Component\HttpFoundation\Request;
48use Symfony\Component\HttpFoundation\Response;
49
50require __DIR__ . '/vendor/autoload.php';
51
52const WT_ROOT = __DIR__ . DIRECTORY_SEPARATOR;
53
54Webtrees::init();
55
56// Initialise the DebugBar for development.
57// Use `composer install --dev` on a development build to enable.
58// Note that you may need to increase the size of the fcgi buffers on nginx.
59// e.g. add these lines to your fastcgi_params file:
60// fastcgi_buffers 16 16m;
61// fastcgi_buffer_size 32m;
62DebugBar::init(class_exists('\\DebugBar\\StandardDebugBar'));
63
64// Use an array cache for database calls, etc.
65app()->instance('cache.array', new Repository(new ArrayStore()));
66
67// Extract the request parameters.
68$request = Request::createFromGlobals();
69app()->instance(Request::class, $request);
70
71// Dummy value, until we have created our first tree.
72app()->bind(Tree::class, function () {
73    return null;
74});
75
76// Calculate the base URL, so we can generate absolute URLs.
77$request_uri = $request->getSchemeAndHttpHost() . $request->getRequestUri();
78
79// Remove any PHP script name and parameters.
80$base_uri = preg_replace('/[^\/]+\.php(\?.*)?$/', '', $request_uri);
81define('WT_BASE_URL', $base_uri);
82
83DebugBar::startMeasure('init database');
84
85// Connect to the database
86try {
87    // No config file? Run the setup wizard
88    if (!file_exists(Webtrees::CONFIG_FILE)) {
89        define('WT_DATA_DIR', 'data/');
90        /** @var SetupController $controller */
91        $controller = app()->make(SetupController::class);
92        $response   = $controller->setup($request);
93        $response->prepare($request)->send();
94
95        return;
96    }
97
98    $database_config = parse_ini_file(Webtrees::CONFIG_FILE);
99
100    if ($database_config === false) {
101        throw new Exception('Invalid config file: ' . Webtrees::CONFIG_FILE);
102    }
103
104    // Read the connection settings and create the database
105    Database::connect($database_config);
106
107    // Update the database schema, if necessary.
108    app()->make(MigrationService::class)
109        ->updateSchema('\Fisharebest\Webtrees\Schema', 'WT_SCHEMA_VERSION', Webtrees::SCHEMA_VERSION);
110} catch (PDOException $exception) {
111    defined('WT_DATA_DIR') || define('WT_DATA_DIR', 'data/');
112    I18N::init();
113    if ($exception->getCode() === 1045) {
114        // Error during connection?
115        $content = view('errors/database-connection', ['error' => $exception->getMessage()]);
116    } else {
117        // Error in a migration script?
118        $content = view('errors/database-error', ['error' => $exception->getMessage()]);
119    }
120    $html     = view('layouts/error', ['content' => $content]);
121    $response = new Response($html, Response::HTTP_SERVICE_UNAVAILABLE);
122    $response->prepare($request)->send();
123
124    return;
125} catch (Throwable $exception) {
126    defined('WT_DATA_DIR') || define('WT_DATA_DIR', 'data/');
127    I18N::init();
128    $content  = view('errors/database-connection', ['error' => $exception->getMessage()]);
129    $html     = view('layouts/error', ['content' => $content]);
130    $response = new Response($html, Response::HTTP_SERVICE_UNAVAILABLE);
131    $response->prepare($request)->send();
132
133    return;
134}
135
136DebugBar::stopMeasure('init database');
137
138// The config.ini.php file must always be in a fixed location.
139// Other user files can be stored elsewhere...
140define('WT_DATA_DIR', realpath(Site::getPreference('INDEX_DIRECTORY', 'data/')) . DIRECTORY_SEPARATOR);
141
142$filesystem = new Filesystem(new CachedAdapter(new Local(WT_DATA_DIR), new Memory()));
143
144// Request more resources - if we can/want to
145$memory_limit = Site::getPreference('MEMORY_LIMIT');
146if ($memory_limit !== '' && strpos(ini_get('disable_functions'), 'ini_set') === false) {
147    ini_set('memory_limit', $memory_limit);
148}
149$max_execution_time = Site::getPreference('MAX_EXECUTION_TIME');
150if ($max_execution_time !== '' && strpos(ini_get('disable_functions'), 'set_time_limit') === false) {
151    set_time_limit((int) $max_execution_time);
152}
153
154try {
155    // Most requests will need the current tree and user.
156    $tree = Tree::findByName($request->get('ged')) ?? null;
157
158    // No tree specified/available?  Choose one.
159    if ($tree === null && $request->getMethod() === Request::METHOD_GET) {
160        $tree = Tree::findByName(Site::getPreference('DEFAULT_GEDCOM')) ?? array_values(Tree::getAll())[0] ?? null;
161    }
162
163    // Most layouts will require a tree for the page header/footer
164    View::share('tree', $tree);
165
166    app()->instance(Tree::class, $tree);
167    app()->instance(UserInterface::class, Auth::user());
168    app()->instance(TimeoutService::class, new TimeoutService(microtime(true)));
169    app()->instance(Filesystem::class, $filesystem);
170
171    $middleware_stack = [
172        CheckForMaintenanceMode::class,
173        UseSession::class,
174        UseLocale::class,
175    ];
176
177    if (class_exists(DebugBar::class)) {
178        $middleware_stack[] = DebugBarData::class;
179    }
180
181    if ($request->getMethod() === Request::METHOD_GET) {
182        $middleware_stack[] = Housekeeping::class;
183        $middleware_stack[] = UseTheme::class;
184    }
185
186    if ($request->getMethod() === Request::METHOD_POST) {
187        $middleware_stack[] = UseTransaction::class;
188        $middleware_stack[] = CheckCsrf::class;
189    }
190
191    // Allow modules to provide middleware.
192    foreach (app()->make(ModuleService::class)->findByInterface(MiddlewareInterface::class) as $middleware) {
193        $middleware_stack[] = get_class($middleware);
194    }
195
196    // We build the "onion" from the inside outwards, and some middleware (e.g. UseTheme) is dependant on others (e.g. UseLocale)
197    $middleware_stack = array_reverse($middleware_stack);
198
199    // Apply the middleware using the "onion" pattern.
200    $pipeline = array_reduce($middleware_stack, function (Closure $next, string $middleware): Closure {
201        // Create a closure to apply the middleware.
202        return function (Request $request) use ($middleware, $next): Response {
203            return app()->make($middleware)->handle($request, $next);
204        };
205    }, function (Request $request): Response {
206        // Load the route and routing table.
207        $route  = $request->get('route');
208        $routes = require 'routes/web.php';
209
210        // Find the controller and action for the selected route
211        $controller_action = $routes[$request->getMethod() . ':' . $route] ?? 'ErrorController@noRouteFound';
212        [$controller_name, $action] = explode('@', $controller_action);
213        $controller_class = '\\Fisharebest\\Webtrees\\Http\\Controllers\\' . $controller_name;
214
215        $controller = app()->make($controller_class);
216
217        return app()->dispatch($controller, $action);
218    });
219
220    $response = call_user_func($pipeline, $request);
221} catch (Exception $exception) {
222    $response = (new Handler())->render($request, $exception);
223}
224
225// Send response
226$response->prepare($request)->send();
227