Issues (50)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/helpers/Console.php (3 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
3
namespace carono\janitor\helpers;
4
5
class Console
6
{
7
    // foreground color control codes
8
    const FG_BLACK = 30;
9
    const FG_RED = 31;
10
    const FG_GREEN = 32;
11
    const FG_YELLOW = 33;
12
    const FG_BLUE = 34;
13
    const FG_PURPLE = 35;
14
    const FG_CYAN = 36;
15
    const FG_GREY = 37;
16
    // background color control codes
17
    const BG_BLACK = 40;
18
    const BG_RED = 41;
19
    const BG_GREEN = 42;
20
    const BG_YELLOW = 43;
21
    const BG_BLUE = 44;
22
    const BG_PURPLE = 45;
23
    const BG_CYAN = 46;
24
    const BG_GREY = 47;
25
    // fonts style control codes
26
    const RESET = 0;
27
    const NORMAL = 0;
28
    const BOLD = 1;
29
    const ITALIC = 3;
30
    const UNDERLINE = 4;
31
    const BLINK = 5;
32
    const NEGATIVE = 7;
33
    const CONCEALED = 8;
34
    const CROSSED_OUT = 9;
35
    const FRAMED = 51;
36
    const ENCIRCLED = 52;
37
    const OVERLINED = 53;
38
39
40
    /**
41
     * Moves the terminal cursor up by sending ANSI control code CUU to the terminal.
42
     * If the cursor is already at the edge of the screen, this has no effect.
43
     *
44
     * @param int $rows number of rows the cursor should be moved up
45
     */
46
    public static function moveCursorUp($rows = 1)
47
    {
48
        echo "\033[" . (int)$rows . 'A';
49
    }
50
51
    /**
52
     * Moves the terminal cursor down by sending ANSI control code CUD to the terminal.
53
     * If the cursor is already at the edge of the screen, this has no effect.
54
     *
55
     * @param int $rows number of rows the cursor should be moved down
56
     */
57
    public static function moveCursorDown($rows = 1)
58
    {
59
        echo "\033[" . (int)$rows . 'B';
60
    }
61
62
    /**
63
     * Moves the terminal cursor forward by sending ANSI control code CUF to the terminal.
64
     * If the cursor is already at the edge of the screen, this has no effect.
65
     *
66
     * @param int $steps number of steps the cursor should be moved forward
67
     */
68
    public static function moveCursorForward($steps = 1)
69
    {
70
        echo "\033[" . (int)$steps . 'C';
71
    }
72
73
    /**
74
     * Moves the terminal cursor backward by sending ANSI control code CUB to the terminal.
75
     * If the cursor is already at the edge of the screen, this has no effect.
76
     *
77
     * @param int $steps number of steps the cursor should be moved backward
78
     */
79
    public static function moveCursorBackward($steps = 1)
80
    {
81
        echo "\033[" . (int)$steps . 'D';
82
    }
83
84
    /**
85
     * Moves the terminal cursor to the beginning of the next line by sending ANSI control code CNL to the terminal.
86
     *
87
     * @param int $lines number of lines the cursor should be moved down
88
     */
89
    public static function moveCursorNextLine($lines = 1)
90
    {
91
        echo "\033[" . (int)$lines . 'E';
92
    }
93
94
    /**
95
     * Moves the terminal cursor to the beginning of the previous line by sending ANSI control code CPL to the terminal.
96
     *
97
     * @param int $lines number of lines the cursor should be moved up
98
     */
99
    public static function moveCursorPrevLine($lines = 1)
100
    {
101
        echo "\033[" . (int)$lines . 'F';
102
    }
103
104
    /**
105
     * Moves the cursor to an absolute position given as column and row by sending ANSI control code CUP or CHA to the terminal.
106
     *
107
     * @param int $column 1-based column number, 1 is the left edge of the screen.
108
     * @param int|null $row 1-based row number, 1 is the top edge of the screen. if not set, will move cursor only in current line.
109
     */
110
    public static function moveCursorTo($column, $row = null)
111
    {
112
        if ($row === null) {
113
            echo "\033[" . (int)$column . 'G';
114
        } else {
115
            echo "\033[" . (int)$row . ';' . (int)$column . 'H';
116
        }
117
    }
118
119
    /**
120
     * Scrolls whole page up by sending ANSI control code SU to the terminal.
121
     * New lines are added at the bottom. This is not supported by ANSI.SYS used in windows.
122
     *
123
     * @param int $lines number of lines to scroll up
124
     */
125
    public static function scrollUp($lines = 1)
126
    {
127
        echo "\033[" . (int)$lines . 'S';
128
    }
129
130
    /**
131
     * Scrolls whole page down by sending ANSI control code SD to the terminal.
132
     * New lines are added at the top. This is not supported by ANSI.SYS used in windows.
133
     *
134
     * @param int $lines number of lines to scroll down
135
     */
136
    public static function scrollDown($lines = 1)
137
    {
138
        echo "\033[" . (int)$lines . 'T';
139
    }
140
141
    /**
142
     * Saves the current cursor position by sending ANSI control code SCP to the terminal.
143
     * Position can then be restored with [[restoreCursorPosition()]].
144
     */
145
    public static function saveCursorPosition()
146
    {
147
        echo "\033[s";
148
    }
149
150
    /**
151
     * Restores the cursor position saved with [[saveCursorPosition()]] by sending ANSI control code RCP to the terminal.
152
     */
153
    public static function restoreCursorPosition()
154
    {
155
        echo "\033[u";
156
    }
157
158
    /**
159
     * Hides the cursor by sending ANSI DECTCEM code ?25l to the terminal.
160
     * Use [[showCursor()]] to bring it back.
161
     * Do not forget to show cursor when your application exits. Cursor might stay hidden in terminal after exit.
162
     */
163
    public static function hideCursor()
164
    {
165
        echo "\033[?25l";
166
    }
167
168
    /**
169
     * Will show a cursor again when it has been hidden by [[hideCursor()]]  by sending ANSI DECTCEM code ?25h to the terminal.
170
     */
171
    public static function showCursor()
172
    {
173
        echo "\033[?25h";
174
    }
175
176
    /**
177
     * Clears entire screen content by sending ANSI control code ED with argument 2 to the terminal.
178
     * Cursor position will not be changed.
179
     * **Note:** ANSI.SYS implementation used in windows will reset cursor position to upper left corner of the screen.
180
     */
181
    public static function clearScreen()
182
    {
183
        if (static::isRunningOnWindows()) {
184
            for ($i = 0; $i < 50; $i++) {
185
                echo "\n\r";
186
            }
187
        } else {
188
            echo "\033[2J";
189
        }
190
    }
191
192
    /**
193
     * Clears text from cursor to the beginning of the screen by sending ANSI control code ED with argument 1 to the terminal.
194
     * Cursor position will not be changed.
195
     */
196
    public static function clearScreenBeforeCursor()
197
    {
198
        echo "\033[1J";
199
    }
200
201
    /**
202
     * Clears text from cursor to the end of the screen by sending ANSI control code ED with argument 0 to the terminal.
203
     * Cursor position will not be changed.
204
     */
205
    public static function clearScreenAfterCursor()
206
    {
207
        echo "\033[0J";
208
    }
209
210
    /**
211
     * Clears the line, the cursor is currently on by sending ANSI control code EL with argument 2 to the terminal.
212
     * Cursor position will not be changed.
213
     */
214
    public static function clearLine()
215
    {
216
        echo "\033[2K";
217
    }
218
219
    /**
220
     * Clears text from cursor position to the beginning of the line by sending ANSI control code EL with argument 1 to the terminal.
221
     * Cursor position will not be changed.
222
     */
223
    public static function clearLineBeforeCursor()
224
    {
225
        echo "\033[1K";
226
    }
227
228
    /**
229
     * Clears text from cursor position to the end of the line by sending ANSI control code EL with argument 0 to the terminal.
230
     * Cursor position will not be changed.
231
     */
232
    public static function clearLineAfterCursor()
233
    {
234
        echo "\033[0K";
235
    }
236
237
    /**
238
     * Returns the ANSI format code.
239
     *
240
     * @param array $format An array containing formatting values.
241
     * You can pass any of the `FG_*`, `BG_*` and `TEXT_*` constants
242
     * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format.
243
     * @return string The ANSI format code according to the given formatting constants.
244
     */
245
    public static function ansiFormatCode($format)
246
    {
247
        return "\033[" . implode(';', $format) . 'm';
248
    }
249
250
    /**
251
     * Echoes an ANSI format code that affects the formatting of any text that is printed afterwards.
252
     *
253
     * @param array $format An array containing formatting values.
254
     * You can pass any of the `FG_*`, `BG_*` and `TEXT_*` constants
255
     * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format.
256
     * @see ansiFormatCode()
257
     * @see endAnsiFormat()
258
     */
259
    public static function beginAnsiFormat($format)
260
    {
261
        echo "\033[" . implode(';', $format) . 'm';
262
    }
263
264
    /**
265
     * Resets any ANSI format set by previous method [[beginAnsiFormat()]]
266
     * Any output after this will have default text format.
267
     * This is equal to calling.
268
     *
269
     * ```php
270
     * echo Console::ansiFormatCode([Console::RESET])
271
     * ```
272
     */
273
    public static function endAnsiFormat()
274
    {
275
        echo "\033[0m";
276
    }
277
278
    /**
279
     * Will return a string formatted with the given ANSI style.
280
     *
281
     * @param string $string the string to be formatted
282
     * @param array $format An array containing formatting values.
283
     * You can pass any of the `FG_*`, `BG_*` and `TEXT_*` constants
284
     * and also [[xtermFgColor]] and [[xtermBgColor]] to specify a format.
285
     * @return string
286
     */
287
    public static function ansiFormat($string, $format = [])
288
    {
289
        $code = implode(';', $format);
290
291
        return "\033[0m" . ($code !== '' ? "\033[" . $code . 'm' : '') . $string . "\033[0m";
292
    }
293
294
    /**
295
     * Returns the ansi format code for xterm foreground color.
296
     *
297
     * You can pass the return value of this to one of the formatting methods:
298
     * [[ansiFormat]], [[ansiFormatCode]], [[beginAnsiFormat]].
299
     *
300
     * @param int $colorCode xterm color code
301
     * @return string
302
     * @see http://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors
303
     */
304
    public static function xtermFgColor($colorCode)
305
    {
306
        return '38;5;' . $colorCode;
307
    }
308
309
    /**
310
     * Returns the ansi format code for xterm background color.
311
     *
312
     * You can pass the return value of this to one of the formatting methods:
313
     * [[ansiFormat]], [[ansiFormatCode]], [[beginAnsiFormat]].
314
     *
315
     * @param int $colorCode xterm color code
316
     * @return string
317
     * @see http://en.wikipedia.org/wiki/Talk:ANSI_escape_code#xterm-256colors
318
     */
319
    public static function xtermBgColor($colorCode)
320
    {
321
        return '48;5;' . $colorCode;
322
    }
323
324
    /**
325
     * Strips ANSI control codes from a string.
326
     *
327
     * @param string $string String to strip
328
     * @return string
329
     */
330
    public static function stripAnsiFormat($string)
331
    {
332
        return preg_replace('/\033\[[\d;?]*\w/', '', $string);
333
    }
334
335
    /**
336
     * Returns the length of the string without ANSI color codes.
337
     *
338
     * @param string $string the string to measure
339
     * @return int the length of the string not counting ANSI format characters
340
     */
341
    public static function ansiStrlen($string)
342
    {
343
        return mb_strlen(static::stripAnsiFormat($string));
344
    }
345
346
    /**
347
     * Converts a string to ansi formatted by replacing patterns like %y (for yellow) with ansi control codes.
348
     *
349
     * Uses almost the same syntax as https://github.com/pear/Console_Color2/blob/master/Console/Color2.php
350
     * The conversion table is: ('bold' meaning 'light' on some
351
     * terminals). It's almost the same conversion table irssi uses.
352
     * <pre>
353
     *                  text      text            background
354
     *      ------------------------------------------------
355
     *      %k %K %0    black     dark grey       black
356
     *      %r %R %1    red       bold red        red
357
     *      %g %G %2    green     bold green      green
358
     *      %y %Y %3    yellow    bold yellow     yellow
359
     *      %b %B %4    blue      bold blue       blue
360
     *      %m %M %5    magenta   bold magenta    magenta
361
     *      %p %P       magenta (think: purple)
362
     *      %c %C %6    cyan      bold cyan       cyan
363
     *      %w %W %7    white     bold white      white
364
     *
365
     *      %F     Blinking, Flashing
366
     *      %U     Underline
367
     *      %8     Reverse
368
     *      %_,%9  Bold
369
     *
370
     *      %n     Resets the color
371
     *      %%     A single %
372
     * </pre>
373
     * First param is the string to convert, second is an optional flag if
374
     * colors should be used. It defaults to true, if set to false, the
375
     * color codes will just be removed (And %% will be transformed into %)
376
     *
377
     * @param string $string String to convert
378
     * @param bool $colored Should the string be colored?
379
     * @return string
380
     */
381
    public static function renderColoredString($string, $colored = true)
382
    {
383
        // TODO rework/refactor according to https://github.com/yiisoft/yii2/issues/746
384
        static $conversions = [
385
            '%y' => [self::FG_YELLOW],
386
            '%g' => [self::FG_GREEN],
387
            '%b' => [self::FG_BLUE],
388
            '%r' => [self::FG_RED],
389
            '%p' => [self::FG_PURPLE],
390
            '%m' => [self::FG_PURPLE],
391
            '%c' => [self::FG_CYAN],
392
            '%w' => [self::FG_GREY],
393
            '%k' => [self::FG_BLACK],
394
            '%n' => [0], // reset
395
            '%Y' => [self::FG_YELLOW, self::BOLD],
396
            '%G' => [self::FG_GREEN, self::BOLD],
397
            '%B' => [self::FG_BLUE, self::BOLD],
398
            '%R' => [self::FG_RED, self::BOLD],
399
            '%P' => [self::FG_PURPLE, self::BOLD],
400
            '%M' => [self::FG_PURPLE, self::BOLD],
401
            '%C' => [self::FG_CYAN, self::BOLD],
402
            '%W' => [self::FG_GREY, self::BOLD],
403
            '%K' => [self::FG_BLACK, self::BOLD],
404
            '%N' => [0, self::BOLD],
405
            '%3' => [self::BG_YELLOW],
406
            '%2' => [self::BG_GREEN],
407
            '%4' => [self::BG_BLUE],
408
            '%1' => [self::BG_RED],
409
            '%5' => [self::BG_PURPLE],
410
            '%6' => [self::BG_CYAN],
411
            '%7' => [self::BG_GREY],
412
            '%0' => [self::BG_BLACK],
413
            '%F' => [self::BLINK],
414
            '%U' => [self::UNDERLINE],
415
            '%8' => [self::NEGATIVE],
416
            '%9' => [self::BOLD],
417
            '%_' => [self::BOLD],
418
        ];
419
420
        if ($colored) {
421
            $string = str_replace('%%', '% ', $string);
422
            foreach ($conversions as $key => $value) {
423
                $string = str_replace(
424
                    $key,
425
                    static::ansiFormatCode($value),
426
                    $string
427
                );
428
            }
429
            $string = str_replace('% ', '%', $string);
430
        } else {
431
            $string = preg_replace('/%((%)|.)/', '$2', $string);
432
        }
433
434
        return $string;
435
    }
436
437
    /**
438
     * Escapes % so they don't get interpreted as color codes when
439
     * the string is parsed by [[renderColoredString]].
440
     *
441
     * @param string $string String to escape
442
     *
443
     * @return string
444
     */
445
    public static function escape($string)
446
    {
447
        // TODO rework/refactor according to https://github.com/yiisoft/yii2/issues/746
448
        return str_replace('%', '%%', $string);
449
    }
450
451
    /**
452
     * Returns true if the stream supports colorization. ANSI colors are disabled if not supported by the stream.
453
     *
454
     * - windows without ansicon
455
     * - not tty consoles
456
     *
457
     * @param mixed $stream
458
     * @return bool true if the stream supports ANSI colors, otherwise false.
459
     */
460
    public static function streamSupportsAnsiColors($stream)
461
    {
462
        return DIRECTORY_SEPARATOR === '\\'
463
            ? getenv('ANSICON') !== false || getenv('ConEmuANSI') === 'ON'
464
            : function_exists('posix_isatty') && @posix_isatty($stream);
465
    }
466
467
    /**
468
     * Returns true if the console is running on windows.
469
     *
470
     * @return bool
471
     */
472
    public static function isRunningOnWindows()
473
    {
474
        return DIRECTORY_SEPARATOR === '\\';
475
    }
476
477
    /**
478
     * Returns terminal screen size.
479
     *
480
     * Usage:
481
     *
482
     * ```php
483
     * list($width, $height) = ConsoleHelper::getScreenSize();
484
     * ```
485
     *
486
     * @param bool $refresh whether to force checking and not re-use cached size value.
487
     * This is useful to detect changing window size while the application is running but may
488
     * not get up to date values on every terminal.
489
     * @return array|bool An array of ($width, $height) or false when it was not able to determine size.
490
     */
491
    public static function getScreenSize($refresh = false)
492
    {
493
        static $size;
494
        if ($size !== null && !$refresh) {
495
            return $size;
496
        }
497
498
        if (static::isRunningOnWindows()) {
499
            $output = [];
500
            exec('mode con', $output);
501
            if (isset($output[1]) && strpos($output[1], 'CON') !== false) {
502
                return $size = [(int)preg_replace('~\D~', '', $output[4]), (int)preg_replace('~\D~', '', $output[3])];
503
            }
504
        } else {
505
            // try stty if available
506
            $stty = [];
507
            if (exec('stty -a 2>&1', $stty)) {
508
                $stty = implode(' ', $stty);
509
510
                // Linux stty output
511 View Code Duplication
                if (preg_match('/rows\s+(\d+);\s*columns\s+(\d+);/mi', $stty, $matches)) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
512
                    return $size = [(int)$matches[2], (int)$matches[1]];
513
                }
514
515
                // MacOS stty output
516 View Code Duplication
                if (preg_match('/(\d+)\s+rows;\s*(\d+)\s+columns;/mi', $stty, $matches)) {
0 ignored issues
show
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
517
                    return $size = [(int)$matches[2], (int)$matches[1]];
518
                }
519
            }
520
521
            // fallback to tput, which may not be updated on terminal resize
522
            if (($width = (int)exec('tput cols 2>&1')) > 0 && ($height = (int)exec('tput lines 2>&1')) > 0) {
523
                return $size = [$width, $height];
524
            }
525
526
            // fallback to ENV variables, which may not be updated on terminal resize
527
            if (($width = (int)getenv('COLUMNS')) > 0 && ($height = (int)getenv('LINES')) > 0) {
528
                return $size = [$width, $height];
529
            }
530
        }
531
532
        return $size = false;
533
    }
534
535
    /**
536
     * Word wrap text with indentation to fit the screen size.
537
     *
538
     * If screen size could not be detected, or the indentation is greater than the screen size, the text will not be wrapped.
539
     *
540
     * The first line will **not** be indented, so `Console::wrapText("Lorem ipsum dolor sit amet.", 4)` will result in the
541
     * following output, given the screen width is 16 characters:
542
     *
543
     * ```
544
     * Lorem ipsum
545
     *     dolor sit
546
     *     amet.
547
     * ```
548
     *
549
     * @param string $text the text to be wrapped
550
     * @param int $indent number of spaces to use for indentation.
551
     * @param bool $refresh whether to force refresh of screen size.
552
     * This will be passed to [[getScreenSize()]].
553
     * @return string the wrapped text.
554
     * @since 2.0.4
555
     */
556
    public static function wrapText($text, $indent = 0, $refresh = false)
557
    {
558
        $size = static::getScreenSize($refresh);
559
        if ($size === false || $size[0] <= $indent) {
560
            return $text;
561
        }
562
        $pad = str_repeat(' ', $indent);
563
        $lines = explode("\n", wordwrap($text, $size[0] - $indent, "\n", true));
564
        $first = true;
565
        foreach ($lines as $i => $line) {
566
            if ($first) {
567
                $first = false;
568
                continue;
569
            }
570
            $lines[$i] = $pad . $line;
571
        }
572
573
        return implode("\n", $lines);
574
    }
575
576
    /**
577
     * Gets input from STDIN and returns a string right-trimmed for EOLs.
578
     *
579
     * @param bool $raw If set to true, returns the raw string without trimming
580
     * @return string the string read from stdin
581
     */
582
    public static function stdin($raw = false)
583
    {
584
        $result = $raw ? fgets(\STDIN) : rtrim(fgets(\STDIN), PHP_EOL);
585
        rewind(\STDIN);
586
        return $result;
587
    }
588
589
    /**
590
     * Prints a string to STDOUT.
591
     *
592
     * @param string $string the string to print
593
     * @return int|bool Number of bytes printed or false on error
594
     */
595
    public static function stdout($string)
596
    {
597
        return fwrite(\STDOUT, $string);
598
    }
599
600
    /**
601
     * Prints a string to STDERR.
602
     *
603
     * @param string $string the string to print
604
     * @return int|bool Number of bytes printed or false on error
605
     */
606
    public static function stderr($string)
607
    {
608
        return fwrite(\STDERR, $string);
609
    }
610
611
    /**
612
     * Asks the user for input. Ends when the user types a carriage return (PHP_EOL). Optionally, It also provides a
613
     * prompt.
614
     *
615
     * @param string $prompt the prompt to display before waiting for input (optional)
616
     * @return string the user's input
617
     */
618
    public static function input($prompt = null)
619
    {
620
        if (isset($prompt)) {
621
            static::stdout($prompt);
622
        }
623
624
        return static::stdin();
625
    }
626
627
    /**
628
     * Prints text to STDOUT appended with a carriage return (PHP_EOL).
629
     *
630
     * @param string $string the text to print
631
     * @return int|bool number of bytes printed or false on error.
632
     */
633
    public static function output($string = null)
634
    {
635
        return static::stdout($string . PHP_EOL);
636
    }
637
638
    /**
639
     * Prints text to STDERR appended with a carriage return (PHP_EOL).
640
     *
641
     * @param string $string the text to print
642
     * @return int|bool number of bytes printed or false on error.
643
     */
644
    public static function error($string = null)
645
    {
646
        return static::stderr($string . PHP_EOL);
647
    }
648
649
    /**
650
     * Prompts the user for input and validates it.
651
     *
652
     * @param string $text prompt string
653
     * @param array $options the options to validate the input:
654
     *
655
     * - `required`: whether it is required or not
656
     * - `default`: default value if no input is inserted by the user
657
     * - `pattern`: regular expression pattern to validate user input
658
     * - `validator`: a callable function to validate input. The function must accept two parameters:
659
     * - `input`: the user input to validate
660
     * - `error`: the error value passed by reference if validation failed.
661
     *
662
     * @return string the user input
663
     */
664
    public static function prompt($text, $options = [])
665
    {
666
        $options = ArrayHelper::merge(
667
            [
668
                'required' => false,
669
                'default' => null,
670
                'pattern' => null,
671
                'validator' => null,
672
                'error' => 'Invalid input.',
673
            ],
674
            $options
675
        );
676
        $error = null;
677
678
        top:
679
        $input = $options['default']
680
            ? static::input("$text [" . $options['default'] . '] ')
681
            : static::input("$text ");
682
683
        if ($input === '') {
684
            if (isset($options['default'])) {
685
                $input = $options['default'];
686
            } elseif ($options['required']) {
687
                static::output($options['error']);
688
                goto top;
689
            }
690
        } elseif ($options['pattern'] && !preg_match($options['pattern'], $input)) {
691
            static::output($options['error']);
692
            goto top;
693
        } elseif ($options['validator'] &&
694
            !call_user_func_array($options['validator'], [$input, &$error])
695
        ) {
696
            static::output(isset($error) ? $error : $options['error']);
697
            goto top;
698
        }
699
700
        return $input;
701
    }
702
703
    /**
704
     * Asks user to confirm by typing y or n.
705
     *
706
     * A typical usage looks like the following:
707
     *
708
     * ```php
709
     * if (Console::confirm("Are you sure?")) {
710
     *     echo "user typed yes\n";
711
     * } else {
712
     *     echo "user typed no\n";
713
     * }
714
     * ```
715
     *
716
     * @param string $message to print out before waiting for user input
717
     * @param bool $default this value is returned if no selection is made.
718
     * @return bool whether user confirmed
719
     */
720
    public static function confirm($message, $default = false)
721
    {
722
        while (true) {
723
            static::stdout($message . ' (yes|no) [' . ($default ? 'yes' : 'no') . ']:');
724
            $input = trim(static::stdin());
725
726
            if (empty($input)) {
727
                return $default;
728
            }
729
730
            if (!strcasecmp($input, 'y') || !strcasecmp($input, 'yes')) {
731
                return true;
732
            }
733
734
            if (!strcasecmp($input, 'n') || !strcasecmp($input, 'no')) {
735
                return false;
736
            }
737
        }
738
    }
739
740
    public static function selectBox($prompt, $items = [], $selected = [])
741
    {
742
        echo "\n" . $prompt . "\n\n";
743
        foreach ($items as $i => $name) {
744
            echo '[' . (in_array($i, (array)$selected) ? 'X' : ' ') . '] ' . $name . "\n";
745
        }
746
    }
747
748
    /**
749
     * Gives the user an option to choose from. Giving '?' as an input will show
750
     * a list of options to choose from and their explanations.
751
     *
752
     * @param string $prompt the prompt message
753
     * @param array $options Key-value array of options to choose from. Key is what is inputed and used, value is
754
     * what's displayed to end user by help command.
755
     *
756
     * @return string An option character the user chose
757
     */
758
    public static function select($prompt, $options = [])
759
    {
760
        top:
761
        static::stdout("$prompt [" . implode(',', array_keys($options)) . ',?]: ');
762
        $input = static::stdin();
763
        if ($input === '?') {
764
            foreach ($options as $key => $value) {
765
                static::output(" $key - $value");
766
            }
767
            static::output(' ? - Show help');
768
            goto top;
769
        } elseif (!array_key_exists($input, $options)) {
770
            goto top;
771
        }
772
773
        return $input;
774
    }
775
776
    private static $_progressStart;
777
    private static $_progressWidth;
778
    private static $_progressPrefix;
779
    private static $_progressEta;
780
    private static $_progressEtaLastDone = 0;
781
    private static $_progressEtaLastUpdate;
782
783
    /**
784
     * Starts display of a progress bar on screen.
785
     *
786
     * This bar will be updated by [[updateProgress()]] and my be ended by [[endProgress()]].
787
     *
788
     * The following example shows a simple usage of a progress bar:
789
     *
790
     * ```php
791
     * Console::startProgress(0, 1000);
792
     * for ($n = 1; $n <= 1000; $n++) {
793
     *     usleep(1000);
794
     *     Console::updateProgress($n, 1000);
795
     * }
796
     * Console::endProgress();
797
     * ```
798
     *
799
     * Git clone like progress (showing only status information):
800
     * ```php
801
     * Console::startProgress(0, 1000, 'Counting objects: ', false);
802
     * for ($n = 1; $n <= 1000; $n++) {
803
     *     usleep(1000);
804
     *     Console::updateProgress($n, 1000);
805
     * }
806
     * Console::endProgress("done." . PHP_EOL);
807
     * ```
808
     *
809
     * @param int $done the number of items that are completed.
810
     * @param int $total the total value of items that are to be done.
811
     * @param string $prefix an optional string to display before the progress bar.
812
     * Default to empty string which results in no prefix to be displayed.
813
     * @param int|bool $width optional width of the progressbar. This can be an integer representing
814
     * the number of characters to display for the progress bar or a float between 0 and 1 representing the
815
     * percentage of screen with the progress bar may take. It can also be set to false to disable the
816
     * bar and only show progress information like percent, number of items and ETA.
817
     * If not set, the bar will be as wide as the screen. Screen size will be detected using [[getScreenSize()]].
818
     * @see startProgress
819
     * @see updateProgress
820
     * @see endProgress
821
     */
822
    public static function startProgress($done, $total, $prefix = '', $width = null)
823
    {
824
        self::$_progressStart = time();
825
        self::$_progressWidth = $width;
826
        self::$_progressPrefix = $prefix;
827
        self::$_progressEta = null;
828
        self::$_progressEtaLastDone = 0;
829
        self::$_progressEtaLastUpdate = time();
830
831
        static::updateProgress($done, $total);
832
    }
833
834
    /**
835
     * Updates a progress bar that has been started by [[startProgress()]].
836
     *
837
     * @param int $done the number of items that are completed.
838
     * @param int $total the total value of items that are to be done.
839
     * @param string $prefix an optional string to display before the progress bar.
840
     * Defaults to null meaning the prefix specified by [[startProgress()]] will be used.
841
     * If prefix is specified it will update the prefix that will be used by later calls.
842
     * @see startProgress
843
     * @see endProgress
844
     */
845
    public static function updateProgress($done, $total, $prefix = null)
846
    {
847
        if ($prefix === null) {
848
            $prefix = self::$_progressPrefix;
849
        } else {
850
            self::$_progressPrefix = $prefix;
851
        }
852
        $width = static::getProgressbarWidth($prefix);
0 ignored issues
show
Since getProgressbarWidth() is declared private, calling it with static will lead to errors in possible sub-classes. You can either use self, or increase the visibility of getProgressbarWidth() to at least protected.

Let’s assume you have a class which uses late-static binding:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
}

public static function getSomeVariable()
{
    return static::getTemperature();
}

}

