System::_parseArgs()   A
last analyzed

Complexity

Conditions 5
Paths 2

Size

Total Lines 32
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 8
nc 2
nop 3
dl 0
loc 32
rs 9.6111
c 0
b 0
f 0
1
<?php
2
/**
3
 * File/Directory manipulation
4
 *
5
 * PHP versions 4 and 5
6
 *
7
 * @category   pear
8
 * @package    System
9
 * @author     Tomas V.V.Cox <[email protected]>
10
 * @copyright  1997-2009 The Authors
11
 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
12
 * @link       http://pear.php.net/package/PEAR
13
 * @since      File available since Release 0.1
14
 */
15
16
/**
17
 * base class
18
 */
19
require_once 'PEAR.php';
20
require_once 'Console/Getopt.php';
21
22
$GLOBALS['_System_temp_files'] = [];
23
24
/**
25
 * System offers cross platform compatible system functions
26
 *
27
 * Static functions for different operations. Should work under
28
 * Unix and Windows. The names and usage has been taken from its respectively
29
 * GNU commands. The functions will return (bool) false on error and will
30
 * trigger the error with the PHP trigger_error() function (you can silence
31
 * the error by prefixing a '@' sign after the function call, but this
32
 * is not recommended practice.  Instead use an error handler with
33
 * {@link set_error_handler()}).
34
 *
35
 * Documentation on this class you can find in:
36
 * http://pear.php.net/manual/
37
 *
38
 * Example usage:
39
 * if (!@System::rm('-r file1 dir1')) {
40
 *    print "could not delete file1 or dir1";
41
 * }
42
 *
43
 * In case you need to to pass file names with spaces,
44
 * pass the params as an array:
45
 *
46
 * System::rm(array('-r', $file1, $dir1));
47
 *
48
 * @category   pear
49
 * @package    System
50
 * @author     Tomas V.V. Cox <[email protected]>
51
 * @copyright  1997-2006 The PHP Group
52
 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
53
 * @version    Release: 1.10.12
54
 * @link       http://pear.php.net/package/PEAR
55
 * @since      Class available since Release 0.1
56
 * @static
57
 */
