Passed
Push — master ( 42ef30...517487 )
by Wilmer
09:11 queued 06:40
created

RequirementsChecker::checkPhpIniOff()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 4
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 8
ccs 0
cts 5
cp 0
crap 6
rs 10
1
<?php
2
3
if (version_compare(PHP_VERSION, '4.3', '<')) {
4
    echo 'At least PHP 4.3 is required to run this script!';
5
    exit(1);
6
}
7
8
/**
9
 * YiiRequirementChecker allows checking, if current system meets the requirements for running the Yii application.
10
 * This class allows rendering of the check report for the web and console application interface.
11
 *
12
 * Example:
13
 *
14
 * ```php
15
 * require_once 'path/to/YiiRequirementChecker.php';
16
 * $requirementsChecker = new YiiRequirementChecker();
17
 * $requirements = array(
18
 *     array(
19
 *         'name' => 'PHP Some Extension',
20
 *         'mandatory' => true`,
21
 *         'condition' => extension_loaded('some_extension'),
22
 *         'by' => 'Some application feature',
23
 *         'memo' => 'PHP extension "some_extension" required',
24
 *     ),
25
 * );
26
 * $requirementsChecker->checkYii()->check($requirements)->render();
27
 * ```
28
 *
29
 * If you wish to render the report with your own representation, use [[getResult()]] instead of [[render()]]
30
 *
31
 * Requirement condition could be in format "eval:PHP expression".
32
 * In this case specified PHP expression will be evaluated in the context of this class instance.
33
 * For example:
34
 *
35
 * ```php
36
 * $requirements = array(
37
 *     array(
38
 *         'name' => 'Upload max file size',
39
 *         'condition' => 'eval:$this->checkUploadMaxFileSize("5M")',
40
 *     ),
41
 * );
42
 * ```
43
 *
44
 * Note: this class definition does not match ordinary Yii style, because it should match PHP 4.3
45
 * and should not use features from newer PHP versions!
46
 *
47
 * @property array|null $result the check results, this property is for internal usage only.
48
 */
