Updated Drupal to 8.6. This goes with the following updates because it's possible...
[yaffs-website] / vendor / symfony / console / Helper / QuestionHelper.php
1 <?php
2
3 /*
4  * This file is part of the Symfony package.
5  *
6  * (c) Fabien Potencier <fabien@symfony.com>
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\Exception\InvalidArgumentException;
15 use Symfony\Component\Console\Exception\RuntimeException;
16 use Symfony\Component\Console\Formatter\OutputFormatter;
17 use Symfony\Component\Console\Formatter\OutputFormatterStyle;
18 use Symfony\Component\Console\Input\InputInterface;
19 use Symfony\Component\Console\Input\StreamableInputInterface;
20 use Symfony\Component\Console\Output\ConsoleOutputInterface;
21 use Symfony\Component\Console\Output\OutputInterface;
22 use Symfony\Component\Console\Question\ChoiceQuestion;
23 use Symfony\Component\Console\Question\Question;
24
25 /**
26  * The QuestionHelper class provides helpers to interact with the user.
27  *
28  * @author Fabien Potencier <fabien@symfony.com>
29  */
30 class QuestionHelper extends Helper
31 {
32     private $inputStream;
33     private static $shell;
34     private static $stty;
35
36     /**
37      * Asks a question to the user.
38      *
39      * @return mixed The user answer
40      *
41      * @throws RuntimeException If there is no data to read in the input stream
42      */
43     public function ask(InputInterface $input, OutputInterface $output, Question $question)
44     {
45         if ($output instanceof ConsoleOutputInterface) {
46             $output = $output->getErrorOutput();
47         }
48
49         if (!$input->isInteractive()) {
50             $default = $question->getDefault();
51
52             if (null !== $default && $question instanceof ChoiceQuestion) {
53                 $choices = $question->getChoices();
54
55                 if (!$question->isMultiselect()) {
56                     return isset($choices[$default]) ? $choices[$default] : $default;
57                 }
58
59                 $default = explode(',', $default);
60                 foreach ($default as $k => $v) {
61                     $v = trim($v);
62                     $default[$k] = isset($choices[$v]) ? $choices[$v] : $v;
63                 }
64             }
65
66             return $default;
67         }
68
69         if ($input instanceof StreamableInputInterface && $stream = $input->getStream()) {
70             $this->inputStream = $stream;
71         }
72
73         if (!$question->getValidator()) {
74             return $this->doAsk($output, $question);
75         }
76
77         $interviewer = function () use ($output, $question) {
78             return $this->doAsk($output, $question);
79         };
80
81         return $this->validateAttempts($interviewer, $output, $question);
82     }
83
84     /**
85      * Sets the input stream to read from when interacting with the user.
86      *
87      * This is mainly useful for testing purpose.
88      *
89      * @deprecated since version 3.2, to be removed in 4.0. Use
90      *             StreamableInputInterface::setStream() instead.
91      *
92      * @param resource $stream The input stream
93      *
94      * @throws InvalidArgumentException In case the stream is not a resource
95      */
96     public function setInputStream($stream)
97     {
98         @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.2 and will be removed in 4.0. Use %s::setStream() instead.', __METHOD__, StreamableInputInterface::class), E_USER_DEPRECATED);
99
100         if (!\is_resource($stream)) {
101             throw new InvalidArgumentException('Input stream must be a valid resource.');
102         }
103
104         $this->inputStream = $stream;
105     }
106
107     /**
108      * Returns the helper's input stream.
109      *
110      * @deprecated since version 3.2, to be removed in 4.0. Use
111      *             StreamableInputInterface::getStream() instead.
112      *
113      * @return resource
114      */
115     public function getInputStream()
116     {
117         if (0 === \func_num_args() || func_get_arg(0)) {
118             @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.2 and will be removed in 4.0. Use %s::getStream() instead.', __METHOD__, StreamableInputInterface::class), E_USER_DEPRECATED);
119         }
120
121         return $this->inputStream;
122     }
123
124     /**
125      * {@inheritdoc}
126      */
127     public function getName()
128     {
129         return 'question';
130     }
131
132     /**
133      * Prevents usage of stty.
134      */
135     public static function disableStty()
136     {
137         self::$stty = false;
138     }
139
140     /**
141      * Asks the question to the user.
142      *
143      * @return bool|mixed|string|null
144      *
145      * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
146      */
147     private function doAsk(OutputInterface $output, Question $question)
148     {
149         $this->writePrompt($output, $question);
150
151         $inputStream = $this->inputStream ?: STDIN;
152         $autocomplete = $question->getAutocompleterValues();
153
154         if (null === $autocomplete || !$this->hasSttyAvailable()) {
155             $ret = false;
156             if ($question->isHidden()) {
157                 try {
158                     $ret = trim($this->getHiddenResponse($output, $inputStream));
159                 } catch (RuntimeException $e) {
160                     if (!$question->isHiddenFallback()) {
161                         throw $e;
162                     }
163                 }
164             }
165
166             if (false === $ret) {
167                 $ret = fgets($inputStream, 4096);
168                 if (false === $ret) {
169                     throw new RuntimeException('Aborted');
170                 }
171                 $ret = trim($ret);
172             }
173         } else {
174             $ret = trim($this->autocomplete($output, $question, $inputStream, \is_array($autocomplete) ? $autocomplete : iterator_to_array($autocomplete, false)));
175         }
176
177         $ret = \strlen($ret) > 0 ? $ret : $question->getDefault();
178
179         if ($normalizer = $question->getNormalizer()) {
180             return $normalizer($ret);
181         }
182
183         return $ret;
184     }
185
186     /**
187      * Outputs the question prompt.
188      */
189     protected function writePrompt(OutputInterface $output, Question $question)
190     {
191         $message = $question->getQuestion();
192
193         if ($question instanceof ChoiceQuestion) {
194             $maxWidth = max(array_map(array($this, 'strlen'), array_keys($question->getChoices())));
195
196             $messages = (array) $question->getQuestion();
197             foreach ($question->getChoices() as $key => $value) {
198                 $width = $maxWidth - $this->strlen($key);
199                 $messages[] = '  [<info>'.$key.str_repeat(' ', $width).'</info>] '.$value;
200             }
201
202             $output->writeln($messages);
203
204             $message = $question->getPrompt();
205         }
206
207         $output->write($message);
208     }
209
210     /**
211      * Outputs an error message.
212      */
213     protected function writeError(OutputInterface $output, \Exception $error)
214     {
215         if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) {
216             $message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error');
217         } else {
218             $message = '<error>'.$error->getMessage().'</error>';
219         }
220
221         $output->writeln($message);
222     }
223
224     /**
225      * Autocompletes a question.
226      *
227      * @param OutputInterface $output
228      * @param Question        $question
229      * @param resource        $inputStream
230      * @param array           $autocomplete
231      *
232      * @return string
233      */
234     private function autocomplete(OutputInterface $output, Question $question, $inputStream, array $autocomplete)
235     {
236         $ret = '';
237
238         $i = 0;
239         $ofs = -1;
240         $matches = $autocomplete;
241         $numMatches = \count($matches);
242
243         $sttyMode = shell_exec('stty -g');
244
245         // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead)
246         shell_exec('stty -icanon -echo');
247
248         // Add highlighted text style
249         $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white'));
250
251         // Read a keypress
252         while (!feof($inputStream)) {
253             $c = fread($inputStream, 1);
254
255             // Backspace Character
256             if ("\177" === $c) {
257                 if (0 === $numMatches && 0 !== $i) {
258                     --$i;
259                     // Move cursor backwards
260                     $output->write("\033[1D");
261                 }
262
263                 if (0 === $i) {
264                     $ofs = -1;
265                     $matches = $autocomplete;
266                     $numMatches = \count($matches);
267                 } else {
268                     $numMatches = 0;
269                 }
270
271                 // Pop the last character off the end of our string
272                 $ret = substr($ret, 0, $i);
273             } elseif ("\033" === $c) {
274                 // Did we read an escape sequence?
275                 $c .= fread($inputStream, 2);
276
277                 // A = Up Arrow. B = Down Arrow
278                 if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) {
279                     if ('A' === $c[2] && -1 === $ofs) {
280                         $ofs = 0;
281                     }
282
283                     if (0 === $numMatches) {
284                         continue;
285                     }
286
287                     $ofs += ('A' === $c[2]) ? -1 : 1;
288                     $ofs = ($numMatches + $ofs) % $numMatches;
289                 }
290             } elseif (\ord($c) < 32) {
291                 if ("\t" === $c || "\n" === $c) {
292                     if ($numMatches > 0 && -1 !== $ofs) {
293                         $ret = $matches[$ofs];
294                         // Echo out remaining chars for current match
295                         $output->write(substr($ret, $i));
296                         $i = \strlen($ret);
297                     }
298
299                     if ("\n" === $c) {
300                         $output->write($c);
301                         break;
302                     }
303
304                     $numMatches = 0;
305                 }
306
307                 continue;
308             } else {
309                 $output->write($c);
310                 $ret .= $c;
311                 ++$i;
312
313                 $numMatches = 0;
314                 $ofs = 0;
315
316                 foreach ($autocomplete as $value) {
317                     // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
318                     if (0 === strpos($value, $ret)) {
319                         $matches[$numMatches++] = $value;
320                     }
321                 }
322             }
323
324             // Erase characters from cursor to end of line
325             $output->write("\033[K");
326
327             if ($numMatches > 0 && -1 !== $ofs) {
328                 // Save cursor position
329                 $output->write("\0337");
330                 // Write highlighted text
331                 $output->write('<hl>'.OutputFormatter::escapeTrailingBackslash(substr($matches[$ofs], $i)).'</hl>');
332                 // Restore cursor position
333                 $output->write("\0338");
334             }
335         }
336
337         // Reset stty so it behaves normally again
338         shell_exec(sprintf('stty %s', $sttyMode));
339
340         return $ret;
341     }
342
343     /**
344      * Gets a hidden response from user.
345      *
346      * @param OutputInterface $output      An Output instance
347      * @param resource        $inputStream The handler resource
348      *
349      * @return string The answer
350      *
351      * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
352      */
353     private function getHiddenResponse(OutputInterface $output, $inputStream)
354     {
355         if ('\\' === \DIRECTORY_SEPARATOR) {
356             $exe = __DIR__.'/../Resources/bin/hiddeninput.exe';
357
358             // handle code running from a phar
359             if ('phar:' === substr(__FILE__, 0, 5)) {
360                 $tmpExe = sys_get_temp_dir().'/hiddeninput.exe';
361                 copy($exe, $tmpExe);
362                 $exe = $tmpExe;
363             }
364
365             $value = rtrim(shell_exec($exe));
366             $output->writeln('');
367
368             if (isset($tmpExe)) {
369                 unlink($tmpExe);
370             }
371
372             return $value;
373         }
374
375         if ($this->hasSttyAvailable()) {
376             $sttyMode = shell_exec('stty -g');
377
378             shell_exec('stty -echo');
379             $value = fgets($inputStream, 4096);
380             shell_exec(sprintf('stty %s', $sttyMode));
381
382             if (false === $value) {
383                 throw new RuntimeException('Aborted');
384             }
385
386             $value = trim($value);
387             $output->writeln('');
388
389             return $value;
390         }
391
392         if (false !== $shell = $this->getShell()) {
393             $readCmd = 'csh' === $shell ? 'set mypassword = $<' : 'read -r mypassword';
394             $command = sprintf("/usr/bin/env %s -c 'stty -echo; %s; stty echo; echo \$mypassword'", $shell, $readCmd);
395             $value = rtrim(shell_exec($command));
396             $output->writeln('');
397
398             return $value;
399         }
400
401         throw new RuntimeException('Unable to hide the response.');
402     }
403
404     /**
405      * Validates an attempt.
406      *
407      * @param callable        $interviewer A callable that will ask for a question and return the result
408      * @param OutputInterface $output      An Output instance
409      * @param Question        $question    A Question instance
410      *
411      * @return mixed The validated response
412      *
413      * @throws \Exception In case the max number of attempts has been reached and no valid response has been given
414      */
415     private function validateAttempts(callable $interviewer, OutputInterface $output, Question $question)
416     {
417         $error = null;
418         $attempts = $question->getMaxAttempts();
419         while (null === $attempts || $attempts--) {
420             if (null !== $error) {
421                 $this->writeError($output, $error);
422             }
423
424             try {
425                 return \call_user_func($question->getValidator(), $interviewer());
426             } catch (RuntimeException $e) {
427                 throw $e;
428             } catch (\Exception $error) {
429             }
430         }
431
432         throw $error;
433     }
434
435     /**
436      * Returns a valid unix shell.
437      *
438      * @return string|bool The valid shell name, false in case no valid shell is found
439      */
440     private function getShell()
441     {
442         if (null !== self::$shell) {
443             return self::$shell;
444         }
445
446         self::$shell = false;
447
448         if (file_exists('/usr/bin/env')) {
449             // handle other OSs with bash/zsh/ksh/csh if available to hide the answer
450             $test = "/usr/bin/env %s -c 'echo OK' 2> /dev/null";
451             foreach (array('bash', 'zsh', 'ksh', 'csh') as $sh) {
452                 if ('OK' === rtrim(shell_exec(sprintf($test, $sh)))) {
453                     self::$shell = $sh;
454                     break;
455                 }
456             }
457         }
458
459         return self::$shell;
460     }
461
462     /**
463      * Returns whether Stty is available or not.
464      *
465      * @return bool
466      */
467     private function hasSttyAvailable()
468     {
469         if (null !== self::$stty) {
470             return self::$stty;
471         }
472
473         exec('stty 2>&1', $output, $exitcode);
474
475         return self::$stty = 0 === $exitcode;
476     }
477 }