1<?php
2/**
3 * EGroupware API - Check for updates
4 *
5 * @link http://www.egroupware.org
6 * @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
7 * @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
8 * @package api
9 * @subpackage framework
10 * @access public
11 */
12
13namespace EGroupware\Api\Framework;
14
15use EGroupware\Api\Html;
16use EGroupware\Api\Cache;
17use EGroupware\Api;
18
19/**
20 * Check for updates
21 *
22 * https://www.egroupware.org/currentversion
23 *
24 * Contains multiple lines with version numbers:
25 * 1. current stable version      eg. 17.1.20180118
26 * 2. last stable security update eg. 17.1.20180118
27 * 3. last old-stable security up.eg. 16.1.20171106 (only if that is still secure!)
28 * 4. further old secure versions, if available
29 */
30class Updates
31{
32	/**
33	 * URL to check for security or maintenance updates
34	 */
35	const CURRENT_VERSION_URL = 'https://www.egroupware.org/currentversion';
36	/**
37	 * How long to cache (in secs) / often to check for updates
38	 */
39	const VERSIONS_CACHE_TIMEOUT = 7200;
40	/**
41	 * After how many days of not applied security updates, start warning non-admins too
42	 */
43	const WARN_USERS_DAYS = 5;
44
45	/**
46	 * Get versions of available updates
47	 *
48	 * @param string $api =null major api version to return security for, default latest
49	 * @return array verions for keys "current" and "security"
50	 */
51	public static function available($api=null)
52	{
53		$versions = Cache::getTree(__CLASS__, 'versions', function() use ($api)
54		{
55			$versions = array();
56			$security = null;
57			if (($remote = file_get_contents(self::CURRENT_VERSION_URL, false, Api\Framework::proxy_context())))
58			{
59				$all_versions = explode("\n", $remote);
60				$current = array_shift($all_versions);
61				if (empty($all_versions)) $all_versions = array($current);
62				// find latest security release for optional API version
63				foreach(array_reverse($all_versions) as $security)
64				{
65					if (isset($api) && $api === substr($security, 0, strlen($api))) break;
66				}
67				$versions = array(
68					'current'  => $current,		// last maintenance update
69					'security' => $security,	// last security update
70				);
71			}
72			return $versions;
73		}, array(), self::VERSIONS_CACHE_TIMEOUT);
74
75		//error_log(__METHOD__."($api) returning ".array2string($versions));
76		return $versions;
77	}
78
79	/**
80	 * Check update status
81	 *
82	 * @return string
83	 * @todo Check from client-side, if server-side check fails
84	 */
85	public static function notification()
86	{
87		$api = preg_replace('/ ?EPL$/', '', self::api_version());
88		$api_major = $matches = null;
89		if (preg_match('/^(\d+\.\d+)\./', $api, $matches))
90		{
91			$api_major = $matches[1];
92		}
93
94		$versions = self::available($api_major);
95
96		if ($versions)
97		{
98			if (version_compare($api, $versions['security'], '<'))
99			{
100				if (!$GLOBALS['egw_info']['user']['apps']['admin'] && !self::update_older($versions['security'], self::WARN_USERS_DAYS))
101				{
102					return null;
103				}
104				return Html::a_href(Html::image('api', 'security-update', lang('EGroupware security update %1 needs to be installed!', $versions['security'])),
105					'http://www.egroupware.org/changelog', null, ' target="_blank"');
106			}
107			if ($GLOBALS['egw_info']['user']['apps']['admin'] && version_compare($api, $versions['current'], '<'))
108			{
109				$msg = substr($versions['current'], 0, strlen($api_major)) == $api_major ?
110					lang('EGroupware maintenance update %1 available', $versions['current']) :
111					lang('New EGroupware release %1 available', $versions['current']);
112				return Html::a_href(Html::image('api', 'update', $msg),
113					'http://www.egroupware.org/changelog', null, ' target="_blank"');
114			}
115		}
116		elseif ($GLOBALS['egw_info']['user']['apps']['admin'])
117		{
118			$error = lang('Automatic update check failed, you need to check manually!');
119			if (!ini_get('allow_url_fopen'))
120			{
121				$error .= "\n".lang('%1 setting "%2" = %3 disallows access via http!',
122					'php.ini', 'allow_url_fopen', array2string(ini_get('allow_url_fopen')));
123			}
124			return Html::a_href(Html::image('api', 'update', $error),
125				'http://www.egroupware.org/changelog', null, ' target="_blank" data-api-version="'.$api.'"');
126		}
127		return null;
128	}
129
130	/**
131	 * Check if version is older then $days days
132	 *
133	 * @param string $version eg. "14.1.20140715" last part is checked (only if > 20140000!)
134	 * @param int $days
135	 * @return boolean
136	 */
137	protected static function update_older($version, $days)
138	{
139		list(,,$date) = explode('.', $version);
140		if ($date < 20140000) return false;
141		$version_timestamp = mktime(0, 0, 0, (int)substr($date, 4, 2), (int)substr($date, -2), (int)substr($date, 0, 4));
142
143		return (time() - $version_timestamp) / 86400 > $days;
144	}
145
146	/**
147	 * Get current API version from api/setup/setup.inc.php "maintenance_release" or database, whichever is bigger
148	 *
149	 * @param string &$changelog on return path to changelog
150	 * @return string
151	 */
152	public static function api_version(&$changelog=null)
153	{
154		$changelog = EGW_SERVER_ROOT.'/doc/rpm-build/debian.changes';
155
156		return Cache::getTree(__CLASS__, 'api_version', function()
157		{
158			$version = preg_replace('/[^0-9.]/', '', $GLOBALS['egw_info']['server']['versions']['api']);
159
160			if (empty($GLOBALS['egw_info']['server']['versions']['maintenance_release']))
161			{
162				$setup_info = null;
163				include (EGW_SERVER_ROOT.'/api/setup/setup.inc.php');
164				$GLOBALS['egw_info']['server']['versions'] += $setup_info['api']['versions'];
165				unset($setup_info);
166			}
167			if (version_compare($version, $GLOBALS['egw_info']['server']['versions']['maintenance_release'], '<'))
168			{
169				$version = $GLOBALS['egw_info']['server']['versions']['maintenance_release'];
170			}
171			return $version;
172		}, array(), 300);
173	}
174}
175