GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

DialogHelper::askAndValidate()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 10
Code Lines 5

Duplication

Lines 10
Ratio 100 %

Importance

Changes 0
Metric Value
cc 1
eloc 5
nc 1
nop 6
dl 10
loc 10
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
/*
4
 * This file is part of the Symfony package.
5
 *
6
 * (c) Fabien Potencier <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace Symfony\Component\Console\Helper;
13
14
use Symfony\Component\Console\Output\OutputInterface;
15
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
16
17
/**
18
 * The Dialog class provides helpers to interact with the user.
19
 *
20
 * @author Fabien Potencier <[email protected]>
21
 *
22
 * @deprecated Deprecated since version 2.5, to be removed in 3.0.
23
 *             Use the question helper instead.
24
 */
25
class DialogHelper extends InputAwareHelper
26
{
27
    private $inputStream;
28
    private static $shell;
29
    private static $stty;
30
31
    /**
32
     * Asks the user to select a value.
33
     *
34
     * @param OutputInterface $output       An Output instance
35
     * @param string|array    $question     The question to ask
36
     * @param array           $choices      List of choices to pick from
37
     * @param bool|string     $default      The default answer if the user enters nothing
38
     * @param bool|int        $attempts     Max number of times to ask before giving up (false by default, which means infinite)
39
     * @param string          $errorMessage Message which will be shown if invalid value from choice list would be picked
40
     * @param bool            $multiselect  Select more than one value separated by comma
41
     *
42
     * @return int|string|array The selected value or values (the key of the choices array)
43
     *
44
     * @throws \InvalidArgumentException
45
     */
46
    public function select(OutputInterface $output, $question, $choices, $default = null, $attempts = false, $errorMessage = 'Value "%s" is invalid', $multiselect = false)
47
    {
48
        $width = max(array_map('strlen', array_keys($choices)));
49
50
        $messages = (array) $question;
51
        foreach ($choices as $key => $value) {
52
            $messages[] = sprintf("  [<info>%-${width}s</info>] %s", $key, $value);
53
        }
54
55
        $output->writeln($messages);
56
57 View Code Duplication
        $result = $this->askAndValidate($output, '> ', function ($picked) use ($choices, $errorMessage, $multiselect) {
58
            // Collapse all spaces.
59
            $selectedChoices = str_replace(' ', '', $picked);
60
61
            if ($multiselect) {
62
                // Check for a separated comma values
63
                if (!preg_match('/^[a-zA-Z0-9_-]+(?:,[a-zA-Z0-9_-]+)*$/', $selectedChoices, $matches)) {
64
                    throw new \InvalidArgumentException(sprintf($errorMessage, $picked));
65
                }
66
                $selectedChoices = explode(',', $selectedChoices);
67
            } else {
68
                $selectedChoices = array($picked);
69
            }
70
71
            $multiselectChoices = array();
72
73
            foreach ($selectedChoices as $value) {
74
                if (empty($choices[$value])) {
75
                    throw new \InvalidArgumentException(sprintf($errorMessage, $value));
76
                }
77
                array_push($multiselectChoices, $value);
78
            }
79
80
            if ($multiselect) {
81
                return $multiselectChoices;
82
            }
83
84
            return $picked;
85
        }, $attempts, $default);
0 ignored issues
show
Bug introduced by
It seems like $attempts defined by parameter $attempts on line 46 can also be of type boolean; however, Symfony\Component\Consol...elper::askAndValidate() does only seem to accept false|integer, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
Bug introduced by
It seems like $default defined by parameter $default on line 46 can also be of type boolean; however, Symfony\Component\Consol...elper::askAndValidate() does only seem to accept string|null, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
86
87
        return $result;
88
    }
89
90
    /**
91
     * Asks a question to the user.
92
     *
93
     * @param OutputInterface $output       An Output instance
94
     * @param string|array    $question     The question to ask
95
     * @param string          $default      The default answer if none is given by the user
96
     * @param array           $autocomplete List of values to autocomplete
97
     *
98
     * @return string The user answer
99
     *
100
     * @throws \RuntimeException If there is no data to read in the input stream
101
     */
102
    public function ask(OutputInterface $output, $question, $default = null, array $autocomplete = null)
103
    {
104
        if ($this->input && !$this->input->isInteractive()) {
105
            return $default;
106
        }
107
108
        $output->write($question);
109
110
        $inputStream = $this->inputStream ?: STDIN;
111
112
        if (null === $autocomplete || !$this->hasSttyAvailable()) {
113
            $ret = fgets($inputStream, 4096);
114
            if (false === $ret) {
115
                throw new \RuntimeException('Aborted');
116
            }
117
            $ret = trim($ret);
118
        } else {
119
            $ret = '';
120
121
            $i = 0;
122
            $ofs = -1;
123
            $matches = $autocomplete;
124
            $numMatches = count($matches);
125
126
            $sttyMode = shell_exec('stty -g');
127
128
            // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead)
129
            shell_exec('stty -icanon -echo');
130
131
            // Add highlighted text style
132
            $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white'));
133
134
            // Read a keypress
135 View Code Duplication
            while (!feof($inputStream)) {
136
                $c = fread($inputStream, 1);
137
138
                // Backspace Character
139
                if ("\177" === $c) {
140
                    if (0 === $numMatches && 0 !== $i) {
141
                        $i--;
142
                        // Move cursor backwards
143
                        $output->write("\033[1D");
144
                    }
145
146
                    if ($i === 0) {
147
                        $ofs = -1;
148
                        $matches = $autocomplete;
149
                        $numMatches = count($matches);
150
                    } else {
151
                        $numMatches = 0;
152
                    }
153
154
                    // Pop the last character off the end of our string
155
                    $ret = substr($ret, 0, $i);
156
                } elseif ("\033" === $c) {
157
                    // Did we read an escape sequence?
158
                    $c .= fread($inputStream, 2);
159
160
                    // A = Up Arrow. B = Down Arrow
161
                    if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) {
162
                        if ('A' === $c[2] && -1 === $ofs) {
163
                            $ofs = 0;
164
                        }
165
166
                        if (0 === $numMatches) {
167
                            continue;
168
                        }
169
170
                        $ofs += ('A' === $c[2]) ? -1 : 1;
171
                        $ofs = ($numMatches + $ofs) % $numMatches;
172
                    }
173
                } elseif (ord($c) < 32) {
174
                    if ("\t" === $c || "\n" === $c) {
175
                        if ($numMatches > 0 && -1 !== $ofs) {
176
                            $ret = $matches[$ofs];
177
                            // Echo out remaining chars for current match
178
                            $output->write(substr($ret, $i));
179
                            $i = strlen($ret);
180
                        }
181
182
                        if ("\n" === $c) {
183
                            $output->write($c);
184
                            break;
185
                        }
186
187
                        $numMatches = 0;
188
                    }
189
190
                    continue;
191
                } else {
192
                    $output->write($c);
193
                    $ret .= $c;
194
                    $i++;
195
196
                    $numMatches = 0;
197
                    $ofs = 0;
198
199
                    foreach ($autocomplete as $value) {
200
                        // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
201
                        if (0 === strpos($value, $ret) && $i !== strlen($value)) {
202
                            $matches[$numMatches++] = $value;
203
                        }
204
                    }
205
                }
206
207
                // Erase characters from cursor to end of line
208
                $output->write("\033[K");
209
210
                if ($numMatches > 0 && -1 !== $ofs) {
211
                    // Save cursor position
212
                    $output->write("\0337");
213
                    // Write highlighted text
214
                    $output->write('<hl>'.substr($matches[$ofs], $i).'</hl>');
215
                    // Restore cursor position
216
                    $output->write("\0338");
217
                }
218
            }
219
220
            // Reset stty so it behaves normally again
221
            shell_exec(sprintf('stty %s', $sttyMode));
222
        }
223
224
        return strlen($ret) > 0 ? $ret : $default;
225
    }
