Passed
Pull Request — master (#47)
by
unknown
02:00
created

RequirementsChecker::getServerInfo()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 1
nc 2
nop 0
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Yiisoft\Requirements;
6
7
/**
8
 * YiiRequirementChecker allows checking, if current system meets the requirements for running the Yii application.
9
 * This class allows rendering of the check report for the web and console application interface.
10
 *
11
 * Example:
12
 *
13
 * ```php
14
 * require_once 'path/to/YiiRequirementChecker.php';
15
 * $requirementsChecker = new YiiRequirementChecker();
16
 * $requirements = array(
17
 *     array(
18
 *         'name' => 'PHP Some Extension',
19
 *         'mandatory' => true`,
20
 *         'condition' => extension_loaded('some_extension'),
21
 *         'by' => 'Some application feature',
22
 *         'memo' => 'PHP extension "some_extension" required',
23
 *     ),
24
 * );
25
 * $requirementsChecker->checkYii()->check($requirements)->render();
26
 * ```
27
 *
28
 * If you wish to render the report with your own representation, use [[getResult()]] instead of [[render()]]
29
 *
30
 * Requirement condition could be in format "eval:PHP expression".
31
 * In this case specified PHP expression will be evaluated in the context of this class instance.
32
 * For example:
33
 *
34
 * ```php
35
 * $requirements = array(
36
 *     array(
37
 *         'name' => 'Upload max file size',
38
 *         'condition' => 'eval:$this->checkUploadMaxFileSize("5M")',
39
 *     ),
40
 * );
41
 * ```
42
 *
43
 * Note: this class definition does not match ordinary Yii style, because it should match PHP 4.3
44
 * and should not use features from newer PHP versions!
45
 *
46
 * @property array|null $result the check results, this property is for internal usage only.
47
 */
48
class RequirementsChecker
49
{
50
    /**
51
     * Check the given requirements, collecting results into internal field.
52
     * This method can be invoked several times checking different requirement sets.
53
     * Use [[getResult()]] or [[render()]] to get the results.
54
     * @param array|string $requirements requirements to be checked.
55
     * If an array, it is treated as the set of requirements;
56
     * If a string, it is treated as the path of the file, which contains the requirements;
57
     * @return $this self instance.
58
     */
59 3
    public function check($requirements)
60
    {
61 3
        if (version_compare(PHP_VERSION, '4.3', '<')) {
62
            echo 'At least PHP 4.3 is required to run this script!';
63
            exit(1);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
64
        }
65 3
        if (is_string($requirements)) {
66
            $requirements = require $requirements;
67
        }
68 3
        if (!is_array($requirements)) {
69
            $this->usageError('Requirements must be an array, "' . gettype($requirements) . '" has been given!');
70
        }
71 3
        if (!isset($this->result) || !is_array($this->result)) {
72 3
            $this->result = array(
73
                'summary' => array(
74
                    'total' => 0,
75
                    'errors' => 0,
76
                    'warnings' => 0,
77
                ),
78
                'requirements' => array(),
79
            );
80
        }
81 3
        foreach ($requirements as $key => $rawRequirement) {
82 3
            $requirement = $this->normalizeRequirement($rawRequirement, $key);
83 3
            $this->result['summary']['total']++;
84 3
            if (!$requirement['condition']) {
85 2
                if ($requirement['mandatory']) {
86 2
                    $requirement['error'] = true;
87 2
                    $requirement['warning'] = true;
88 2
                    $this->result['summary']['errors']++;
89
                } else {
90 1
                    $requirement['error'] = false;
91 1
                    $requirement['warning'] = true;
92 2
                    $this->result['summary']['warnings']++;
93
                }
94
            } else {
95 3
                $requirement['error'] = false;
96 3
                $requirement['warning'] = false;
97
            }
98 3
            $this->result['requirements'][] = $requirement;
99
        }
100
101 3
        return $this;
102
    }
103
104
    /**
105
     * Performs the check for the Yii core requirements.
106
     * @return RequirementsChecker self instance.
107
     */
108
    public function checkYii()
109
    {
110
        return $this->check(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'requirements.php');
111
    }
112
113
    /**
114
     * Return the check results.
115
     * @return array|null check results in format:
116
     *
117
     * ```php
118
     * array(
119
     *     'summary' => array(
120
     *         'total' => total number of checks,
121
     *         'errors' => number of errors,
122
     *         'warnings' => number of warnings,
123
     *     ),
124
     *     'requirements' => array(
125
     *         array(
126
     *             ...
127
     *             'error' => is there an error,
128
     *             'warning' => is there a warning,
129
     *         ),
130
     *         ...
131
     *     ),
132
     * )
133
     * ```
134
     */
135 3
    public function getResult()
136
    {
137 3
        if (isset($this->result)) {
138 3
            return $this->result;
139
        } else {
140
            return null;
141
        }
142
    }
143
144
    /**
145
     * Renders the requirements check result.
146
     * The output will vary depending is a script running from web or from console.
147
     */
148
    public function render()
149
    {
150
        if (!isset($this->result)) {
151
            $this->usageError('Nothing to render!');
152
        }
153
        $baseViewFilePath = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'views';
154
        if (!empty($_SERVER['argv'])) {
155
            $viewFileName = $baseViewFilePath . DIRECTORY_SEPARATOR . 'console' . DIRECTORY_SEPARATOR . 'index.php';
156
        } else {
157
            $viewFileName = $baseViewFilePath . DIRECTORY_SEPARATOR . 'web' . DIRECTORY_SEPARATOR . 'index.php';
158
        }
159
        $this->renderViewFile($viewFileName, $this->result);
160
    }
161
162
    /**
163
     * Checks if the given PHP extension is available and its version matches the given one.
164
     * @param string $extensionName PHP extension name.
165
     * @param string $version required PHP extension version.
166
     * @param string $compare comparison operator, by default '>='
167
     * @return bool if PHP extension version matches.
168
     */
169 1
    public function checkPhpExtensionVersion($extensionName, $version, $compare = '>=')
170
    {
171 1
        if (!extension_loaded($extensionName)) {
172 1
            return false;
173
        }
174 1
        $extensionVersion = phpversion($extensionName);
175 1
        if (empty($extensionVersion)) {
176
            return false;
177
        }
178 1
        if (strncasecmp($extensionVersion, 'PECL-', 5) === 0) {
179
            $extensionVersion = substr($extensionVersion, 5);
180
        }
181
182 1
        return version_compare($extensionVersion, $version, $compare);
0 ignored issues
show
Bug Best Practice introduced by
The expression return version_compare($...on, $version, $compare) also could return the type integer which is incompatible with the documented return type boolean.
Loading history...
183
    }
184
185
    /**
186
     * Checks if PHP configuration option (from php.ini) is on.
187
     * @param string $name configuration option name.
188
     * @return bool option is on.
189
     */
190
    public function checkPhpIniOn($name)
191
    {
192
        $value = ini_get($name);
193
        if (empty($value)) {
194
            return false;
195
        }
196
197
        return ((int) $value === 1 || strtolower($value) === 'on');
198
    }
199
200
    /**
201
     * Checks if PHP configuration option (from php.ini) is off.
202
     * @param string $name configuration option name.
203
     * @return bool option is off.
204
     */
205
    public function checkPhpIniOff($name)
206
    {
207
        $value = ini_get($name);
208
        if (empty($value)) {
209
            return true;
210
        }
211
212
        return (strtolower($value) === 'off');
213
    }
214
215
    /**
216
     * Compare byte sizes of values given in the verbose representation,
217
     * like '5M', '15K' etc.
218
     * @param string $a first value.
219
     * @param string $b second value.
220
     * @param string $compare comparison operator, by default '>='.
221
     * @return bool comparison result.
222
     */
223 5
    public function compareByteSize($a, $b, $compare = '>=')
224
    {
225 5
        $compareExpression = '(' . $this->getByteSize($a) . $compare . $this->getByteSize($b) . ')';
226
227 5
        return $this->evaluateExpression($compareExpression);
228
    }
229
230
    /**
231
     * Gets the size in bytes from verbose size representation.
232
     * For example: '5K' => 5*1024
233
     * @param string $verboseSize verbose size representation.
234
     * @return int actual size in bytes.
235
     */
236 12
    public function getByteSize($verboseSize)
237
    {
238 12
        if (empty($verboseSize)) {
239
            return 0;
240
        }
241 12
        if (is_numeric($verboseSize)) {
242 2
            return (int) $verboseSize;
243
        }
244 11
        $sizeUnit = trim($verboseSize, '0123456789');
245 11
        $size = trim(str_replace($sizeUnit, '', $verboseSize));
246 11
        if (!is_numeric($size)) {
247
            return 0;
248
        }
249 11
        switch (strtolower($sizeUnit)) {
250 11
            case 'kb':
251 10
            case 'k':
252 5
                return $size * 1024;
253 8
            case 'mb':
254 7
            case 'm':
255 6
                return $size * 1024 * 1024;
256 2
            case 'gb':
257 1
            case 'g':
258 2
                return $size * 1024 * 1024 * 1024;
259
            default:
260
                return 0;
261
        }
262
    }
263
264
    /**
265
     * Checks if upload max file size matches the given range.
266
     * @param string|null $min verbose file size minimum required value, pass null to skip minimum check.
267
     * @param string|null $max verbose file size maximum required value, pass null to skip maximum check.
268
     * @return bool success.
269
     */
270
    public function checkUploadMaxFileSize($min = null, $max = null)
271
    {
272
        $postMaxSize = ini_get('post_max_size');
273
        $uploadMaxFileSize = ini_get('upload_max_filesize');
274
        if ($min !== null) {
275
            $minCheckResult = $this->compareByteSize($postMaxSize, $min, '>=') && $this->compareByteSize($uploadMaxFileSize, $min, '>=');
276
        } else {
277
            $minCheckResult = true;
278
        }
279
        if ($max !== null) {
280
            $maxCheckResult = $this->compareByteSize($postMaxSize, $max, '<=') && $this->compareByteSize($uploadMaxFileSize, $max, '<=');
281
        } else {
282
            $maxCheckResult = true;
283
        }
284
285
        return ($minCheckResult && $maxCheckResult);
286
    }
287
288
    /**
289
     * Renders a view file.
290
     * This method includes the view file as a PHP script
291
     * and captures the display result if required.
292
     * @param string $_viewFile_ view file
293
     * @param array $_data_ data to be extracted and made available to the view file
294
     * @param bool $_return_ whether the rendering result should be returned as a string
295
     * @return string the rendering result. Null if the rendering result is not required.
296
     */
297
    public function renderViewFile($_viewFile_, $_data_ = null, $_return_ = false)
298
    {
299
        // we use special variable names here to avoid conflict when extracting data
300
        if (is_array($_data_)) {
301
            extract($_data_, EXTR_PREFIX_SAME, 'data');
302
        } else {
303
            $data = $_data_;
304
        }
305
        if ($_return_) {
306
            ob_start();
307
            PHP_VERSION_ID >= 80000 ? ob_implicit_flush(false) : ob_implicit_flush(0);
308
            require $_viewFile_;
309
310
            return ob_get_clean();
311
        } else {
312
            require $_viewFile_;
313
        }
314
    }
315
316
    /**
317
     * Normalizes requirement ensuring it has correct format.
318
     * @param array $requirement raw requirement.
319
     * @param int $requirementKey requirement key in the list.
320
     * @return array normalized requirement.
321
     */
322 3
    public function normalizeRequirement($requirement, $requirementKey = 0)
323
    {
324 3
        if (!is_array($requirement)) {
0 ignored issues
show
introduced by
The condition is_array($requirement) is always true.
Loading history...
325
            $this->usageError('Requirement must be an array!');
326
        }
327 3
        if (!array_key_exists('condition', $requirement)) {
328
            $this->usageError("Requirement '{$requirementKey}' has no condition!");
329
        } else {
330 3
            $evalPrefix = 'eval:';
331 3
            if (is_string($requirement['condition']) && strpos($requirement['condition'], $evalPrefix) === 0) {
332 1
                $expression = substr($requirement['condition'], strlen($evalPrefix));
333 1
                $requirement['condition'] = $this->evaluateExpression($expression);
334
            }
335
        }
336 3
        if (!array_key_exists('name', $requirement)) {
337
            $requirement['name'] = is_numeric($requirementKey) ? 'Requirement #' . $requirementKey : $requirementKey;
0 ignored issues
show
introduced by
The condition is_numeric($requirementKey) is always true.
Loading history...
338
        }
339 3
        if (!array_key_exists('mandatory', $requirement)) {
340
            if (array_key_exists('required', $requirement)) {
341
                $requirement['mandatory'] = $requirement['required'];
342
            } else {
343
                $requirement['mandatory'] = false;
344
            }
345
        }
346 3
        if (!array_key_exists('by', $requirement)) {
347
            $requirement['by'] = 'Unknown';
348
        }
349 3
        if (!array_key_exists('memo', $requirement)) {
350
            $requirement['memo'] = '';
351
        }
352
353 3
        return $requirement;
354
    }
355
356
    /**
357
     * Displays a usage error.
358
     * This method will then terminate the execution of the current application.
359
     * @param string $message the error message
360
     */
361
    public function usageError($message)
362
    {
363
        echo "Error: $message\n\n";
364
        exit(1);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
365
    }
366
367
    /**
368
     * Evaluates a PHP expression under the context of this class.
369
     * @param string $expression a PHP expression to be evaluated.
370
     * @return mixed the expression result.
371
     */
372 6
    public function evaluateExpression($expression)
373
    {
374 6
        return eval('return ' . $expression . ';');
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
375
    }
376
377
    /**
378
     * Returns the server information.
379
     * @return string server information.
380
     */
381
    public function getServerInfo()
382
    {
383
        return isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : '';
384
    }
385
386
    /**
387
     * Returns the now date if possible in string representation.
388
     * @return string now date.
389
     */
390
    public function getNowDate()
391
    {
392
        return @strftime('%Y-%m-%d %H:%M', time());
0 ignored issues
show
Bug Best Practice introduced by
The expression return @strftime('%Y-%m-%d %H:%M', time()) could also return false which is incompatible with the documented return type string. Did you maybe forget to handle an error condition?

If the returned type also contains false, it is an indicator that maybe an error condition leading to the specific return statement remains unhandled.

Loading history...
393
    }
394
}
395