[ Index ]

PHP Cross Reference of phpBB-3.3.14-deutsch

title

Body

[close]

/vendor/symfony/console/Helper/ -> QuestionHelper.php (source)

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


Generated: Mon Nov 25 19:05:08 2024 Cross-referenced by PHPXref 0.7.1