226
227
    /**
228
     * Asks a confirmation to the user.
229
     *
230
     * The question will be asked until the user answers by nothing, yes, or no.
231
     *
232
     * @param OutputInterface $output   An Output instance
233
     * @param string|array    $question The question to ask
234
     * @param bool            $default  The default answer if the user enters nothing
235
     *
236
     * @return bool true if the user has confirmed, false otherwise
237
     */
238
    public function askConfirmation(OutputInterface $output, $question, $default = true)
239
    {
240
        $answer = 'z';
241
        while ($answer && !in_array(strtolower($answer[0]), array('y', 'n'))) {
242
            $answer = $this->ask($output, $question);
243
        }
244
245
        if (false === $default) {
246
            return $answer && 'y' == strtolower($answer[0]);
247
        }
248
249
        return !$answer || 'y' == strtolower($answer[0]);
250
    }
251
252
    /**
253
     * Asks a question to the user, the response is hidden.
254
     *
255
     * @param OutputInterface $output   An Output instance
256
     * @param string|array    $question The question
257
     * @param bool            $fallback In case the response can not be hidden, whether to fallback on non-hidden question or not
258
     *
259
     * @return string The answer
260
     *
261
     * @throws \RuntimeException In case the fallback is deactivated and the response can not be hidden
262
     */