49
class RequirementsChecker
50
{
51
    /**
52
     * Check the given requirements, collecting results into internal field.
53
     * This method can be invoked several times checking different requirement sets.
54
     * Use [[getResult()]] or [[render()]] to get the results.
55
     * @param array|string $requirements requirements to be checked.
56
     * If an array, it is treated as the set of requirements;
57
     * If a string, it is treated as the path of the file, which contains the requirements;
58
     * @return $this self instance.
59
     */
60 3
    public function check($requirements)
61
    {
62 3
        if (is_string($requirements)) {
63
            $requirements = require $requirements;
64
        }
65 3
        if (!is_array($requirements)) {
66
            $this->usageError('Requirements must be an array, "' . gettype($requirements) . '" has been given!');
67
        }
68 3
        if (!isset($this->result) || !is_array($this->result)) {
69 3
            $this->result = array(
70
                'summary' => array(
71
                    'total' => 0,
72
                    'errors' => 0,
73
                    'warnings' => 0,
74
                ),
75
                'requirements' => array(),
76
            );
77
        }
78 3
        foreach ($requirements as $key => $rawRequirement) {
79 3
            $requirement = $this->normalizeRequirement($rawRequirement, $key);
80 3
            $this->result['summary']['total']++;
81 3
            if (!$requirement['condition']) {
82 2
                if ($requirement['mandatory']) {
83 2
                    $requirement['error'] = true;
84 2
                    $requirement['warning'] = true;
85 2
                    $this->result['summary']['errors']++;
86
                } else {
87 1
                    $requirement['error'] = false;
88 1
                    $requirement['warning'] = true;
89 2
                    $this->result['summary']['warnings']++;
90
                }
91
            } else {
92 3
                $requirement['error'] = false;
93 3
                $requirement['warning'] = false;
94
            }
95 3
            $this->result['requirements'][] = $requirement;
96
        }
97
98 3
        return $this;
99
    }
100
101
    /**
102
     * Performs the check for the Yii core requirements.
103
     * @return RequirementsChecker self instance.
104
     */
105
    public function checkYii()
106
    {
107
        return $this->check(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'requirements.php');
108
    }
109
110
    /**
111
     * Return the check results.
112
     * @return array|null check results in format:
113
     *
114
     * ```php
115
     * array(
116
     *     'summary' => array(
117
     *         'total' => total number of checks,
118
     *         'errors' => number of errors,
119
     *         'warnings' => number of warnings,
120
     *     ),
121
     *     'requirements' => array(
122
     *         array(
123
     *             ...
124
     *             'error' => is there an error,
125
     *             'warning' => is there a warning,
126
     *         ),
127
     *         ...
128
     *     ),
129
     * )
130
     * ```
131
     */
132 3
    public function getResult()
133
    {
134 3
        if (isset($this->result)) {
135 3
            return $this->result;
136
        } else {
137
            return null;
138
        }
139
    }
140
141
    /**
142
     * Renders the requirements check result.
143
     * The output will vary depending is a script running from web or from console.
144
     */
145
    public function render()
146
    {
147
        if (!isset($this->result)) {
148
            $this->usageError('Nothing to render!');
149
        }
150
        $baseViewFilePath = dirname(__FILE__) . DIRECTORY_SEPARATOR . 'views';
151
        if (!empty($_SERVER['argv'])) {
152
            $viewFileName = $baseViewFilePath . DIRECTORY_SEPARATOR . 'console' . DIRECTORY_SEPARATOR . 'index.php';
153
        } else {
154
            $viewFileName = $baseViewFilePath . DIRECTORY_SEPARATOR . 'web' . DIRECTORY_SEPARATOR . 'index.php';
155
        }
156
        $this->renderViewFile($viewFileName, $this->result);
157
    }
158
159
    /**
160
     * Checks if the given PHP extension is available and its version matches the given one.
161
     * @param string $extensionName PHP extension name.
162
     * @param string $version required PHP extension version.
163
     * @param string $compare comparison operator, by default '>='
164
     * @return bool if PHP extension version matches.
165
     */
166 1
    public function checkPhpExtensionVersion($extensionName, $version, $compare = '>=')
167
    {
168 1
        if (!extension_loaded($extensionName)) {
169 1
            return false;
170
        }
171 1
        $extensionVersion = phpversion($extensionName);
172 1
        if (empty($extensionVersion)) {
173
            return false;
174
        }
175 1
        if (strncasecmp($extensionVersion, 'PECL-', 5) === 0) {
176
            $extensionVersion = substr($extensionVersion, 5);
177
        }
178
179 1
        return version_compare($extensionVersion, $version, $compare);
180
    }
181
182
    /**
183
     * Checks if PHP configuration option (from php.ini) is on.
184
     * @param string $name configuration option name.
185
     * @return bool option is on.
186
     */
187
    public function checkPhpIniOn($name)
188
    {
189
        $value = ini_get($name);
190
        if (empty($value)) {
191
            return false;
192
        }
193
194
        return ((int) $value === 1 || strtolower($value) === 'on');
195
    }
196
197
    /**
198
     * Checks if PHP configuration option (from php.ini) is off.
199
     * @param string $name configuration option name.
200
     * @return bool option is off.
201
     */
202
    public function checkPhpIniOff($name)
203
    {
204
        $value = ini_get($name);
205
        if (empty($value)) {
206
            return true;
207
        }
208
209
        return (strtolower($value) === 'off');
210
    }
211
212
    /**
213
     * Compare byte sizes of values given in the verbose representation,
214
     * like '5M', '15K' etc.
215
     * @param string $a first value.
216
     * @param string $b second value.
217
     * @param string $compare comparison operator, by default '>='.
218
     * @return bool comparison result.
219
     */
220 5
    public function compareByteSize($a, $b, $compare = '>=')
221
    {
222 5
        $compareExpression = '(' . $this->getByteSize($a) . $compare . $this->getByteSize($b) . ')';
223
224 5
        return $this->evaluateExpression($compareExpression);
225
    }
226
227
    /**
228
     * Gets the size in bytes from verbose size representation.
229
     * For example: '5K' => 5*1024
230
     * @param string $verboseSize verbose size representation.
231
     * @return int actual size in bytes.
232
     */
233 12
    public function getByteSize($verboseSize)
234
    {
235 12
        if (empty($verboseSize)) {
236
            return 0;
237
        }
238 12
        if (is_numeric($verboseSize)) {
239 2
            return (int) $verboseSize;
240
        }
241 11
        $sizeUnit = trim($verboseSize, '0123456789');
242 11
        $size = trim(str_replace($sizeUnit, '', $verboseSize));
243 11
        if (!is_numeric($size)) {
244
            return 0;
245
        }
246 11
        switch (strtolower($sizeUnit)) {
247 11
            case 'kb':
248 10
            case 'k':
249 5
                return $size * 1024;
250 8
            case 'mb':
251 7
            case 'm':
252 6
                return $size * 1024 * 1024;
253 2
            case 'gb':
254 1
            case 'g':
255 2
                return $size * 1024 * 1024 * 1024;
256
            default:
257
                return 0;
258
        }
259
    }
260
261
    /**
262
     * Checks if upload max file size matches the given range.
263
     * @param string|null $min verbose file size minimum required value, pass null to skip minimum check.
264
     * @param string|null $max verbose file size maximum required value, pass null to skip maximum check.
265
     * @return bool success.
266
     */
267
    public function checkUploadMaxFileSize($min = null, $max = null)
268
    {
269
        $postMaxSize = ini_get('post_max_size');
270
        $uploadMaxFileSize = ini_get('upload_max_filesize');
271
        if ($min !== null) {
272
            $minCheckResult = $this->compareByteSize($postMaxSize, $min, '>=') && $this->compareByteSize($uploadMaxFileSize, $min, '>=');
273
        } else {
274
            $minCheckResult = true;
275
        }
276
        if ($max !== null) {
277
            $maxCheckResult = $this->compareByteSize($postMaxSize, $max, '<=') && $this->compareByteSize($uploadMaxFileSize, $max, '<=');
278
        } else {
279
            $maxCheckResult = true;
280
        }
281
282
        return ($minCheckResult && $maxCheckResult);
283
    }
284
285
    /**
286
     * Renders a view file.
287
     * This method includes the view file as a PHP script
288
     * and captures the display result if required.
289
     * @param string $_viewFile_ view file
290
     * @param array $_data_ data to be extracted and made available to the view file
291
     * @param bool $_return_ whether the rendering result should be returned as a string
292
     * @return string the rendering result. Null if the rendering result is not required.
293
     */
294
    public function renderViewFile($_viewFile_, $_data_ = null, $_return_ = false)
295
    {
296
        // we use special variable names here to avoid conflict when extracting data
297
        if (is_array($_data_)) {
298
            extract($_data_, EXTR_PREFIX_SAME, 'data');
299
        } else {
300
            $data = $_data_;
301
        }
302
        if ($_return_) {
303
            ob_start();
304
            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

304
            PHP_VERSION_ID >= 80000 ? ob_implicit_flush(/** @scrutinizer ignore-type */ false) : ob_implicit_flush(0);
Loading history...
305
            require $_viewFile_;
306
307
            return ob_get_clean();
308
        } else {
309
            require $_viewFile_;
310
        }
311
    }
312
313
    /**
314
     * Normalizes requirement ensuring it has correct format.
315
     * @param array $requirement raw requirement.
316
     * @param int $requirementKey requirement key in the list.
317
     * @return array normalized requirement.
318
     */
319 3
    public function normalizeRequirement($requirement, $requirementKey = 0)
320
    {
321 3
        if (!is_array($requirement)) {
0 ignored issues
show
introduced by
The condition is_array($requirement) is always true.
Loading history...
322
            $this->usageError('Requirement must be an array!');
323
        }
324 3
        if (!array_key_exists('condition', $requirement)) {
325
            $this->usageError("Requirement '{$requirementKey}' has no condition!");
326
        } else {
327 3
            $evalPrefix = 'eval:';
328 3
            if (is_string($requirement['condition']) && strpos($requirement['condition'], $evalPrefix) === 0) {
329 1
                $expression = substr($requirement['condition'], strlen($evalPrefix));
330 1
                $requirement['condition'] = $this->evaluateExpression($expression);
331
            }
332
        }
333 3
        if (!array_key_exists('name', $requirement)) {
334
            $requirement['name'] = is_numeric($requirementKey) ? 'Requirement #' . $requirementKey : $requirementKey;
0 ignored issues
show
introduced by
The condition is_numeric($requirementKey) is always true.
Loading history...
335
        }
336 3
        if (!array_key_exists('mandatory', $requirement)) {
337
            if (array_key_exists('required', $requirement)) {
338
                $requirement['mandatory'] = $requirement['required'];
339
            } else {
340
                $requirement['mandatory'] = false;
341
            }
342
        }
343 3
        if (!array_key_exists('by', $requirement)) {
344
            $requirement['by'] = 'Unknown';
345
        }
346 3
        if (!array_key_exists('memo', $requirement)) {
347
            $requirement['memo'] = '';
348
        }
349
350 3
        return $requirement;
351
    }
352
353
    /**
354
     * Displays a usage error.
355
     * This method will then terminate the execution of the current application.
356
     * @param string $message the error message
357
     */
358
    public function usageError($message)
359
    {
360
        echo "Error: $message\n\n";
361
        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...
362
    }
363
364
    /**
365
     * Evaluates a PHP expression under the context of this class.
366
     * @param string $expression a PHP expression to be evaluated.
367
     * @return mixed the expression result.
368
     */
369 6
    public function evaluateExpression($expression)
370
    {
371 6
        return eval('return ' . $expression . ';');
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
372
    }
373
374
    /**
375
     * Returns the server information.
376
     * @return string server information.
377
     */
378
    public function getServerInfo()
379
    {
380
        return isset($_SERVER['SERVER_SOFTWARE']) ? $_SERVER['SERVER_SOFTWARE'] : '';
381
    }
382
383
    /**
384
     * Returns the now date if possible in string representation.
385
     * @return string now date.
386
     */
387
    public function getNowDate()
388
    {
389
        return @strftime('%Y-%m-%d %H:%M', time());
390
    }
391
}
392