Passed
Push — master ( 517487...103b66 )
by Wilmer
01:53
created

RequirementsChecker::getNowDate()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 0
cts 2
cp 0
crap 2
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);
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);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type integer expected by parameter $flag of ob_implicit_flush(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

307
            PHP_VERSION_ID >= 80000 ? ob_implicit_flush(/** @scrutinizer ignore-type */ false) : ob_implicit_flush(0);
Loading history...
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());
393
    }
394
}
395