]>
Commit | Line | Data |
---|---|---|
2e28269b | 1 | <?php |
9778a155 V |
2 | namespace Shaarli; |
3 | ||
4 | use Exception; | |
5 | use Shaarli\Config\ConfigManager; | |
6 | ||
2e28269b V |
7 | /** |
8 | * Shaarli (application) utilities | |
9 | */ | |
10 | class ApplicationUtils | |
11 | { | |
b786c883 A |
12 | /** |
13 | * @var string File containing the current version | |
14 | */ | |
15 | public static $VERSION_FILE = 'shaarli_version.php'; | |
16 | ||
4bf35ba5 | 17 | private static $GIT_URL = 'https://raw.githubusercontent.com/shaarli/Shaarli'; |
b897c81f | 18 | private static $GIT_BRANCHES = array('latest', 'stable'); |
4bf35ba5 V |
19 | private static $VERSION_START_TAG = '<?php /* '; |
20 | private static $VERSION_END_TAG = ' */ ?>'; | |
21 | ||
22 | /** | |
23 | * Gets the latest version code from the Git repository | |
24 | * | |
25 | * The code is read from the raw content of the version file on the Git server. | |
26 | * | |
7af9a418 A |
27 | * @param string $url URL to reach to get the latest version. |
28 | * @param int $timeout Timeout to check the URL (in seconds). | |
29 | * | |
4bf35ba5 V |
30 | * @return mixed the version code from the repository if available, else 'false' |
31 | */ | |
f211e417 | 32 | public static function getLatestGitVersionCode($url, $timeout = 2) |
4bf35ba5 | 33 | { |
1557cefb | 34 | list($headers, $data) = get_http_response($url, $timeout); |
4bf35ba5 V |
35 | |
36 | if (strpos($headers[0], '200 OK') === false) { | |
37 | error_log('Failed to retrieve ' . $url); | |
38 | return false; | |
39 | } | |
40 | ||
b786c883 A |
41 | return $data; |
42 | } | |
43 | ||
44 | /** | |
45 | * Retrieve the version from a remote URL or a file. | |
46 | * | |
47 | * @param string $remote URL or file to fetch. | |
48 | * @param int $timeout For URLs fetching. | |
49 | * | |
50 | * @return bool|string The version or false if it couldn't be retrieved. | |
51 | */ | |
52 | public static function getVersion($remote, $timeout = 2) | |
53 | { | |
54 | if (startsWith($remote, 'http')) { | |
55 | if (($data = static::getLatestGitVersionCode($remote, $timeout)) === false) { | |
56 | return false; | |
57 | } | |
58 | } else { | |
9778a155 | 59 | if (!is_file($remote)) { |
b786c883 A |
60 | return false; |
61 | } | |
62 | $data = file_get_contents($remote); | |
63 | } | |
64 | ||
4bf35ba5 V |
65 | return str_replace( |
66 | array(self::$VERSION_START_TAG, self::$VERSION_END_TAG, PHP_EOL), | |
67 | array('', '', ''), | |
68 | $data | |
69 | ); | |
70 | } | |
71 | ||
72 | /** | |
73 | * Checks if a new Shaarli version has been published on the Git repository | |
74 | * | |
75 | * Updates checks are run periodically, according to the following criteria: | |
76 | * - the update checks are enabled (install, global config); | |
77 | * - the user is logged in (or this is an open instance); | |
78 | * - the last check is older than a given interval; | |
79 | * - the check is non-blocking if the HTTPS connection to Git fails; | |
80 | * - in case of failure, the update file's modification date is updated, | |
81 | * to avoid intempestive connection attempts. | |
82 | * | |
83 | * @param string $currentVersion the current version code | |
84 | * @param string $updateFile the file where to store the latest version code | |
85 | * @param int $checkInterval the minimum interval between update checks (in seconds | |
86 | * @param bool $enableCheck whether to check for new versions | |
87 | * @param bool $isLoggedIn whether the user is logged in | |
7af9a418 | 88 | * @param string $branch check update for the given branch |
4bf35ba5 | 89 | * |
4a7af975 V |
90 | * @throws Exception an invalid branch has been set for update checks |
91 | * | |
4bf35ba5 V |
92 | * @return mixed the new version code if available and greater, else 'false' |
93 | */ | |
f211e417 V |
94 | public static function checkUpdate( |
95 | $currentVersion, | |
96 | $updateFile, | |
97 | $checkInterval, | |
98 | $enableCheck, | |
99 | $isLoggedIn, | |
100 | $branch = 'stable' | |
101 | ) { | |
b897c81f A |
102 | // Do not check versions for visitors |
103 | // Do not check if the user doesn't want to | |
104 | // Do not check with dev version | |
9778a155 | 105 | if (!$isLoggedIn || empty($enableCheck) || $currentVersion === 'dev') { |
4bf35ba5 V |
106 | return false; |
107 | } | |
108 | ||
109 | if (is_file($updateFile) && (filemtime($updateFile) > time() - $checkInterval)) { | |
110 | // Shaarli has checked for updates recently - skip HTTP query | |
111 | $latestKnownVersion = file_get_contents($updateFile); | |
112 | ||
113 | if (version_compare($latestKnownVersion, $currentVersion) == 1) { | |
114 | return $latestKnownVersion; | |
115 | } | |
116 | return false; | |
117 | } | |
118 | ||
9778a155 | 119 | if (!in_array($branch, self::$GIT_BRANCHES)) { |
4407b45f V |
120 | throw new Exception( |
121 | 'Invalid branch selected for updates: "' . $branch . '"' | |
122 | ); | |
123 | } | |
124 | ||
4bf35ba5 V |
125 | // Late Static Binding allows overriding within tests |
126 | // See http://php.net/manual/en/language.oop5.late-static-bindings.php | |
b786c883 | 127 | $latestVersion = static::getVersion( |
4407b45f | 128 | self::$GIT_URL . '/' . $branch . '/' . self::$VERSION_FILE |
4bf35ba5 V |
129 | ); |
130 | ||
9778a155 | 131 | if (!$latestVersion) { |
4bf35ba5 V |
132 | // Only update the file's modification date |
133 | file_put_contents($updateFile, $currentVersion); | |
134 | return false; | |
135 | } | |
136 | ||
137 | // Update the file's content and modification date | |
138 | file_put_contents($updateFile, $latestVersion); | |
139 | ||
140 | if (version_compare($latestVersion, $currentVersion) == 1) { | |
141 | return $latestVersion; | |
142 | } | |
143 | ||
144 | return false; | |
145 | } | |
2e28269b | 146 | |
c9cf2715 V |
147 | /** |
148 | * Checks the PHP version to ensure Shaarli can run | |
149 | * | |
150 | * @param string $minVersion minimum PHP required version | |
151 | * @param string $curVersion current PHP version (use PHP_VERSION) | |
152 | * | |
def39d0d A |
153 | * @return bool true on success |
154 | * | |
c9cf2715 V |
155 | * @throws Exception the PHP version is not supported |
156 | */ | |
157 | public static function checkPHPVersion($minVersion, $curVersion) | |
158 | { | |
159 | if (version_compare($curVersion, $minVersion) < 0) { | |
12266213 | 160 | $msg = t( |
c9cf2715 | 161 | 'Your PHP version is obsolete!' |
9778a155 V |
162 | . ' Shaarli requires at least PHP %s, and thus cannot run.' |
163 | . ' Your PHP version has known security vulnerabilities and should be' | |
164 | . ' updated as soon as possible.' | |
c9cf2715 | 165 | ); |
12266213 | 166 | throw new Exception(sprintf($msg, $minVersion)); |
c9cf2715 | 167 | } |
def39d0d | 168 | return true; |
c9cf2715 V |
169 | } |
170 | ||
2e28269b V |
171 | /** |
172 | * Checks Shaarli has the proper access permissions to its resources | |
173 | * | |
278d9ee2 A |
174 | * @param ConfigManager $conf Configuration Manager instance. |
175 | * | |
2e28269b V |
176 | * @return array A list of the detected configuration issues |
177 | */ | |
278d9ee2 | 178 | public static function checkResourcePermissions($conf) |
2e28269b V |
179 | { |
180 | $errors = array(); | |
e4325b15 | 181 | $rainTplDir = rtrim($conf->get('resource.raintpl_tpl'), '/'); |
2e28269b V |
182 | |
183 | // Check script and template directories are readable | |
184 | foreach (array( | |
9778a155 V |
185 | 'application', |
186 | 'inc', | |
187 | 'plugins', | |
188 | $rainTplDir, | |
189 | $rainTplDir . '/' . $conf->get('resource.theme'), | |
190 | ) as $path) { | |
191 | if (!is_readable(realpath($path))) { | |
192 | $errors[] = '"' . $path . '" ' . t('directory is not readable'); | |
2e28269b V |
193 | } |
194 | } | |
195 | ||
7af9a418 | 196 | // Check cache and data directories are readable and writable |
2e28269b | 197 | foreach (array( |
9778a155 V |
198 | $conf->get('resource.thumbnails_cache'), |
199 | $conf->get('resource.data_dir'), | |
200 | $conf->get('resource.page_cache'), | |
201 | $conf->get('resource.raintpl_tmp'), | |
202 | ) as $path) { | |
203 | if (!is_readable(realpath($path))) { | |
204 | $errors[] = '"' . $path . '" ' . t('directory is not readable'); | |
2e28269b | 205 | } |
9778a155 V |
206 | if (!is_writable(realpath($path))) { |
207 | $errors[] = '"' . $path . '" ' . t('directory is not writable'); | |
2e28269b V |
208 | } |
209 | } | |
210 | ||
7af9a418 | 211 | // Check configuration files are readable and writable |
2e28269b | 212 | foreach (array( |
9778a155 V |
213 | $conf->getConfigFileExt(), |
214 | $conf->get('resource.datastore'), | |
215 | $conf->get('resource.ban_file'), | |
216 | $conf->get('resource.log'), | |
217 | $conf->get('resource.update_check'), | |
218 | ) as $path) { | |
219 | if (!is_file(realpath($path))) { | |
2e28269b V |
220 | # the file may not exist yet |
221 | continue; | |
222 | } | |
223 | ||
9778a155 V |
224 | if (!is_readable(realpath($path))) { |
225 | $errors[] = '"' . $path . '" ' . t('file is not readable'); | |
2e28269b | 226 | } |
9778a155 V |
227 | if (!is_writable(realpath($path))) { |
228 | $errors[] = '"' . $path . '" ' . t('file is not writable'); | |
2e28269b V |
229 | } |
230 | } | |
231 | ||
232 | return $errors; | |
233 | } | |
bfe4f536 A |
234 | |
235 | /** | |
236 | * Returns a salted hash representing the current Shaarli version. | |
237 | * | |
238 | * Useful for assets browser cache. | |
239 | * | |
240 | * @param string $currentVersion of Shaarli | |
241 | * @param string $salt User personal salt, also used for the authentication | |
242 | * | |
243 | * @return string version hash | |
244 | */ | |
245 | public static function getVersionHash($currentVersion, $salt) | |
246 | { | |
247 | return hash_hmac('sha256', $currentVersion, $salt); | |
248 | } | |
2e28269b | 249 | } |