3 namespace Shaarli\Helper
;
6 use Shaarli\Config\ConfigManager
;
9 * Shaarli (application) utilities
11 class ApplicationUtils
14 * @var string File containing the current version
16 public static $VERSION_FILE = 'shaarli_version.php';
18 public static $GITHUB_URL = 'https://github.com/shaarli/Shaarli';
19 public static $GIT_RAW_URL = 'https://raw.githubusercontent.com/shaarli/Shaarli';
20 public static $GIT_BRANCHES = ['latest', 'stable'];
21 private static $VERSION_START_TAG = '<?php /* ';
22 private static $VERSION_END_TAG = ' */ ?>';
25 * Gets the latest version code from the Git repository
27 * The code is read from the raw content of the version file on the Git server.
29 * @param string $url URL to reach to get the latest version.
30 * @param int $timeout Timeout to check the URL (in seconds).
32 * @return mixed the version code from the repository if available, else 'false'
34 public static function getLatestGitVersionCode($url, $timeout = 2)
36 list($headers, $data) = get_http_response($url, $timeout);
38 if (strpos($headers[0], '200 OK') === false) {
39 error_log('Failed to retrieve ' . $url);
47 * Retrieve the version from a remote URL or a file.
49 * @param string $remote URL or file to fetch.
50 * @param int $timeout For URLs fetching.
52 * @return bool|string The version or false if it couldn't be retrieved.
54 public static function getVersion($remote, $timeout = 2)
56 if (startsWith($remote, 'http')) {
57 if (($data = static::getLatestGitVersionCode($remote, $timeout)) === false) {
61 if (!is_file($remote)) {
64 $data = file_get_contents($remote);
68 [self
::$VERSION_START_TAG, self
::$VERSION_END_TAG, PHP_EOL
],
75 * Checks if a new Shaarli version has been published on the Git repository
77 * Updates checks are run periodically, according to the following criteria:
78 * - the update checks are enabled (install, global config);
79 * - the user is logged in (or this is an open instance);
80 * - the last check is older than a given interval;
81 * - the check is non-blocking if the HTTPS connection to Git fails;
82 * - in case of failure, the update file's modification date is updated,
83 * to avoid intempestive connection attempts.
85 * @param string $currentVersion the current version code
86 * @param string $updateFile the file where to store the latest version code
87 * @param int $checkInterval the minimum interval between update checks (in seconds
88 * @param bool $enableCheck whether to check for new versions
89 * @param bool $isLoggedIn whether the user is logged in
90 * @param string $branch check update for the given branch
92 * @throws Exception an invalid branch has been set for update checks
94 * @return mixed the new version code if available and greater, else 'false'
96 public static function checkUpdate(
104 // Do not check versions for visitors
105 // Do not check if the user doesn't want to
106 // Do not check with dev version
107 if (!$isLoggedIn || empty($enableCheck) || $currentVersion === 'dev') {
111 if (is_file($updateFile) && (filemtime($updateFile) > time() - $checkInterval)) {
112 // Shaarli has checked for updates recently - skip HTTP query
113 $latestKnownVersion = file_get_contents($updateFile);
115 if (version_compare($latestKnownVersion, $currentVersion) == 1) {
116 return $latestKnownVersion;
121 if (!in_array($branch, self
::$GIT_BRANCHES)) {
123 'Invalid branch selected for updates: "' . $branch . '"'
127 // Late Static Binding allows overriding within tests
128 // See http://php.net/manual/en/language.oop5.late-static-bindings.php
129 $latestVersion = static::getVersion(
130 self
::$GIT_RAW_URL . '/' . $branch . '/' . self
::$VERSION_FILE
133 if (!$latestVersion) {
134 // Only update the file's modification date
135 file_put_contents($updateFile, $currentVersion);
139 // Update the file's content and modification date
140 file_put_contents($updateFile, $latestVersion);
142 if (version_compare($latestVersion, $currentVersion) == 1) {
143 return $latestVersion;
150 * Checks the PHP version to ensure Shaarli can run
152 * @param string $minVersion minimum PHP required version
153 * @param string $curVersion current PHP version (use PHP_VERSION)
155 * @return bool true on success
157 * @throws Exception the PHP version is not supported
159 public static function checkPHPVersion($minVersion, $curVersion)
161 if (version_compare($curVersion, $minVersion) < 0) {
163 'Your PHP version is obsolete!'
164 . ' Shaarli requires at least PHP %s, and thus cannot run.'
165 . ' Your PHP version has known security vulnerabilities and should be'
166 . ' updated as soon as possible.'
168 throw new Exception(sprintf($msg, $minVersion));
174 * Checks Shaarli has the proper access permissions to its resources
176 * @param ConfigManager $conf Configuration Manager instance.
177 * @param bool $minimalMode In minimal mode we only check permissions to be able to display a template.
178 * Currently we only need to be able to read the theme and write in raintpl cache.
180 * @return array A list of the detected configuration issues
182 public static function checkResourcePermissions(ConfigManager
$conf, bool $minimalMode = false): array
185 $rainTplDir = rtrim($conf->get('resource.raintpl_tpl'), '/');
187 // Check script and template directories are readable
194 $rainTplDir . '/' . $conf->get('resource.theme'),
197 if (!is_readable(realpath($path))) {
198 $errors[] = '"' . $path . '" ' . t('directory is not readable');
202 // Check cache and data directories are readable and writable
205 $conf->get('resource.raintpl_tmp'),
209 $conf->get('resource.thumbnails_cache'),
210 $conf->get('resource.data_dir'),
211 $conf->get('resource.page_cache'),
212 $conf->get('resource.raintpl_tmp'),
216 foreach ($folders as $path) {
217 if (!is_readable(realpath($path))) {
218 $errors[] = '"' . $path . '" ' . t('directory is not readable');
220 if (!is_writable(realpath($path))) {
221 $errors[] = '"' . $path . '" ' . t('directory is not writable');
229 // Check configuration files are readable and writable
232 $conf->getConfigFileExt(),
233 $conf->get('resource.datastore'),
234 $conf->get('resource.ban_file'),
235 $conf->get('resource.log'),
236 $conf->get('resource.update_check'),
239 if (!is_file(realpath($path))) {
240 # the file may not exist yet
244 if (!is_readable(realpath($path))) {
245 $errors[] = '"' . $path . '" ' . t('file is not readable');
247 if (!is_writable(realpath($path))) {
248 $errors[] = '"' . $path . '" ' . t('file is not writable');
256 * Returns a salted hash representing the current Shaarli version.
258 * Useful for assets browser cache.
260 * @param string $currentVersion of Shaarli
261 * @param string $salt User personal salt, also used for the authentication
263 * @return string version hash
265 public static function getVersionHash($currentVersion, $salt)
267 return hash_hmac('sha256', $currentVersion, $salt);
271 * Get a list of PHP extensions used by Shaarli.
273 * @return array[] List of extension with following keys:
274 * - name: extension name
275 * - required: whether the extension is required to use Shaarli
276 * - desc: short description of extension usage in Shaarli
277 * - loaded: whether the extension is properly loaded or not
279 public static function getPhpExtensionsRequirement(): array
282 ['name' => 'json', 'required' => true, 'desc' => t('Configuration parsing')],
283 ['name' => 'simplexml', 'required' => true, 'desc' => t('Slim Framework (routing, etc.)')],
284 ['name' => 'mbstring', 'required' => true, 'desc' => t('Multibyte (Unicode) string support')],
285 ['name' => 'gd', 'required' => false, 'desc' => t('Required to use thumbnails')],
286 ['name' => 'intl', 'required' => false, 'desc' => t('Localized text sorting (e.g. e->รจ->f)')],
287 ['name' => 'curl', 'required' => false, 'desc' => t('Better retrieval of bookmark metadata and thumbnail')],
288 ['name' => 'gettext', 'required' => false, 'desc' => t('Use the translation system in gettext mode')],
289 ['name' => 'ldap', 'required' => false, 'desc' => t('Login using LDAP server')],
292 foreach ($extensions as &$extension) {
293 $extension['loaded'] = extension_loaded($extension['name']);
300 * Return the EOL date of given PHP version. If the version is unknown,
301 * we return today + 2 years.
303 * @param string $fullVersion PHP version, e.g. 7.4.7
305 * @return string Date format: YYYY-MM-DD
307 public static function getPhpEol(string $fullVersion): string
309 preg_match('/(\d+\.\d+)\.\d+/', $fullVersion, $matches);
312 '7.1' => '2019-12-01',
313 '7.2' => '2020-11-30',
314 '7.3' => '2021-12-06',
315 '7.4' => '2022-11-28',
316 '8.0' => '2023-12-01',
317 ][$matches[1]] ?? (new \
DateTime('+2 year'))->format('Y-m-d');