263
    public function askHiddenResponse(OutputInterface $output, $question, $fallback = true)
264
    {
265 View Code Duplication
        if ('\\' === DIRECTORY_SEPARATOR) {
266
            $exe = __DIR__.'/../Resources/bin/hiddeninput.exe';
267
268
            // handle code running from a phar
269
            if ('phar:' === substr(__FILE__, 0, 5)) {
270
                $tmpExe = sys_get_temp_dir().'/hiddeninput.exe';
271
                copy($exe, $tmpExe);
272
                $exe = $tmpExe;
273
            }
274
275
            $output->write($question);
276
            $value = rtrim(shell_exec($exe));
277
            $output->writeln('');
278
279
            if (isset($tmpExe)) {
280
                unlink($tmpExe);
281
            }
282
283
            return $value;
284
        }
285
286 View Code Duplication
        if ($this->hasSttyAvailable()) {
287
            $output->write($question);
288
289
            $sttyMode = shell_exec('stty -g');
290
291
            shell_exec('stty -echo');
292
            $value = fgets($this->inputStream ?: STDIN, 4096);
293
            shell_exec(sprintf('stty %s', $sttyMode));
294
295
            if (false === $value) {
296
                throw new \RuntimeException('Aborted');
297
            }
298
299
            $value = trim($value);
300
            $output->writeln('');
301
302
            return $value;
303
        }
304
305 View Code Duplication
        if (false !== $shell = $this->getShell()) {
306
            $output->write($question);
307
            $readCmd = $shell === 'csh' ? 'set mypassword = $<' : 'read -r mypassword';
308
            $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd);
309
            $value = rtrim(shell_exec($command));
310
            $output->writeln('');
311
312
            return $value;
313
        }
314
315
        if ($fallback) {
316
            return $this->ask($output, $question);
317
        }
318
319
        throw new \RuntimeException('Unable to hide the response');
320
    }
321
322
    /**
323
     * Asks for a value and validates the response.
324
     *
325
     * The validator receives the data to validate. It must return the
326
     * validated data when the data is valid and throw an exception
327
     * otherwise.
328
     *
329
     * @param OutputInterface $output       An Output instance
330
     * @param string|array    $question     The question to ask
331
     * @param callable        $validator    A PHP callback
332
     * @param int|false       $attempts     Max number of times to ask before giving up (false by default, which means infinite)
333
     * @param string          $default      The default answer if none is given by the user
334
     * @param array           $autocomplete List of values to autocomplete
335
     *
336
     * @return mixed
337
     *
338
     * @throws \Exception When any of the validators return an error
339
     */
340 View Code Duplication
    public function askAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $default = null, array $autocomplete = null)