58
class System
59
{
60
    /**
61
     * returns the commandline arguments of a function
62
     *
63
     * @param string $argv          the commandline
64
     * @param string $short_options the allowed option short-tags
65
     * @param string $long_options  the allowed option long-tags
66
     * @return   array   the given options and there values
67
     */
68
    public static function _parseArgs($argv, $short_options, $long_options = null)
69
    {
70
        if (!is_array($argv) && null !== $argv) {
0 ignored issues
show
introduced by
The condition is_array($argv) is always false.
Loading history...
introduced by
The condition null !== $argv is always true.
Loading history...
71
            /*
72
            // Quote all items that are a short option
73
            $av = preg_split('/(\A| )--?[a-z0-9]+[ =]?((?<!\\\\)((,\s*)|((?<!,)\s+))?)/i', $argv, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
74
            $offset = 0;
75
            foreach ($av as $a) {
76
                $b = trim($a[0]);
77
                if ($b[0] == '"' || $b[0] == "'") {
78
                    continue;
79
                }
80
81
                $escape = escapeshellarg($b);
82
                $pos = $a[1] + $offset;
83
                $argv = substr_replace($argv, $escape, $pos, strlen($b));
84
                $offset += 2;
85
            }
86
            */
87
88
            // Find all items, quoted or otherwise
89
            preg_match_all("/(?:[\"'])(.*?)(?:['\"])|(\S+)/", $argv, $av);
90
            $argv = $av[1];
91
            foreach ($av[2] as $k => $a) {
92
                if (empty($a)) {
93
                    continue;
94
                }
95
                $argv[$k] = trim($a);
96
            }
97
        }
98
99
        return Console_Getopt::getopt2($argv, $short_options, $long_options);
0 ignored issues
show
Bug introduced by
It seems like $long_options can also be of type string; however, parameter $long_options of Console_Getopt::getopt2() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

99
        return Console_Getopt::getopt2($argv, $short_options, /** @scrutinizer ignore-type */ $long_options);
Loading history...
100
    }
101
102
    /**
103
     * Output errors with PHP trigger_error(). You can silence the errors
104
     * with prefixing a "@" sign to the function call: @System::mkdir(..);
105
     *
106
     * @param mixed $error a PEAR error or a string with the error message
107
     * @return bool false
108
     */
109
    protected static function raiseError($error)
110
    {
111
        if (PEAR::isError($error)) {
112
            $error = $error->getMessage();
113
        }
114
        trigger_error($error, E_USER_WARNING);
115
        return false;
116
    }
117
118
    /**
119
     * Creates a nested array representing the structure of a directory
120
     *
121
     * System::_dirToStruct('dir1', 0) =>
122
     *   Array
123
     *    (
124
     *    [dirs] => Array
125
     *        (
126
     *            [0] => dir1
127
     *        )
128
     *
129
     *    [files] => Array
130
     *        (
131
     *            [0] => dir1/file2
132
     *            [1] => dir1/file3
133
     *        )
134
     *    )
135
     * @param string $sPath   Name of the directory
136
     * @param int    $maxinst max. deep of the lookup
137
     * @param int    $aktinst starting deep of the lookup
138
     * @param bool   $silent  if true, do not emit errors.
139
     * @return   array   the structure of the dir
140
     */
141
    protected static function _dirToStruct($sPath, $maxinst, $aktinst = 0, $silent = false)
142
    {
143
        $struct = ['dirs' => [], 'files' => []];
144
        if (false === ($dir = @opendir($sPath))) {
145
            if (!$silent) {
146
                self::raiseError("Could not open dir $sPath");
147
            }
148
            return $struct; // XXX could not open error
149
        }
150
151
        $struct['dirs'][] = $sPath = realpath($sPath); // XXX don't add if '.' or '..' ?
152
        $list             = [];
153
        while (false !== ($file = readdir($dir))) {
154
            if ('.' != $file && '..' != $file) {
155
                $list[] = $file;
156
            }
157
        }
158
159
        closedir($dir);
160
        natsort($list);
161
        if ($aktinst < $maxinst || 0 == $maxinst) {
162
            foreach ($list as $val) {
163
                $path = $sPath . DIRECTORY_SEPARATOR . $val;
164
                if (is_dir($path) && !is_link($path)) {
165
                    $tmp    = self::_dirToStruct($path, $maxinst, $aktinst + 1, $silent);
166
                    $struct = array_merge_recursive($struct, $tmp);
167
                } else {
168
                    $struct['files'][] = $path;
169
                }
170
            }
171
        }
172
173
        return $struct;
174
    }
175
176
    /**
177
     * Creates a nested array representing the structure of a directory and files
178
     *
179
     * @param array $files Array listing files and dirs
180
     * @return   array
181
     * @static
182
     * @see System::_dirToStruct()
183
     */
184
    protected static function _multipleToStruct($files)
185
    {
186
        $struct = ['dirs' => [], 'files' => []];
187
        $files  = (array)$files;
188
        foreach ($files as $file) {
189
            if (is_dir($file) && !is_link($file)) {
190
                $tmp    = self::_dirToStruct($file, 0);
191
                $struct = array_merge_recursive($tmp, $struct);
192
            } else {
193
                if (!in_array($file, $struct['files'])) {
194
                    $struct['files'][] = $file;
195
                }
196
            }
197
        }
198
        return $struct;
199
    }
200
201
    /**
202
     * The rm command for removing files.
203
     * Supports multiple files and dirs and also recursive deletes
204
     *
205
     * @param string $args the arguments for rm
206
     * @return   mixed   PEAR_Error or true for success
207
     * @static
208
     * @access   public
209
     */
210
    public static function rm($args)
211
    {
212
        $opts = self::_parseArgs($args, 'rf'); // "f" does nothing but I like it :-)
213
        if (PEAR::isError($opts)) {
214
            return self::raiseError($opts);
215
        }
216
        foreach ($opts[0] as $opt) {
217
            if ('r' == $opt[0]) {
218
                $do_recursive = true;
219
            }
220
        }
221
        $ret = true;
222
        if (isset($do_recursive)) {
223
            $struct = self::_multipleToStruct($opts[1]);
224
            foreach ($struct['files'] as $file) {
225
                if (!@unlink($file)) {
226
                    $ret = false;
227
                }
228
            }
229
230
            rsort($struct['dirs']);
231
            foreach ($struct['dirs'] as $dir) {
232
                if (!@rmdir($dir)) {
233
                    $ret = false;
234
                }
235
            }
236
        } else {
237
            foreach ($opts[1] as $file) {
238
                $delete = (is_dir($file)) ? 'rmdir' : 'unlink';
239
                if (!@$delete($file)) {
240
                    $ret = false;
241
                }
242
            }
243
        }
244
        return $ret;
245
    }
246
247
    /**
248
     * Make directories.
249
     *
250
     * The -p option will create parent directories
251
     * @param string $args the name of the director(y|ies) to create
252
     * @return   bool    True for success
253
     */
254
    public static function mkDir($args)
255
    {
256
        $opts = self::_parseArgs($args, 'pm:');
257
        if (PEAR::isError($opts)) {
258
            return self::raiseError($opts);
259
        }
260
261
        $mode = 0777; // default mode
262
        foreach ($opts[0] as $opt) {
263
            if ('p' == $opt[0]) {
264
                $create_parents = true;
265
            } elseif ('m' == $opt[0]) {
266
                // if the mode is clearly an octal number (starts with 0)
267
                // convert it to decimal
268
                if (strlen($opt[1]) && '0' == $opt[1][0]) {
269
                    $opt[1] = octdec($opt[1]);
270
                } else {
271
                    // convert to int
272
                    $opt[1] += 0;
273
                }
274
                $mode = $opt[1];
275
            }
276
        }
277
278
        $ret = true;
279
        if (isset($create_parents)) {
280
            foreach ($opts[1] as $dir) {
281
                $dirstack = [];
282
                while ((!is_dir($dir) || !is_dir($dir))
283
                       && DIRECTORY_SEPARATOR != $dir) {
284
                    array_unshift($dirstack, $dir);
285
                    $dir = \dirname($dir);
286
                }
287
288
                while ($newdir = array_shift($dirstack)) {
289
                    if (!is_writable(dirname($newdir))) {
290
                        $ret = false;
291
                        break;
292
                    }
293
294
                    if (!mkdir($newdir, $mode)) {
295
                        $ret = false;
296
                    }
297
                }
298
            }
299
        } else {
300
            foreach ($opts[1] as $dir) {
301
                if ((@is_dir($dir) || !is_dir($dir)) && !mkdir($dir, $mode)) {
302
                    $ret = false;
303
                }
304
            }
305
        }
306
307
        return $ret;
308
    }
309
310
    /**
311
     * Concatenate files
312
     *
313
     * Usage:
314
     * 1) $var = System::cat('sample.txt test.txt');
315
     * 2) System::cat('sample.txt test.txt > final.txt');
316
     * 3) System::cat('sample.txt test.txt >> final.txt');
317
     *
318
     * Note: as the class use fopen, urls should work also
319
     *
320
     * @param string $args the arguments
321
     * @return   bool true on success
322
     */
323
    public static function &cat($args)
324
    {
325
        $ret   = null;
326
        $files = [];
327
        if (!is_array($args)) {
0 ignored issues
show
introduced by
The condition is_array($args) is always false.
Loading history...
328
            $args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY);
329
        }
330
331
        $count_args = count($args);
332
        for ($i = 0; $i < $count_args; ++$i) {
333
            if ('>' == $args[$i]) {
334
                $mode       = 'wb';
335
                $outputfile = $args[$i + 1];
336
                break;
337
            } elseif ('>>' == $args[$i]) {
338
                $mode       = 'ab+';
339
                $outputfile = $args[$i + 1];
340
                break;
341
            } else {
342
                $files[] = $args[$i];
343
            }
344
        }
345
        $outputfd = false;
346
        if (isset($mode)) {
347
            if (!$outputfd = fopen($outputfile, $mode)) {
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $outputfile does not seem to be defined for all execution paths leading up to this point.
Loading history...
348
                $err = self::raiseError("Could not open $outputfile");
349
                return $err;
350
            }
351
            $ret = true;
352
        }
353
        foreach ($files as $file) {
354
            if (!$fd = fopen($file, 'r')) {
355
                self::raiseError("Could not open $file");
356
                continue;
357
            }
358
            while ($cont = fread($fd, 2048)) {
359
                if (is_resource($outputfd)) {
360
                    fwrite($outputfd, $cont);
361
                } else {
362
                    $ret .= $cont;
363
                }
364
            }
365
            fclose($fd);
366
        }
367
        if (is_resource($outputfd)) {
368
            fclose($outputfd);
369
        }
370
        return $ret;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $ret also could return the type string which is incompatible with the documented return type boolean.
Loading history...
371
    }