The code above will run fine in your PHP runtime. However, if you now create a sub-class and call the getSomeVariable() on that sub-class, you will receive a runtime error:

class YourSubClass extends YourClass {
      private static function getTemperature() {
        return "-182 °C";
    }
}

print YourSubClass::getSomeVariable(); // Will cause an access error.

In the case above, it makes sense to update SomeClass to use self instead:

class YourClass
{
    private static function getTemperature() {
        return "3422 °C";
    }

    public static function getSomeVariable()
    {
        return self::getTemperature();
    }
}
Loading history...
853
        $percent = ($total == 0) ? 1 : $done / $total;
854
        $info = sprintf('%d%% (%d/%d)', $percent * 100, $done, $total);
855
        self::setETA($done, $total);
856
        $info .= self::$_progressEta === null ? ' ETA: n/a' : sprintf(' ETA: %d sec.', self::$_progressEta);
857
858
        // Number extra characters outputted. These are opening [, closing ], and space before info
859
        // Since Windows uses \r\n\ for line endings, there's one more in the case
860
        $extraChars = static::isRunningOnWindows() ? 4 : 3;
861
        $width -= $extraChars + static::ansiStrlen($info);
862
        // skipping progress bar on very small display or if forced to skip
863
        if ($width < 5) {
864
            static::stdout("\r$prefix$info   ");
865
        } else {
866
            if ($percent < 0) {
867
                $percent = 0;
868
            } elseif ($percent > 1) {
869
                $percent = 1;
870
            }
871
            $bar = floor($percent * $width);
872
            $status = str_repeat('=', $bar);
873
            if ($bar < $width) {
874
                $status .= '>';
875
                $status .= str_repeat(' ', $width - $bar - 1);
876
            }
877
            static::stdout("\r$prefix" . "[$status] $info");
878
        }