341
    {
342
        $that = $this;
343
344
        $interviewer = function () use ($output, $question, $default, $autocomplete, $that) {
345
            return $that->ask($output, $question, $default, $autocomplete);
346
        };
347
348
        return $this->validateAttempts($interviewer, $output, $validator, $attempts);
349
    }
350
351
    /**
352
     * Asks for a value, hide and validates the response.
353
     *
354
     * The validator receives the data to validate. It must return the
355
     * validated data when the data is valid and throw an exception
356
     * otherwise.
357
     *
358
     * @param OutputInterface $output    An Output instance
359
     * @param string|array    $question  The question to ask
360
     * @param callable        $validator A PHP callback
361
     * @param int|false       $attempts  Max number of times to ask before giving up (false by default, which means infinite)
362
     * @param bool            $fallback  In case the response can not be hidden, whether to fallback on non-hidden question or not
363
     *
364
     * @return string The response
365
     *
366
     * @throws \Exception        When any of the validators return an error
367
     * @throws \RuntimeException In case the fallback is deactivated and the response can not be hidden
368
     */
369 View Code Duplication
    public function askHiddenResponseAndValidate(OutputInterface $output, $question, $validator, $attempts = false, $fallback = true)
370
    {
371
        $that = $this;
372
373
        $interviewer = function () use ($output, $question, $fallback, $that) {
374
            return $that->askHiddenResponse($output, $question, $fallback);
375
        };
376
377
        return $this->validateAttempts($interviewer, $output, $validator, $attempts);
378
    }
379
380
    /**
381
     * Sets the input stream to read from when interacting with the user.
382
     *
383
     * This is mainly useful for testing purpose.
384
     *
385
     * @param resource $stream The input stream
386
     */
387
    public function setInputStream($stream)
388
    {
389
        $this->inputStream = $stream;
390
    }
391
392
    /**
393
     * Returns the helper's input stream.
394
     *
395
     * @return string
396
     */
397
    public function getInputStream()
398
    {
399
        return $this->inputStream;
400
    }
401
402
    /**
403
     * {@inheritdoc}
404
     */
405
    public function getName()
406
    {
407
        return 'dialog';
408
    }
409
410
    /**
411
     * Return a valid Unix shell.
412
     *
413
     * @return string|bool The valid shell name, false in case no valid shell is found
414
     */
415 View Code Duplication
    private function getShell()
416
    {
417
        if (null !== self::$shell) {
418
            return self::$shell;
419
        }
420
421
        self::$shell = false;
422
423
        if (file_exists('/usr/bin/env')) {
424
            // handle other OSs with bash/zsh/ksh/csh if available to hide the answer
425
            $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null";
426
            foreach (array('bash', 'zsh', 'ksh', 'csh') as $sh) {
427
                if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) {
428
                    self::$shell = $sh;
429
                    break;
430
                }
431
            }
432
        }
433
434
        return self::$shell;
435
    }
436
437 View Code Duplication
    private function hasSttyAvailable()
438
    {
439
        if (null !== self::$stty) {
440
            return self::$stty;
441
        }
442
443
        exec('stty 2>&1', $output, $exitcode);
444
445
        return self::$stty = $exitcode === 0;
446
    }
447
448
    /**
449
     * Validate an attempt.
450
     *
451
     * @param callable        $interviewer A callable that will ask for a question and return the result
452
     * @param OutputInterface $output      An Output instance
453
     * @param callable        $validator   A PHP callback
454
     * @param int|false       $attempts    Max number of times to ask before giving up ; false will ask infinitely
455
     *
456
     * @return string The validated response
457
     *
458
     * @throws \Exception In case the max number of attempts has been reached and no valid response has been given
459
     */
460
    private function validateAttempts($interviewer, OutputInterface $output, $validator, $attempts)
461
    {
462
        $error = null;
463
        while (false === $attempts || $attempts--) {
464
            if (null !== $error) {
465
                $output->writeln($this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error'));
466
            }
467
468
            try {
469
                return call_user_func($validator, $interviewer());
470
            } catch (\Exception $error) {
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
471
            }
472
        }
473
474
        throw $error;
475
    }
476
}
477