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