879
        flush();
880
    }
881
882
    /**
883
     * Return width of the progressbar
884
     *
885
     * @param string $prefix an optional string to display before the progress bar.
886
     * @see updateProgress
887
     * @return int screen width
888
     * @since 2.0.14
889
     */
890
    private static function getProgressbarWidth($prefix)
891
    {
892
        $width = self::$_progressWidth;
893
894
        if ($width === false) {
895
            return 0;
896
        }
897
898
        $screenSize = static::getScreenSize(true);
899
        if ($screenSize === false && $width < 1) {
900
            return 0;
901
        }
902
903
        if ($width === null) {
904
            $width = $screenSize[0];
905
        } elseif ($width > 0 && $width < 1) {
906
            $width = floor($screenSize[0] * $width);
907
        }
908
909
        $width -= static::ansiStrlen($prefix);
910
911
        return $width;
912
    }
913
914
    /**
915
     * Calculate $_progressEta, $_progressEtaLastUpdate and $_progressEtaLastDone
916
     *
917
     * @param int $done the number of items that are completed.
918
     * @param int $total the total value of items that are to be done.
919
     * @see updateProgress
920
     * @since 2.0.14
921
     */
922
    private static function setETA($done, $total)
923
    {
924
        if ($done > $total || $done == 0) {
925
            self::$_progressEta = null;
926
            self::$_progressEtaLastUpdate = time();
927
            return;
928
        }
929
930
        if ($done < $total && (time() - self::$_progressEtaLastUpdate > 1 && $done > self::$_progressEtaLastDone)) {
931
            $rate = (time() - (self::$_progressEtaLastUpdate ?: self::$_progressStart)) / ($done - self::$_progressEtaLastDone);
932
            self::$_progressEta = $rate * ($total - $done);
933
            self::$_progressEtaLastUpdate = time();
934
            self::$_progressEtaLastDone = $done;
935
        }
936
    }