372
373
    /**
374
     * Creates temporary files or directories. This function will remove
375
     * the created files when the scripts finish its execution.
376
     *
377
     * Usage:
378
     *   1) $tempfile = System::mktemp("prefix");
379
     *   2) $tempdir  = System::mktemp("-d prefix");
380
     *   3) $tempfile = System::mktemp();
381
     *   4) $tempfile = System::mktemp("-t /var/tmp prefix");
382
     *
383
     * prefix -> The string that will be prepended to the temp name
384
     *           (defaults to "tmp").
385
     * -d     -> A temporary dir will be created instead of a file.
386
     * -t     -> The target dir where the temporary (file|dir) will be created. If
387
     *           this param is missing by default the env vars TMP on Windows or
388
     *           TMPDIR in Unix will be used. If these vars are also missing
389
     *           c:\windows\temp or /tmp will be used.
390
     *
391
     * @param string $args The arguments
392
     * @return  mixed   the full path of the created (file|dir) or false
393
     * @see System::tmpdir()
394
     */
395
    public static function mktemp($args = null)
396
    {
397
        static $first_time = true;
398
        $opts = self::_parseArgs($args, 't:d');
399
        if (PEAR::isError($opts)) {
400
            return self::raiseError($opts);
401
        }
402
403
        foreach ($opts[0] as $opt) {
404
            if ('d' == $opt[0]) {
405
                $tmp_is_dir = true;
406
            } elseif ('t' == $opt[0]) {
407
                $tmpdir = $opt[1];
408
            }
409
        }
410
411
        $prefix = $opts[1][0] ?? 'tmp';
412
        if (!isset($tmpdir)) {
413
            $tmpdir = self::tmpdir();
414
        }
415
416
        if (!self::mkDir(['-p', $tmpdir])) {
0 ignored issues
show
Bug introduced by
array('-p', $tmpdir) of type array<integer,mixed|string> is incompatible with the type string expected by parameter $args of System::mkDir(). ( Ignorable by Annotation )

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

416
        if (!self::mkDir(/** @scrutinizer ignore-type */ ['-p', $tmpdir])) {
Loading history...
417
            return false;
418
        }
419
420
        $tmp = tempnam($tmpdir, $prefix);
421
        if (isset($tmp_is_dir)) {
422
            unlink($tmp); // be careful possible race condition here
423
            if (!mkdir($tmp, 0700)) {
424
                return self::raiseError("Unable to create temporary directory $tmpdir");
425
            }
426
        }
427
428
        $GLOBALS['_System_temp_files'][] = $tmp;
429
        if (isset($tmp_is_dir)) {
430
            //$GLOBALS['_System_temp_files'][] = \dirname($tmp);
431
        }
432
433
        if ($first_time) {
434
            PEAR::registerShutdownFunc(['System', '_removeTmpFiles']);
435
            $first_time = false;
436
        }
437
438
        return $tmp;
439
    }
440
441
    /**
442
     * Remove temporary files created my mkTemp. This function is executed
443
     * at script shutdown time
444
     */
445
    public static function _removeTmpFiles()
446
    {
447
        if (count($GLOBALS['_System_temp_files'])) {
448
            $delete = $GLOBALS['_System_temp_files'];
449
            array_unshift($delete, '-r');
450
            self::rm($delete);
451
            $GLOBALS['_System_temp_files'] = [];
452
        }
453
    }
454
455
    /**
456
     * Get the path of the temporal directory set in the system
457
     * by looking in its environments variables.
458
     * Note: php.ini-recommended removes the "E" from the variables_order setting,
459
     * making unavaible the $_ENV array, that s why we do tests with _ENV
460
     *
461
     * @return string The temporary directory on the system
462
     */
463
    public static function tmpdir()
464
    {
465
        if (OS_WINDOWS) {
466
            if ($var = $_ENV['TMP'] ?? getenv('TMP')) {
467
                return $var;
468
            }
469
            if ($var = $_ENV['TEMP'] ?? getenv('TEMP')) {
470
                return $var;
471
            }
472
            if ($var = $_ENV['USERPROFILE'] ?? getenv('USERPROFILE')) {
473
                return $var;
474
            }
475
            if ($var = $_ENV['windir'] ?? getenv('windir')) {
476
                return $var;
477
            }
478
            return getenv('SystemRoot') . '\temp';
479
        }
480
        if ($var = $_ENV['TMPDIR'] ?? getenv('TMPDIR')) {
481
            return $var;
482
        }
483
        return realpath(function_exists('sys_get_temp_dir') ? sys_get_temp_dir() : '/tmp');
484
    }
485
486
    /**
487
     * The "which" command (show the full path of a command)
488
     *
489
     * @param string $program  The command to search for
490
     * @param mixed  $fallback Value to return if $program is not found
491
     *
492
     * @return mixed A string with the full path or false if not found
493
     * @author Stig Bakken <[email protected]>
494
     */
495
    public static function which($program, $fallback = false)
496
    {
497
        // enforce API
498
        if (!is_string($program) || '' == $program) {
0 ignored issues
show
introduced by
The condition is_string($program) is always true.
Loading history...
499
            return $fallback;
500
        }
501
502
        // full path given
503
        if (basename($program) != $program) {
504
            $path_elements[] = \dirname($program);
0 ignored issues
show
Comprehensibility Best Practice introduced by
$path_elements was never initialized. Although not strictly required by PHP, it is generally a good practice to add $path_elements = array(); before regardless.
Loading history...
505
            $program         = basename($program);
506
        } else {
507
            $path = getenv('PATH');
508
            if (!$path) {
509
                $path = getenv('Path'); // some OSes are just stupid enough to do this
510
            }
511
512
            $path_elements = explode(PATH_SEPARATOR, $path);
513
        }
514
515
        if (OS_WINDOWS) {
516
            $exe_suffixes = getenv('PATHEXT') ? explode(PATH_SEPARATOR, getenv('PATHEXT')) : ['.exe', '.bat', '.cmd', '.com'];
517
            // allow passing a command.exe param
518
            if (false !== strpos($program, '.')) {
519
                array_unshift($exe_suffixes, '');
520
            }
521
        } else {
522
            $exe_suffixes = [''];
523
        }
524
525
        foreach ($exe_suffixes as $suff) {
526
            foreach ($path_elements as $dir) {
527
                $file = $dir . DIRECTORY_SEPARATOR . $program . $suff;
528
                // It's possible to run a .bat on Windows that is_executable
529
                // would return false for. The is_executable check is meaningless...
530
                if (OS_WINDOWS) {
531
                    return $file;
532
                } else {
533
                    if (is_executable($file)) {
534
                        return $file;
535
                    }
536
                }
537
            }
538
        }
539
        return $fallback;
540
    }
541
542
    /**
543
     * The "find" command
544
     *
545
     * Usage:
546
     *
547
     * System::find($dir);
548
     * System::find("$dir -type d");
549
     * System::find("$dir -type f");
550
     * System::find("$dir -name *.php");
551
     * System::find("$dir -name *.php -name *.htm*");
552
     * System::find("$dir -maxdepth 1");
553
     *
554
     * Params implemented:
555
     * $dir            -> Start the search at this directory
556
     * -type d         -> return only directories
557
     * -type f         -> return only files
558
     * -maxdepth <n>   -> max depth of recursion
559
     * -name <pattern> -> search pattern (bash style). Multiple -name param allowed
560
     *
561
     * @param mixed Either array or string with the command line
0 ignored issues
show
Bug introduced by
The type Either was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
562
     * @return array Array of found files
563
     */
564
    public static function find($args)
565
    {
566
        if (!is_array($args)) {
567
            $args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY);
568
        }
569
        $dir = realpath(array_shift($args));
570
        if (!$dir) {
571
            return [];
572
        }
573
        $patterns   = [];
574
        $depth      = 0;
575
        $do_files   = $do_dirs = true;
576
        $args_count = count($args);
577
        for ($i = 0; $i < $args_count; ++$i) {
578
            switch ($args[$i]) {
579
                case '-type':
580
                    if (in_array($args[$i + 1], ['d', 'f'])) {
581
                        if ('d' == $args[$i + 1]) {
582
                            $do_files = false;
583
                        } else {
584
                            $do_dirs = false;
585
                        }
586
                    }
587
                    $i++;
588
                    break;
589
                case '-name':
590
                    $name = preg_quote($args[$i + 1], '#');
591
                    // our magic characters ? and * have just been escaped,
592
                    // so now we change the escaped versions to PCRE operators
593
                    $name       = strtr($name, ['\?' => '.', '\*' => '.*']);
594
                    $patterns[] = '(' . $name . ')';
595
                    $i++;
596
                    break;
597
                case '-maxdepth':
598
                    $depth = $args[$i + 1];
599
                    break;
600
            }
601
        }
602
        $path = self::_dirToStruct($dir, $depth, 0, true);
603
        if ($do_files && $do_dirs) {
0 ignored issues
show
introduced by
The condition $do_dirs is always true.
Loading history...
604
            $files = array_merge($path['files'], $path['dirs']);
605
        } elseif ($do_dirs) {
606
            $files = $path['dirs'];
607
        } else {
608
            $files = $path['files'];
609
        }
610
        if (count($patterns)) {
611
            $dsq         = preg_quote(DIRECTORY_SEPARATOR, '#');
612
            $pattern     = '#(^|' . $dsq . ')' . implode('|', $patterns) . '($|' . $dsq . ')#';
613
            $ret         = [];
614
            $files_count = count($files);
615
            for ($i = 0; $i < $files_count; ++$i) {
616
                // only search in the part of the file below the current directory
617
                $filepart = basename($files[$i]);
618
                if (preg_match($pattern, $filepart)) {
619
                    $ret[] = $files[$i];
620
                }
621
            }
622
            return $ret;
623
        }
624
        return $files;
625
    }
626
}
627