xref: /webtrees/app/Services/ServerCheckService.php (revision 0b5fd0a636fa959f5279ee28ebd2f27e921c091e)
1b7059dccSGreg Roach<?php
2b7059dccSGreg Roach/**
3b7059dccSGreg Roach * webtrees: online genealogy
4b7059dccSGreg Roach * Copyright (C) 2019 webtrees development team
5b7059dccSGreg Roach * This program is free software: you can redistribute it and/or modify
6b7059dccSGreg Roach * it under the terms of the GNU General Public License as published by
7b7059dccSGreg Roach * the Free Software Foundation, either version 3 of the License, or
8b7059dccSGreg Roach * (at your option) any later version.
9b7059dccSGreg Roach * This program is distributed in the hope that it will be useful,
10b7059dccSGreg Roach * but WITHOUT ANY WARRANTY; without even the implied warranty of
11b7059dccSGreg Roach * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12b7059dccSGreg Roach * GNU General Public License for more details.
13b7059dccSGreg Roach * You should have received a copy of the GNU General Public License
14b7059dccSGreg Roach * along with this program. If not, see <http://www.gnu.org/licenses/>.
15b7059dccSGreg Roach */
16b7059dccSGreg Roachdeclare(strict_types=1);
17b7059dccSGreg Roach
18b7059dccSGreg Roachnamespace Fisharebest\Webtrees\Services;
19b7059dccSGreg Roach
20b7059dccSGreg Roachuse Fisharebest\Webtrees\I18N;
21b7059dccSGreg Roachuse Illuminate\Support\Collection;
22b7059dccSGreg Roachuse Illuminate\Support\Str;
23b7059dccSGreg Roachuse SQLite3;
24b7059dccSGreg Roachuse function array_map;
25b7059dccSGreg Roachuse function explode;
26b7059dccSGreg Roachuse function extension_loaded;
27b7059dccSGreg Roachuse function in_array;
28b7059dccSGreg Roachuse function ini_get;
297bb10f9aSGreg Roachuse function strtolower;
30b7059dccSGreg Roachuse function sys_get_temp_dir;
31b7059dccSGreg Roachuse function trim;
32b7059dccSGreg Roachuse function version_compare;
33b7059dccSGreg Roachuse const PATH_SEPARATOR;
34b7059dccSGreg Roachuse const PHP_MAJOR_VERSION;
35b7059dccSGreg Roachuse const PHP_MINOR_VERSION;
36b7059dccSGreg Roach
37b7059dccSGreg Roach/**
38b7059dccSGreg Roach * Check if the server meets the minimum requirements for webtrees.
39b7059dccSGreg Roach */
40b7059dccSGreg Roachclass ServerCheckService
41b7059dccSGreg Roach{
42bb5a472eSGreg Roach    private const PHP_SUPPORT_URL   = 'https://secure.php.net/supported-versions.php';
43bb5a472eSGreg Roach    private const PHP_MINOR_VERSION = PHP_MAJOR_VERSION . '.' . PHP_MINOR_VERSION;
44bb5a472eSGreg Roach    private const PHP_SUPPORT_DATES = [
45b7059dccSGreg Roach        '7.1' => '2019-12-01',
46b7059dccSGreg Roach        '7.2' => '2020-11-30',
47b7059dccSGreg Roach        '7.3' => '2021-12-06',
48b7059dccSGreg Roach    ];
49b7059dccSGreg Roach
50b7059dccSGreg Roach    // As required by illuminate/database 5.8
51b7059dccSGreg Roach    private const MINIMUM_SQLITE_VERSION = '3.7.11';
52b7059dccSGreg Roach
53b7059dccSGreg Roach    /**
54b7059dccSGreg Roach     * Things that may cause webtrees to break.
55b7059dccSGreg Roach     *
56b7059dccSGreg Roach     * @param string $driver
57b7059dccSGreg Roach     *
5854c7f8dfSGreg Roach     * @return Collection
5954c7f8dfSGreg Roach     * @return string[]
60b7059dccSGreg Roach     */
61b7059dccSGreg Roach    public function serverErrors($driver = ''): Collection
62b7059dccSGreg Roach    {
63b7059dccSGreg Roach        $errors = Collection::make([
64b7059dccSGreg Roach            $this->databaseDriverErrors($driver),
65b7059dccSGreg Roach            $this->checkPhpExtension('mbstring'),
66b7059dccSGreg Roach            $this->checkPhpExtension('iconv'),
67b7059dccSGreg Roach            $this->checkPhpExtension('pcre'),
68b7059dccSGreg Roach            $this->checkPhpExtension('session'),
69b7059dccSGreg Roach            $this->checkPhpExtension('xml'),
70b7059dccSGreg Roach            $this->checkPhpFunction('parse_ini_file'),
71b7059dccSGreg Roach        ]);
72b7059dccSGreg Roach
73b7059dccSGreg Roach        return $errors
74b7059dccSGreg Roach            ->flatten()
75b7059dccSGreg Roach            ->filter();
76b7059dccSGreg Roach    }
77b7059dccSGreg Roach
78b7059dccSGreg Roach    /**
79b7059dccSGreg Roach     * Things that should be fixed, but which won't stop completely webtrees from running.
80b7059dccSGreg Roach     *
81b7059dccSGreg Roach     * @param string $driver
82b7059dccSGreg Roach     *
8354c7f8dfSGreg Roach     * @return Collection
8454c7f8dfSGreg Roach     * @return string[]
85b7059dccSGreg Roach     */
86b7059dccSGreg Roach    public function serverWarnings($driver = ''): Collection
87b7059dccSGreg Roach    {
88b7059dccSGreg Roach        $warnings = Collection::make([
89b7059dccSGreg Roach            $this->databaseDriverWarnings($driver),
90b7059dccSGreg Roach            $this->checkPhpExtension('curl'),
91b7059dccSGreg Roach            $this->checkPhpExtension('gd'),
92b7059dccSGreg Roach            $this->checkPhpExtension('simplexml'),
93b7059dccSGreg Roach            $this->checkPhpIni('file_uploads', true),
94b7059dccSGreg Roach            $this->checkSystemTemporaryFolder(),
95b7059dccSGreg Roach            $this->checkPhpVersion(),
96b7059dccSGreg Roach        ]);
97b7059dccSGreg Roach
98b7059dccSGreg Roach        return $warnings
99b7059dccSGreg Roach            ->flatten()
100b7059dccSGreg Roach            ->filter();
101b7059dccSGreg Roach    }
102b7059dccSGreg Roach
103b7059dccSGreg Roach    /**
104b7059dccSGreg Roach     * Check if a PHP extension is loaded.
105b7059dccSGreg Roach     *
106b7059dccSGreg Roach     * @param string $extension
107b7059dccSGreg Roach     *
108b7059dccSGreg Roach     * @return string
109b7059dccSGreg Roach     */
110b7059dccSGreg Roach    private function checkPhpExtension(string $extension): string
111b7059dccSGreg Roach    {
112b7059dccSGreg Roach        if (!extension_loaded($extension)) {
113b7059dccSGreg Roach            return I18N::translate('The PHP extension “%s” is not installed.', $extension);
114b7059dccSGreg Roach        }
115b7059dccSGreg Roach
116b7059dccSGreg Roach        return '';
117b7059dccSGreg Roach    }
118b7059dccSGreg Roach
119b7059dccSGreg Roach    /**
120b7059dccSGreg Roach     * Check if a PHP setting is correct.
121b7059dccSGreg Roach     *
122b7059dccSGreg Roach     * @param string $varname
123b7059dccSGreg Roach     * @param bool   $expected
124b7059dccSGreg Roach     *
125b7059dccSGreg Roach     * @return string
126b7059dccSGreg Roach     */
127b7059dccSGreg Roach    private function checkPhpIni(string $varname, bool $expected): string
128b7059dccSGreg Roach    {
129b7059dccSGreg Roach        $ini_get = (bool) ini_get($varname);
130b7059dccSGreg Roach
131b7059dccSGreg Roach        if ($expected && $ini_get !== $expected) {
132b7059dccSGreg Roach            return I18N::translate('The PHP.INI setting “%1$s” is disabled.', $varname);
133b7059dccSGreg Roach        }
134b7059dccSGreg Roach
135b7059dccSGreg Roach        if (!$expected && $ini_get !== $expected) {
136b7059dccSGreg Roach            return I18N::translate('The PHP.INI setting “%1$s” is enabled.', $varname);
137b7059dccSGreg Roach        }
138b7059dccSGreg Roach
139b7059dccSGreg Roach        return '';
140b7059dccSGreg Roach    }
141b7059dccSGreg Roach
142b7059dccSGreg Roach    /**
1437bb10f9aSGreg Roach     * Check if a PHP function is in the list of disabled functions.
1447bb10f9aSGreg Roach     *
1457bb10f9aSGreg Roach     * @param string $function
1467bb10f9aSGreg Roach     *
1477d99559cSGreg Roach     * @return bool
1487bb10f9aSGreg Roach     */
1497bb10f9aSGreg Roach    public function isFunctionDisabled(string $function): bool
1507bb10f9aSGreg Roach    {
1517bb10f9aSGreg Roach        $disable_functions = explode(',', ini_get('disable_functions'));
152*0b5fd0a6SGreg Roach        $disable_functions = array_map(static function (string $func): string {
153e364afe4SGreg Roach            return strtolower(trim($func));
1547bb10f9aSGreg Roach        }, $disable_functions);
1557bb10f9aSGreg Roach
1567bb10f9aSGreg Roach        $function = strtolower($function);
1577bb10f9aSGreg Roach
1587bb10f9aSGreg Roach        return in_array($function, $disable_functions, true) || !function_exists($function);
1597bb10f9aSGreg Roach    }
1607bb10f9aSGreg Roach
1617bb10f9aSGreg Roach    /**
1627bb10f9aSGreg Roach     * Create a warning message for a disabled function.
163b7059dccSGreg Roach     *
164b7059dccSGreg Roach     * @param string $function
165b7059dccSGreg Roach     *
166b7059dccSGreg Roach     * @return string
167b7059dccSGreg Roach     */
168b7059dccSGreg Roach    private function checkPhpFunction(string $function): string
169b7059dccSGreg Roach    {
1707bb10f9aSGreg Roach        if ($this->isFunctionDisabled($function)) {
171acf70b2aSGreg Roach            return I18N::translate('The PHP function “%1$s” is disabled.', $function . '()');
172b7059dccSGreg Roach        }
173b7059dccSGreg Roach
174b7059dccSGreg Roach        return '';
175b7059dccSGreg Roach    }
176b7059dccSGreg Roach
177b7059dccSGreg Roach    /**
178b7059dccSGreg Roach     * Some servers configure their temporary folder in an unaccessible place.
179b7059dccSGreg Roach     */
180b7059dccSGreg Roach    private function checkPhpVersion(): string
181b7059dccSGreg Roach    {
182b7059dccSGreg Roach        $today = date('Y-m-d');
183b7059dccSGreg Roach
184b7059dccSGreg Roach        foreach (self::PHP_SUPPORT_DATES as $version => $end_date) {
185b7059dccSGreg Roach            if (version_compare(self::PHP_MINOR_VERSION, $version) <= 0 && $today > $end_date) {
186b7059dccSGreg Roach                return I18N::translate('Your web server is using PHP version %s, which is no longer receiving security updates. You should upgrade to a later version as soon as possible.', PHP_VERSION) . ' <a href="' . e(self::PHP_SUPPORT_URL) . '">' . e(self::PHP_SUPPORT_URL) . '</a>';
187b7059dccSGreg Roach            }
188b7059dccSGreg Roach        }
189b7059dccSGreg Roach
190b7059dccSGreg Roach        return '';
191b7059dccSGreg Roach    }
192b7059dccSGreg Roach
193b7059dccSGreg Roach    /**
194b7059dccSGreg Roach     * Check the
195b7059dccSGreg Roach     *
196b7059dccSGreg Roach     * @return string
197b7059dccSGreg Roach     */
198b7059dccSGreg Roach    private function checkSqliteVersion(): string
199b7059dccSGreg Roach    {
200b7059dccSGreg Roach        if (class_exists(SQLite3::class)) {
201b7059dccSGreg Roach            $sqlite_version = SQLite3::version()['versionString'];
202b7059dccSGreg Roach
203b7059dccSGreg Roach            if (version_compare($sqlite_version, self::MINIMUM_SQLITE_VERSION) < 0) {
204b7059dccSGreg Roach                return I18N::translate('SQLite version %s is installed. SQLite version %s or later is required.', $sqlite_version, self::MINIMUM_SQLITE_VERSION);
205b7059dccSGreg Roach            }
206b7059dccSGreg Roach        }
207b7059dccSGreg Roach
208b7059dccSGreg Roach        return '';
209b7059dccSGreg Roach    }
210b7059dccSGreg Roach
211b7059dccSGreg Roach    /**
212b7059dccSGreg Roach     * Some servers configure their temporary folder in an unaccessible place.
213b7059dccSGreg Roach     */
214b7059dccSGreg Roach    private function checkSystemTemporaryFolder(): string
215b7059dccSGreg Roach    {
216b7059dccSGreg Roach        $open_basedir  = ini_get('open_basedir');
217b7059dccSGreg Roach        $open_basedirs = explode(PATH_SEPARATOR, $open_basedir);
218b7059dccSGreg Roach        $sys_temp_dir  = sys_get_temp_dir();
219b7059dccSGreg Roach
220b7059dccSGreg Roach        if ($open_basedir === '' || Str::startsWith($sys_temp_dir, $open_basedirs)) {
221b7059dccSGreg Roach            return '';
222b7059dccSGreg Roach        }
223b7059dccSGreg Roach
224b7059dccSGreg Roach        $message = I18N::translate('The server’s temporary folder cannot be accessed.');
225b7059dccSGreg Roach        $message .= '<br>sys_get_temp_dir() = "' . e($sys_temp_dir) . '"';
226b7059dccSGreg Roach        $message .= '<br>ini_get("open_basedir") = "' . e($open_basedir) . '"';
227b7059dccSGreg Roach
228b7059dccSGreg Roach        return $message;
229b7059dccSGreg Roach    }
230b7059dccSGreg Roach
231b7059dccSGreg Roach    /**
232b7059dccSGreg Roach     * @param string $driver
233b7059dccSGreg Roach     *
234b7059dccSGreg Roach     * @return Collection
235b7059dccSGreg Roach     */
236b7059dccSGreg Roach    private function databaseDriverErrors(string $driver): Collection
237b7059dccSGreg Roach    {
238b7059dccSGreg Roach        switch ($driver) {
239b7059dccSGreg Roach            case 'mysql':
240b7059dccSGreg Roach                return Collection::make([
241b7059dccSGreg Roach                    $this->checkPhpExtension('pdo'),
242b7059dccSGreg Roach                    $this->checkPhpExtension('pdo_mysql'),
243b7059dccSGreg Roach                ]);
244b7059dccSGreg Roach
245b7059dccSGreg Roach            case 'sqlite':
246b7059dccSGreg Roach                return Collection::make([
247b7059dccSGreg Roach                    $this->checkPhpExtension('pdo'),
248b7059dccSGreg Roach                    $this->checkPhpExtension('sqlite3'),
249b7059dccSGreg Roach                    $this->checkPhpExtension('pdo_sqlite'),
250b7059dccSGreg Roach                    $this->checkSqliteVersion(),
251b7059dccSGreg Roach                ]);
252b7059dccSGreg Roach
253b7059dccSGreg Roach            case 'pgsql':
254b7059dccSGreg Roach                return Collection::make([
255b7059dccSGreg Roach                    $this->checkPhpExtension('pdo'),
256b7059dccSGreg Roach                    $this->checkPhpExtension('pdo_pgsql'),
257b7059dccSGreg Roach                ]);
258b7059dccSGreg Roach
259b7059dccSGreg Roach            case 'sqlsvr':
260b7059dccSGreg Roach                return Collection::make([
261b7059dccSGreg Roach                    $this->checkPhpExtension('pdo'),
262b7059dccSGreg Roach                    $this->checkPhpExtension('pdo_odbc'),
263b7059dccSGreg Roach                ]);
264b7059dccSGreg Roach
265b7059dccSGreg Roach            default:
266b7059dccSGreg Roach                return new Collection();
267b7059dccSGreg Roach        }
268b7059dccSGreg Roach    }
269b7059dccSGreg Roach
270b7059dccSGreg Roach    /**
271b7059dccSGreg Roach     * @param string $driver
272b7059dccSGreg Roach     *
273b7059dccSGreg Roach     * @return Collection
274b7059dccSGreg Roach     */
275b7059dccSGreg Roach    private function databaseDriverWarnings(string $driver): Collection
276b7059dccSGreg Roach    {
277b7059dccSGreg Roach        switch ($driver) {
278b7059dccSGreg Roach            case 'sqlite':
279b7059dccSGreg Roach                return new Collection([
280b7059dccSGreg Roach                    I18N::translate('SQLite is only suitable for small sites, testing and evaluation.'),
281b7059dccSGreg Roach                ]);
282b7059dccSGreg Roach
283b7059dccSGreg Roach            case 'pgsql':
284b7059dccSGreg Roach                return new Collection([
285b7059dccSGreg Roach                    I18N::translate('Support for PostgreSQL is experimental.'),
286b7059dccSGreg Roach                ]);
287b7059dccSGreg Roach
288b7059dccSGreg Roach            case 'sqlsvr':
289b7059dccSGreg Roach                return new Collection([
290b7059dccSGreg Roach                    I18N::translate('Support for SQL Server is experimental.'),
291b7059dccSGreg Roach                ]);
292b7059dccSGreg Roach
293b7059dccSGreg Roach            default:
294b7059dccSGreg Roach                return new Collection();
295b7059dccSGreg Roach        }
296b7059dccSGreg Roach    }
297b7059dccSGreg Roach}
298