937
938
    /**
939
     * Ends a progress bar that has been started by [[startProgress()]].
940
     *
941
     * @param string|bool $remove This can be `false` to leave the progress bar on screen and just print a newline.
942
     * If set to `true`, the line of the progress bar will be cleared. This may also be a string to be displayed instead
943
     * of the progress bar.
944
     * @param bool $keepPrefix whether to keep the prefix that has been specified for the progressbar when progressbar
945
     * gets removed. Defaults to true.
946
     * @see startProgress
947
     * @see updateProgress
948
     */
949
    public static function endProgress($remove = false, $keepPrefix = true)
950
    {
951
        if ($remove === false) {
952
            static::stdout(PHP_EOL);
953
        } else {
954
            if (static::streamSupportsAnsiColors(STDOUT)) {
955
                static::clearLine();
956
            }
957
            static::stdout("\r" . ($keepPrefix ? self::$_progressPrefix : '') . (is_string($remove) ? $remove : ''));
958
        }
959
        flush();
960
961
        self::$_progressStart = null;
962
        self::$_progressWidth = null;
963
        self::$_progressPrefix = '';
964
        self::$_progressEta = null;
965
        self::$_progressEtaLastDone = 0;
966
        self::$_progressEtaLastUpdate = null;
967
    }
968
}