Passed
Pull Request — master (#67)
by
unknown
02:52
created

RequirementsChecker::ecs()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 0

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 5
Bugs 4 Features 0
Metric Value
cc 1
eloc 0
c 5
b 4
f 0
nc 1
nop 0
dl 0
loc 2
ccs 0
cts 1
cp 0
crap 2
rs 10
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
26
 *     ->chekYii()
27
 *     ->check($requirements)
28
 *     ->render();
29
 * ```
30
 *
31
 * If you wish to render the report with your own representation, use [[getResult()]] instead of [[render()]]
32
 *
33
 * Requirement condition could be in format "eval:PHP expression".
34
 * In this case specified PHP expression will be evaluated in the context of this class instance.
35
 * For example:
36
 *
37
 * ```php
38
 * $requirements = array(
39
 *     array(
40
 *         'name' => 'Upload max file size',
41
 *         'condition' => 'eval:$this->checkUploadMaxFileSize("5M")',
42
 *     ),
43
 * );
44
 * ```
45
 *
46
 * Note: this class definition does not match ordinary Yii style, because it should match PHP 4.3
47
 * and should not use features from newer PHP versions!
48
 *
49
 * @property array|null $result the check results, this property is for internal usage only.
50
 */
51
class RequirementsChecker
52
{
53
    /**
54
     * Check the given requirements, collecting results into internal field.
55
     * This method can be invoked several times checking different requirement sets.
56
     * Use [[getResult()]] or [[render()]] to get the results.
57
     * @param array|string $requirements requirements to be checked.
58
     * If an array, it is treated as the set of requirements;
59
     * If a string, it is treated as the path of the file, which contains the requirements;
60
     * @return $this self instance.
61
     */
62 3
    public function check($requirements)
63
    {
64 3
        if (version_compare(PHP_VERSION, '4.3', '<')) {
65
            echo 'At least PHP 4.3 is required to run this script!';
66
            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...
67
        }
68 3
        if (is_string($requirements)) {
69
            $requirements = require $requirements;
70
        }
71 3
        if (!is_array($requirements)) {
72
            $this->usageError('Requirements must be an array, "' . gettype($requirements) . '" has been given!');
73
        }
74 3
        if (!isset($this->result) || !is_array($this->result)) {
75 3
            $this->result = [
76 3
                'summary' => [
77 3
                    'total' => 0,
78 3
                    'errors' => 0,
79 3
                    'warnings' => 0,
80 3
                ],
81 3
                'requirements' => [],
82 3
            ];
83
        }
84 3
        foreach ($requirements as $key => $rawRequirement) {
85 3
            $requirement = $this->normalizeRequirement($rawRequirement, $key);
86 3
            $this->result['summary']['total']++;
87 3
            if (!$requirement['condition']) {
88 2
                if ($requirement['mandatory']) {
89 2
                    $requirement['error'] = true;
90 2
                    $requirement['warning'] = true;
91 2
                    $this->result['summary']['errors']++;
92
                } else {
93 1
                    $requirement['error'] = false;
94 1
                    $requirement['warning'] = true;
95 2
                    $this->result['summary']['warnings']++;
96
                }
97
            } else {
98 3
                $requirement['error'] = false;
99 3
                $requirement['warning'] = false;
100
            }
101 3
            $this->result['requirements'][] = $requirement;
102
        }
103
104 3
        return $this;
105
    }
106
107
    /**
108
     * Performs the check for the Yii core requirements.
109
     * @return RequirementsChecker self instance.
110
     */
111
    public function checkYii()
112
    {
113
        return $this->check(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'requirements.php');
114
    }
115
116
    /**
117
     * Return the check results.
118
     * @return array|null check results in format:
119
     *
120
     * ```php
121
     * array(
122
     *     'summary' => array(
123
     *         'total' => total number of checks,
124
     *         'errors' => number of errors,
125
     *         'warnings' => number of warnings,
126
     *     ),
127
     *     'requirements' => array(
128
     *         array(
129
     *             ...
130
     *             'error' => is there an error,
131
     *             'warning' => is there a warning,
132
     *         ),
133
     *         ...
134
     *     ),
135
     * )
136
     * ```
137
     */
138 3
    public function getResult()
139
    {
140 3
        if (isset($this->result)) {
141 3
            return $this->result;
142
        } else {
143
            return null;
144
        }
145
    }
146
147
    /**
148
     * Renders the requirements check result.
149
     * The output will vary depending is a script running from web or from console.
150
     */
151
    public function render()
152
    {
153
        if (!isset($this->result)) {
154
            $this->usageError('Nothing to render!');
155
        }
156
        $baseViewFilePath = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'views';
157
        if (!empty($_SERVER['argv'])) {
158
            $viewFileName = $baseViewFilePath . DIRECTORY_SEPARATOR . 'console' . DIRECTORY_SEPARATOR . 'index.php';
159
        } else {
160
            $viewFileName = $baseViewFilePath . DIRECTORY_SEPARATOR . 'web' . DIRECTORY_SEPARATOR . 'index.php';
161
        }
162
        $this->renderViewFile($viewFileName, $this->result);
163
    }
164
165
    /**
166
     * Checks if the given PHP extension is available and its version matches the given one.
167
     * @param string $extensionName PHP extension name.
168
     * @param string $version required PHP extension version.
169
     * @param string $compare comparison operator, by default '>='
170
     * @return bool if PHP extension version matches.
171
     */
172 1
    public function checkPhpExtensionVersion($extensionName, $version, $compare = '>=')
173
    {
174 1
        if (!extension_loaded($extensionName)) {
175 1
            return false;
176
        }
177 1
        $extensionVersion = phpversion($extensionName);
178 1
        if (empty($extensionVersion)) {
179
            return false;
180
        }
181 1
        if (strncasecmp($extensionVersion, 'PECL-', 5) === 0) {
182
            $extensionVersion = substr($extensionVersion, 5);
183
        }
184
185 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...
186
    }
187
188
    /**
189
     * Checks if PHP configuration option (from php.ini) is on.
190
     * @param string $name configuration option name.
191
     * @return bool option is on.
192
     */
193
    public function checkPhpIniOn($name)
194
    {
195
        $value = ini_get($name);
196
        if (empty($value)) {
197
            return false;
198
        }
199
200
        return (int) $value === 1 || strtolower($value) === 'on';
201
    }
202
203
    /**
204
     * Checks if PHP configuration option (from php.ini) is off.
205
     * @param string $name configuration option name.
206
     * @return bool option is off.
207
     */
208
    public function checkPhpIniOff($name)
209
    {
210
        $value = ini_get($name);
211
        if (empty($value)) {
212
            return true;
213
        }
214
215
        return strtolower($value) === 'off';
216
    }
217
218
    /**
219
     * Compare byte sizes of values given in the verbose representation,
220
     * like '5M', '15K' etc.
221
     * @param string $a first value.
222
     * @param string $b second value.
223
     * @param string $compare comparison operator, by default '>='.
224
     * @return bool comparison result.
225
     */
226 5
    public function compareByteSize($a, $b, $compare = '>=')
227
    {
228 5
        $compareExpression = '(' . $this->getByteSize($a) . $compare . $this->getByteSize($b) . ')';
229
230 5
        return $this->evaluateExpression($compareExpression);
231
    }
232
233
    /**
234
     * Gets the size in bytes from verbose size representation.
235
     * For example: '5K' => 5*1024
236
     * @param string $verboseSize verbose size representation.
237
     * @return int actual size in bytes.
238
     */
239 12
    public function getByteSize($verboseSize)
240
    {
241 12
        if (empty($verboseSize)) {
242
            return 0;
243
        }
244 12
        if (is_numeric($verboseSize)) {
245 2
            return (int) $verboseSize;
246
        }
247 11
        $sizeUnit = trim($verboseSize, '0123456789');
248 11
        $size = trim(str_replace($sizeUnit, '', $verboseSize));
249 11
        if (!is_numeric($size)) {
250
            return 0;
251
        }
252 11
        switch (strtolower($sizeUnit)) {
253 11
            case 'kb':
254 10
            case 'k':
255 5
                return $size * 1024;
256 8
            case 'mb':
257 7
            case 'm':
258 6
                return $size * 1024 * 1024;
259 2
            case 'gb':
260 1
            case 'g':
261 2
                return $size * 1024 * 1024 * 1024;
262
            default:
263
                return 0;
264
        }
265
    }
266
267
    /**
268
     * Checks if upload max file size matches the given range.
269
     * @param string|null $min verbose file size minimum required value, pass null to skip minimum check.
270
     * @param string|null $max verbose file size maximum required value, pass null to skip maximum check.
271
     * @return bool success.
272
     */
273
    public function checkUploadMaxFileSize($min = null, $max = null)
274
    {
275
        $postMaxSize = ini_get('post_max_size');
276
        $uploadMaxFileSize = ini_get('upload_max_filesize');
277
        if ($min !== null) {
278
            $minCheckResult = $this->compareByteSize($postMaxSize, $min, '>=') && $this->compareByteSize($uploadMaxFileSize, $min, '>=');
279
        } else {
280
            $minCheckResult = true;
281
        }
282
        if ($max !== null) {
283
            $maxCheckResult = $this->compareByteSize($postMaxSize, $max, '<=') && $this->compareByteSize($uploadMaxFileSize, $max, '<=');
284
        } else {
285
            $maxCheckResult = true;
286
        }
287
288
        return $minCheckResult && $maxCheckResult;
289
    }
290
291
    /**
292
     * Renders a view file.
293
     * This method includes the view file as a PHP script
294
     * and captures the display result if required.
295
     * @param string $_viewFile_ view file
296
     * @param array $_data_ data to be extracted and made available to the view file
297
     * @param bool $_return_ whether the rendering result should be returned as a string
298
     * @return string the rendering result. Null if the rendering result is not required.
299
     */
300
    public function renderViewFile($_viewFile_, $_data_ = null, $_return_ = false)
301
    {
302
        // we use special variable names here to avoid conflict when extracting data
303
        if (is_array($_data_)) {
304
            extract($_data_, EXTR_PREFIX_SAME, 'data');
305
        } else {
306
            $data = $_data_;
307
        }
308
        if ($_return_) {
309
            ob_start();
310
            PHP_VERSION_ID >= 80000 ? ob_implicit_flush(false) : ob_implicit_flush(0);
311
            require $_viewFile_;
312
313
            return ob_get_clean();
314
        } else {
315
            require $_viewFile_;
316
        }
317
    }
318
319
    /**
320
     * Normalizes requirement ensuring it has correct format.
321
     * @param array $requirement raw requirement.
322
     * @param int $requirementKey requirement key in the list.
323
     * @return array normalized requirement.
324
     */
325 3
    public function normalizeRequirement($requirement, $requirementKey = 0)
326
    {
327 3
        if (!is_array($requirement)) {
0 ignored issues
show
introduced by
The condition is_array($requirement) is always true.
Loading history...
328
            $this->usageError('Requirement must be an array!');
329
        }
330 3
        if (!array_key_exists('condition', $requirement)) {
331
            $this->usageError("Requirement '{$requirementKey}' has no condition!");
332
        } else {
333 3
            $evalPrefix = 'eval:';
334 3
            if (is_string($requirement['condition']) && strpos($requirement['condition'], $evalPrefix) === 0) {
335 1
                $expression = substr($requirement['condition'], strlen($evalPrefix));
336 1
                $requirement['condition'] = $this->evaluateExpression($expression);
337
            }
338
        }
339 3
        if (!array_key_exists('name', $requirement)) {
340
            $requirement['name'] = is_numeric($requirementKey) ? 'Requirement #' . $requirementKey : $requirementKey;
0 ignored issues
show
introduced by
The condition is_numeric($requirementKey) is always true.
Loading history...
341
        }
342 3
        if (!array_key_exists('mandatory', $requirement)) {
343
            if (array_key_exists('required', $requirement)) {
344
                $requirement['mandatory'] = $requirement['required'];
345
            } else {
346
                $requirement['mandatory'] = false;
347
            }
348
        }
349 3
        if (!array_key_exists('by', $requirement)) {
350
            $requirement['by'] = 'Unknown';
351
        }
352 3
        if (!array_key_exists('memo', $requirement)) {
353
            $requirement['memo'] = '';
354
        }
355
356 3
        return $requirement;
357
    }
358
359
    /**
360
     * Displays a usage error.
361
     * This method will then terminate the execution of the current application.
362
     * @param string $message the error message
363
     */
364
    public function usageError($message)
365
    {
366
        echo "Error: $message\n\n";
367
        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...
368
    }
369
370
    /**
371
     * Evaluates a PHP expression under the context of this class.
372
     * @param string $expression a PHP expression to be evaluated.
373
     * @return mixed the expression result.
374
     */
375 6
    public function evaluateExpression($expression)
376
    {
377 6
        return eval('return ' . $expression . ';');
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
378
    }
379
380
    /**
381
     * Returns the server information.
382
     * @return string server information.
383
     */
384
    public function getServerInfo()
385
    {
386
        return isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : '';
387
    }
388
389
    /**
390
     * Returns the now date if possible in string representation.
391
     * @return string now date.
392
     */
393
    public function getNowDate()
394
    {
395
        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...
396
    }
397
398
    public function ecs()
399
    {
400
    }
401
}
402