xref: /webtrees/app/Services/UpgradeService.php (revision a45f98897789fc9ff88705eb09ae5f037bf49c10)
1<?php
2/**
3 * webtrees: online genealogy
4 * Copyright (C) 2018 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 */
16namespace Fisharebest\Webtrees\Services;
17
18use Fisharebest\Webtrees\Database;
19use Fisharebest\Webtrees\DebugBar;
20use Fisharebest\Webtrees\Site;
21use GuzzleHttp\Client;
22use GuzzleHttp\Exception\RequestException;
23use Symfony\Component\HttpFoundation\Response;
24
25/**
26 * Automatic upgrades.
27 */
28class UpgradeService
29{
30    // Regular expression to match a version string such as "1.7.10" or "2.0.0-alpha.1"
31    const REGEX_VERSION = '\d+\.\d+\.\d+(-[a-z0-9.-]+)?';
32
33    // Only check the webtrees server infrequently.
34    const CHECK_FOR_UPDATE_INTERVAL = 24 * 60 * 60;
35
36    // Fetch information about upgrades from here.
37    // Note: earlier versions of webtrees used svn.webtrees.net, so we must maintain both URLs.
38    const UPDATE_URL = 'https://dev.webtrees.net/build/latest-version.txt';
39
40    // If the update server doesn't respond after this time, give up.
41    const HTTP_TIMEOUT = 3.0;
42
43    /**
44     * @return bool
45     */
46    public function isUpgradeAvailable(): bool
47    {
48        // If the latest version is unavailable, we will have an empty sting which equates to version 0.
49
50        return version_compare(WT_VERSION, $this->fetchLatestVersion()) < 0;
51    }
52
53    /**
54     * What is the latest version of webtrees.
55     *
56     * @return string
57     */
58    public function latestVersion(): string
59    {
60        $latest_version = $this->fetchLatestVersion();
61
62        list($version) = explode('|', $latest_version);
63
64        return $version;
65    }
66
67    /**
68     * Where can we download the latest version of webtrees.
69     *
70     * @return string
71     */
72    public function downloadUrl(): string
73    {
74        $latest_version = $this->fetchLatestVersion();
75
76        list(, , $url) = explode('|', $latest_version . '||');
77
78        return $url;
79    }
80
81    /**
82     * Check with the webtrees.net server for the latest version of webtrees.
83     * Fetching the remote file can be slow, so check infrequently, and cache the result.
84     * Pass the current versions of webtrees, PHP and MySQL, as the response
85     * may be different for each. The server logs are used to generate
86     * installation statistics which can be found at http://dev.webtrees.net/statistics.html
87     *
88     * @return string
89     */
90    private function fetchLatestVersion(): string
91    {
92        $last_update_timestamp = (int) Site::getPreference('LATEST_WT_VERSION_TIMESTAMP');
93
94        if ($last_update_timestamp < WT_TIMESTAMP - self::CHECK_FOR_UPDATE_INTERVAL) {
95            try {
96                $client = new Client([
97                    'timeout' => self::HTTP_TIMEOUT,
98                ]);
99
100                $response = $client->get(self::UPDATE_URL, [
101                    'query' => $this->serverParameters(),
102                ]);
103
104                if ($response->getStatusCode() === Response::HTTP_OK) {
105                    Site::setPreference('LATEST_WT_VERSION', $response->getBody()->getContents());
106                    Site::setPreference('LATEST_WT_VERSION_TIMESTAMP', WT_TIMESTAMP);
107                }
108            } catch (RequestException $ex) {
109                DebugBar::addThrowable($ex);
110            }
111        }
112
113        return Site::getPreference('LATEST_WT_VERSION');
114    }
115
116    /**
117     * The upgrade server needs to know a little about this server.
118     */
119    private function serverParameters()
120    {
121        $mysql_version = Database::prepare("SHOW VARIABLES LIKE 'version'")->fetchOneRow();
122
123        $operating_system = DIRECTORY_SEPARATOR === '/' ? 'u' : 'w';
124
125        return [
126            'w' => WT_VERSION,
127            'p' => PHP_VERSION,
128            'm' => $mysql_version->value,
129            'o' => $operating_system,
130        ];
131    }
132}
133