aboutsummaryrefslogtreecommitdiffhomepage
path: root/application/ApplicationUtils.php
blob: 85dcbeebdb164858680ff68b9fbc1048340d05f1 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
<?php
/**
 * Shaarli (application) utilities
 */
class ApplicationUtils
{
    /**
     * @var string File containing the current version
     */
    public static $VERSION_FILE = 'shaarli_version.php';

    private static $GIT_URL = 'https://raw.githubusercontent.com/shaarli/Shaarli';
    private static $GIT_BRANCHES = array('latest', 'stable');
    private static $VERSION_START_TAG = '<?php /* ';
    private static $VERSION_END_TAG = ' */ ?>';

    /**
     * Gets the latest version code from the Git repository
     *
     * The code is read from the raw content of the version file on the Git server.
     *
     * @param string $url     URL to reach to get the latest version.
     * @param int    $timeout Timeout to check the URL (in seconds).
     *
     * @return mixed the version code from the repository if available, else 'false'
     */
    public static function getLatestGitVersionCode($url, $timeout=2)
    {
        list($headers, $data) = get_http_response($url, $timeout);

        if (strpos($headers[0], '200 OK') === false) {
            error_log('Failed to retrieve ' . $url);
            return false;
        }

        return $data;
    }

    /**
     * Retrieve the version from a remote URL or a file.
     *
     * @param string $remote  URL or file to fetch.
     * @param int    $timeout For URLs fetching.
     *
     * @return bool|string The version or false if it couldn't be retrieved.
     */
    public static function getVersion($remote, $timeout = 2)
    {
        if (startsWith($remote, 'http')) {
            if (($data = static::getLatestGitVersionCode($remote, $timeout)) === false) {
                return false;
            }
        } else {
            if (! is_file($remote)) {
                return false;
            }
            $data = file_get_contents($remote);
        }

        return str_replace(
            array(self::$VERSION_START_TAG, self::$VERSION_END_TAG, PHP_EOL),
            array('', '', ''),
            $data
        );
    }

    /**
     * Checks if a new Shaarli version has been published on the Git repository
     *
     * Updates checks are run periodically, according to the following criteria:
     * - the update checks are enabled (install, global config);
     * - the user is logged in (or this is an open instance);
     * - the last check is older than a given interval;
     * - the check is non-blocking if the HTTPS connection to Git fails;
     * - in case of failure, the update file's modification date is updated,
     *   to avoid intempestive connection attempts.
     *
     * @param string $currentVersion the current version code
     * @param string $updateFile     the file where to store the latest version code
     * @param int    $checkInterval  the minimum interval between update checks (in seconds
     * @param bool   $enableCheck    whether to check for new versions
     * @param bool   $isLoggedIn     whether the user is logged in
     * @param string $branch         check update for the given branch
     *
     * @throws Exception an invalid branch has been set for update checks
     *
     * @return mixed the new version code if available and greater, else 'false'
     */
    public static function checkUpdate($currentVersion,
                                       $updateFile,
                                       $checkInterval,
                                       $enableCheck,
                                       $isLoggedIn,
                                       $branch='stable')
    {
        // Do not check versions for visitors
        // Do not check if the user doesn't want to
        // Do not check with dev version
        if (! $isLoggedIn || empty($enableCheck) || $currentVersion === 'dev') {
            return false;
        }

        if (is_file($updateFile) && (filemtime($updateFile) > time() - $checkInterval)) {
            // Shaarli has checked for updates recently - skip HTTP query
            $latestKnownVersion = file_get_contents($updateFile);

            if (version_compare($latestKnownVersion, $currentVersion) == 1) {
                return $latestKnownVersion;
            }
            return false;
        }

        if (! in_array($branch, self::$GIT_BRANCHES)) {
            throw new Exception(
                'Invalid branch selected for updates: "' . $branch . '"'
            );
        }

        // Late Static Binding allows overriding within tests
        // See http://php.net/manual/en/language.oop5.late-static-bindings.php
        $latestVersion = static::getVersion(
            self::$GIT_URL . '/' . $branch . '/' . self::$VERSION_FILE
        );

        if (! $latestVersion) {
            // Only update the file's modification date
            file_put_contents($updateFile, $currentVersion);
            return false;
        }

        // Update the file's content and modification date
        file_put_contents($updateFile, $latestVersion);

        if (version_compare($latestVersion, $currentVersion) == 1) {
            return $latestVersion;
        }

        return false;
    }

    /**
     * Checks the PHP version to ensure Shaarli can run
     *
     * @param string $minVersion minimum PHP required version
     * @param string $curVersion current PHP version (use PHP_VERSION)
     *
     * @throws Exception the PHP version is not supported
     */
    public static function checkPHPVersion($minVersion, $curVersion)
    {
        if (version_compare($curVersion, $minVersion) < 0) {
            throw new Exception(
                'Your PHP version is obsolete!'
                .' Shaarli requires at least PHP '.$minVersion.', and thus cannot run.'
                .' Your PHP version has known security vulnerabilities and should be'
                .' updated as soon as possible.'
            );
        }
    }

    /**
     * Checks Shaarli has the proper access permissions to its resources
     *
     * @param ConfigManager $conf Configuration Manager instance.
     *
     * @return array A list of the detected configuration issues
     */
    public static function checkResourcePermissions($conf)
    {
        $errors = array();

        // Check script and template directories are readable
        foreach (array(
            'application',
            'inc',
            'plugins',
            $conf->get('resource.raintpl_tpl'),
            $conf->get('resource.raintpl_tpl').'/'.$conf->get('resource.theme'),
        ) as $path) {
            if (! is_readable(realpath($path))) {
                $errors[] = '"'.$path.'" directory is not readable';
            }
        }

        // Check cache and data directories are readable and writable
        foreach (array(
            $conf->get('resource.thumbnails_cache'),
            $conf->get('resource.data_dir'),
            $conf->get('resource.page_cache'),
            $conf->get('resource.raintpl_tmp'),
        ) as $path) {
            if (! is_readable(realpath($path))) {
                $errors[] = '"'.$path.'" directory is not readable';
            }
            if (! is_writable(realpath($path))) {
                $errors[] = '"'.$path.'" directory is not writable';
            }
        }

        // Check configuration files are readable and writable
        foreach (array(
            $conf->getConfigFileExt(),
            $conf->get('resource.datastore'),
            $conf->get('resource.ban_file'),
            $conf->get('resource.log'),
            $conf->get('resource.update_check'),
        ) as $path) {
            if (! is_file(realpath($path))) {
                # the file may not exist yet
                continue;
            }

            if (! is_readable(realpath($path))) {
                $errors[] = '"'.$path.'" file is not readable';
            }
            if (! is_writable(realpath($path))) {
                $errors[] = '"'.$path.'" file is not writable';
            }
        }

        return $errors;
    }
}