PluginProbe ʕ •ᴥ•ʔ
Independent Analytics – WordPress Analytics Plugin / 1.28.0
Independent Analytics – WordPress Analytics Plugin v1.28.0
2.15.5 2.15.4 2.15.3 2.15.2 2.15.1 2.15.0 2.14.10 trunk 1.1 1.10 1.10.1 1.11 1.12 1.13 1.14 1.15 1.16 1.17 1.17.1 1.17.2 1.17.3 1.17.4 1.18 1.18.1 1.19.0 1.19.1 1.2 1.20.0 1.21.0 1.22.0 1.22.1 1.23.0 1.23.1 1.24.0 1.24.1 1.25.0 1.25.1 1.26.0 1.27.0 1.28.0 1.28.1 1.28.2 1.28.3 1.29.0 1.3 1.30.0 1.30.1 1.4 1.5 1.6 1.7 1.8 1.9 2.0.0 2.0.1 2.1.4 2.1.5 2.1.6 2.10.0 2.10.1 2.10.2 2.10.3 2.10.4 2.11.0 2.11.1 2.11.10 2.11.2 2.11.3 2.11.4 2.11.5 2.11.6 2.11.7 2.11.8 2.11.9 2.12.0 2.12.1 2.12.2 2.13.1 2.13.2 2.13.5 2.13.6 2.14.0 2.14.1 2.14.2 2.14.4 2.14.6 2.14.7 2.14.8 2.14.9 2.2.0 2.2.1 2.3.1 2.3.2 2.4.2 2.4.3 2.5.0 2.5.1 2.6.0 2.6.1 2.6.2 2.6.3 2.6.4 2.7.0 2.7.1 2.7.2 2.7.3 2.8.2 2.8.3 2.8.4 2.8.5 2.8.6 2.8.7 2.8.8 2.8.9 2.9.2 2.9.3 2.9.4 2.9.5 2.9.6 2.9.7
independent-analytics / vendor / symfony / console / Helper / QuestionHelper.php
independent-analytics / vendor / symfony / console / Helper Last commit date
DebugFormatterHelper.php 3 years ago DescriptorHelper.php 3 years ago Dumper.php 3 years ago FormatterHelper.php 3 years ago Helper.php 3 years ago HelperInterface.php 3 years ago HelperSet.php 3 years ago InputAwareHelper.php 3 years ago ProcessHelper.php 3 years ago ProgressBar.php 3 years ago ProgressIndicator.php 3 years ago QuestionHelper.php 3 years ago SymfonyQuestionHelper.php 3 years ago Table.php 3 years ago TableCell.php 3 years ago TableCellStyle.php 3 years ago TableRows.php 3 years ago TableSeparator.php 3 years ago TableStyle.php 3 years ago
QuestionHelper.php
513 lines
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 namespace IAWP_SCOPED\Symfony\Component\Console\Helper;
12
13 use IAWP_SCOPED\Symfony\Component\Console\Cursor;
14 use IAWP_SCOPED\Symfony\Component\Console\Exception\MissingInputException;
15 use IAWP_SCOPED\Symfony\Component\Console\Exception\RuntimeException;
16 use IAWP_SCOPED\Symfony\Component\Console\Formatter\OutputFormatter;
17 use IAWP_SCOPED\Symfony\Component\Console\Formatter\OutputFormatterStyle;
18 use IAWP_SCOPED\Symfony\Component\Console\Input\InputInterface;
19 use IAWP_SCOPED\Symfony\Component\Console\Input\StreamableInputInterface;
20 use IAWP_SCOPED\Symfony\Component\Console\Output\ConsoleOutputInterface;
21 use IAWP_SCOPED\Symfony\Component\Console\Output\ConsoleSectionOutput;
22 use IAWP_SCOPED\Symfony\Component\Console\Output\OutputInterface;
23 use IAWP_SCOPED\Symfony\Component\Console\Question\ChoiceQuestion;
24 use IAWP_SCOPED\Symfony\Component\Console\Question\Question;
25 use IAWP_SCOPED\Symfony\Component\Console\Terminal;
26 use function IAWP_SCOPED\Symfony\Component\String\s;
27 /**
28 * The QuestionHelper class provides helpers to interact with the user.
29 *
30 * @author Fabien Potencier <fabien@symfony.com>
31 */
32 class QuestionHelper extends Helper
33 {
34 /**
35 * @var resource|null
36 */
37 private $inputStream;
38 private static $stty = \true;
39 private static $stdinIsInteractive;
40 /**
41 * Asks a question to the user.
42 *
43 * @return mixed The user answer
44 *
45 * @throws RuntimeException If there is no data to read in the input stream
46 */
47 public function ask(InputInterface $input, OutputInterface $output, Question $question)
48 {
49 if ($output instanceof ConsoleOutputInterface) {
50 $output = $output->getErrorOutput();
51 }
52 if (!$input->isInteractive()) {
53 return $this->getDefaultAnswer($question);
54 }
55 if ($input instanceof StreamableInputInterface && ($stream = $input->getStream())) {
56 $this->inputStream = $stream;
57 }
58 try {
59 if (!$question->getValidator()) {
60 return $this->doAsk($output, $question);
61 }
62 $interviewer = function () use($output, $question) {
63 return $this->doAsk($output, $question);
64 };
65 return $this->validateAttempts($interviewer, $output, $question);
66 } catch (MissingInputException $exception) {
67 $input->setInteractive(\false);
68 if (null === ($fallbackOutput = $this->getDefaultAnswer($question))) {
69 throw $exception;
70 }
71 return $fallbackOutput;
72 }
73 }
74 /**
75 * {@inheritdoc}
76 */
77 public function getName()
78 {
79 return 'question';
80 }
81 /**
82 * Prevents usage of stty.
83 */
84 public static function disableStty()
85 {
86 self::$stty = \false;
87 }
88 /**
89 * Asks the question to the user.
90 *
91 * @return mixed
92 *
93 * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
94 */
95 private function doAsk(OutputInterface $output, Question $question)
96 {
97 $this->writePrompt($output, $question);
98 $inputStream = $this->inputStream ?: \STDIN;
99 $autocomplete = $question->getAutocompleterCallback();
100 if (null === $autocomplete || !self::$stty || !Terminal::hasSttyAvailable()) {
101 $ret = \false;
102 if ($question->isHidden()) {
103 try {
104 $hiddenResponse = $this->getHiddenResponse($output, $inputStream, $question->isTrimmable());
105 $ret = $question->isTrimmable() ? \trim($hiddenResponse) : $hiddenResponse;
106 } catch (RuntimeException $e) {
107 if (!$question->isHiddenFallback()) {
108 throw $e;
109 }
110 }
111 }
112 if (\false === $ret) {
113 $isBlocked = \stream_get_meta_data($inputStream)['blocked'] ?? \true;
114 if (!$isBlocked) {
115 \stream_set_blocking($inputStream, \true);
116 }
117 $ret = $this->readInput($inputStream, $question);
118 if (!$isBlocked) {
119 \stream_set_blocking($inputStream, \false);
120 }
121 if (\false === $ret) {
122 throw new MissingInputException('Aborted.');
123 }
124 if ($question->isTrimmable()) {
125 $ret = \trim($ret);
126 }
127 }
128 } else {
129 $autocomplete = $this->autocomplete($output, $question, $inputStream, $autocomplete);
130 $ret = $question->isTrimmable() ? \trim($autocomplete) : $autocomplete;
131 }
132 if ($output instanceof ConsoleSectionOutput) {
133 $output->addContent($ret);
134 }
135 $ret = \strlen($ret) > 0 ? $ret : $question->getDefault();
136 if ($normalizer = $question->getNormalizer()) {
137 return $normalizer($ret);
138 }
139 return $ret;
140 }
141 /**
142 * @return mixed
143 */
144 private function getDefaultAnswer(Question $question)
145 {
146 $default = $question->getDefault();
147 if (null === $default) {
148 return $default;
149 }
150 if ($validator = $question->getValidator()) {
151 return \call_user_func($question->getValidator(), $default);
152 } elseif ($question instanceof ChoiceQuestion) {
153 $choices = $question->getChoices();
154 if (!$question->isMultiselect()) {
155 return $choices[$default] ?? $default;
156 }
157 $default = \explode(',', $default);
158 foreach ($default as $k => $v) {
159 $v = $question->isTrimmable() ? \trim($v) : $v;
160 $default[$k] = $choices[$v] ?? $v;
161 }
162 }
163 return $default;
164 }
165 /**
166 * Outputs the question prompt.
167 */
168 protected function writePrompt(OutputInterface $output, Question $question)
169 {
170 $message = $question->getQuestion();
171 if ($question instanceof ChoiceQuestion) {
172 $output->writeln(\array_merge([$question->getQuestion()], $this->formatChoiceQuestionChoices($question, 'info')));
173 $message = $question->getPrompt();
174 }
175 $output->write($message);
176 }
177 /**
178 * @return string[]
179 */
180 protected function formatChoiceQuestionChoices(ChoiceQuestion $question, string $tag)
181 {
182 $messages = [];
183 $maxWidth = \max(\array_map([__CLASS__, 'width'], \array_keys($choices = $question->getChoices())));
184 foreach ($choices as $key => $value) {
185 $padding = \str_repeat(' ', $maxWidth - self::width($key));
186 $messages[] = \sprintf(" [<{$tag}>%s{$padding}</{$tag}>] %s", $key, $value);
187 }
188 return $messages;
189 }
190 /**
191 * Outputs an error message.
192 */
193 protected function writeError(OutputInterface $output, \Exception $error)
194 {
195 if (null !== $this->getHelperSet() && $this->getHelperSet()->has('formatter')) {
196 $message = $this->getHelperSet()->get('formatter')->formatBlock($error->getMessage(), 'error');
197 } else {
198 $message = '<error>' . $error->getMessage() . '</error>';
199 }
200 $output->writeln($message);
201 }
202 /**
203 * Autocompletes a question.
204 *
205 * @param resource $inputStream
206 */
207 private function autocomplete(OutputInterface $output, Question $question, $inputStream, callable $autocomplete) : string
208 {
209 $cursor = new Cursor($output, $inputStream);
210 $fullChoice = '';
211 $ret = '';
212 $i = 0;
213 $ofs = -1;
214 $matches = $autocomplete($ret);
215 $numMatches = \count($matches);
216 $sttyMode = \shell_exec('stty -g');
217 $isStdin = 'php://stdin' === (\stream_get_meta_data($inputStream)['uri'] ?? null);
218 $r = [$inputStream];
219 $w = [];
220 // Disable icanon (so we can fread each keypress) and echo (we'll do echoing here instead)
221 \shell_exec('stty -icanon -echo');
222 // Add highlighted text style
223 $output->getFormatter()->setStyle('hl', new OutputFormatterStyle('black', 'white'));
224 // Read a keypress
225 while (!\feof($inputStream)) {
226 while ($isStdin && 0 === @\stream_select($r, $w, $w, 0, 100)) {
227 // Give signal handlers a chance to run
228 $r = [$inputStream];
229 }
230 $c = \fread($inputStream, 1);
231 // as opposed to fgets(), fread() returns an empty string when the stream content is empty, not false.
232 if (\false === $c || '' === $ret && '' === $c && null === $question->getDefault()) {
233 \shell_exec('stty ' . $sttyMode);
234 throw new MissingInputException('Aborted.');
235 } elseif ("" === $c) {
236 // Backspace Character
237 if (0 === $numMatches && 0 !== $i) {
238 --$i;
239 $cursor->moveLeft(s($fullChoice)->slice(-1)->width(\false));
240 $fullChoice = self::substr($fullChoice, 0, $i);
241 }
242 if (0 === $i) {
243 $ofs = -1;
244 $matches = $autocomplete($ret);
245 $numMatches = \count($matches);
246 } else {
247 $numMatches = 0;
248 }
249 // Pop the last character off the end of our string
250 $ret = self::substr($ret, 0, $i);
251 } elseif ("\x1b" === $c) {
252 // Did we read an escape sequence?
253 $c .= \fread($inputStream, 2);
254 // A = Up Arrow. B = Down Arrow
255 if (isset($c[2]) && ('A' === $c[2] || 'B' === $c[2])) {
256 if ('A' === $c[2] && -1 === $ofs) {
257 $ofs = 0;
258 }
259 if (0 === $numMatches) {
260 continue;
261 }
262 $ofs += 'A' === $c[2] ? -1 : 1;
263 $ofs = ($numMatches + $ofs) % $numMatches;
264 }
265 } elseif (\ord($c) < 32) {
266 if ("\t" === $c || "\n" === $c) {
267 if ($numMatches > 0 && -1 !== $ofs) {
268 $ret = (string) $matches[$ofs];
269 // Echo out remaining chars for current match
270 $remainingCharacters = \substr($ret, \strlen(\trim($this->mostRecentlyEnteredValue($fullChoice))));
271 $output->write($remainingCharacters);
272 $fullChoice .= $remainingCharacters;
273 $i = \false === ($encoding = \mb_detect_encoding($fullChoice, null, \true)) ? \strlen($fullChoice) : \mb_strlen($fullChoice, $encoding);
274 $matches = \array_filter($autocomplete($ret), function ($match) use($ret) {
275 return '' === $ret || \str_starts_with($match, $ret);
276 });
277 $numMatches = \count($matches);
278 $ofs = -1;
279 }
280 if ("\n" === $c) {
281 $output->write($c);
282 break;
283 }
284 $numMatches = 0;
285 }
286 continue;
287 } else {
288 if ("\x80" <= $c) {
289 $c .= \fread($inputStream, ["\xc0" => 1, "\xd0" => 1, "\xe0" => 2, "\xf0" => 3][$c & "\xf0"]);
290 }
291 $output->write($c);
292 $ret .= $c;
293 $fullChoice .= $c;
294 ++$i;
295 $tempRet = $ret;
296 if ($question instanceof ChoiceQuestion && $question->isMultiselect()) {
297 $tempRet = $this->mostRecentlyEnteredValue($fullChoice);
298 }
299 $numMatches = 0;
300 $ofs = 0;
301 foreach ($autocomplete($ret) as $value) {
302 // If typed characters match the beginning chunk of value (e.g. [AcmeDe]moBundle)
303 if (\str_starts_with($value, $tempRet)) {
304 $matches[$numMatches++] = $value;
305 }
306 }
307 }
308 $cursor->clearLineAfter();
309 if ($numMatches > 0 && -1 !== $ofs) {
310 $cursor->savePosition();
311 // Write highlighted text, complete the partially entered response
312 $charactersEntered = \strlen(\trim($this->mostRecentlyEnteredValue($fullChoice)));
313 $output->write('<hl>' . OutputFormatter::escapeTrailingBackslash(\substr($matches[$ofs], $charactersEntered)) . '</hl>');
314 $cursor->restorePosition();
315 }
316 }
317 // Reset stty so it behaves normally again
318 \shell_exec('stty ' . $sttyMode);
319 return $fullChoice;
320 }
321 private function mostRecentlyEnteredValue(string $entered) : string
322 {
323 // Determine the most recent value that the user entered
324 if (!\str_contains($entered, ',')) {
325 return $entered;
326 }
327 $choices = \explode(',', $entered);
328 if ('' !== ($lastChoice = \trim($choices[\count($choices) - 1]))) {
329 return $lastChoice;
330 }
331 return $entered;
332 }
333 /**
334 * Gets a hidden response from user.
335 *
336 * @param resource $inputStream The handler resource
337 * @param bool $trimmable Is the answer trimmable
338 *
339 * @throws RuntimeException In case the fallback is deactivated and the response cannot be hidden
340 */
341 private function getHiddenResponse(OutputInterface $output, $inputStream, bool $trimmable = \true) : string
342 {
343 if ('\\' === \DIRECTORY_SEPARATOR) {
344 $exe = __DIR__ . '/../Resources/bin/hiddeninput.exe';
345 // handle code running from a phar
346 if ('phar:' === \substr(__FILE__, 0, 5)) {
347 $tmpExe = \sys_get_temp_dir() . '/hiddeninput.exe';
348 \copy($exe, $tmpExe);
349 $exe = $tmpExe;
350 }
351 $sExec = \shell_exec('"' . $exe . '"');
352 $value = $trimmable ? \rtrim($sExec) : $sExec;
353 $output->writeln('');
354 if (isset($tmpExe)) {
355 \unlink($tmpExe);
356 }
357 return $value;
358 }
359 if (self::$stty && Terminal::hasSttyAvailable()) {
360 $sttyMode = \shell_exec('stty -g');
361 \shell_exec('stty -echo');
362 } elseif ($this->isInteractiveInput($inputStream)) {
363 throw new RuntimeException('Unable to hide the response.');
364 }
365 $value = \fgets($inputStream, 4096);
366 if (self::$stty && Terminal::hasSttyAvailable()) {
367 \shell_exec('stty ' . $sttyMode);
368 }
369 if (\false === $value) {
370 throw new MissingInputException('Aborted.');
371 }
372 if ($trimmable) {
373 $value = \trim($value);
374 }
375 $output->writeln('');
376 return $value;
377 }
378 /**
379 * Validates an attempt.
380 *
381 * @param callable $interviewer A callable that will ask for a question and return the result
382 *
383 * @return mixed The validated response
384 *
385 * @throws \Exception In case the max number of attempts has been reached and no valid response has been given
386 */
387 private function validateAttempts(callable $interviewer, OutputInterface $output, Question $question)
388 {
389 $error = null;
390 $attempts = $question->getMaxAttempts();
391 while (null === $attempts || $attempts--) {
392 if (null !== $error) {
393 $this->writeError($output, $error);
394 }
395 try {
396 return $question->getValidator()($interviewer());
397 } catch (RuntimeException $e) {
398 throw $e;
399 } catch (\Exception $error) {
400 }
401 }
402 throw $error;
403 }
404 private function isInteractiveInput($inputStream) : bool
405 {
406 if ('php://stdin' !== (\stream_get_meta_data($inputStream)['uri'] ?? null)) {
407 return \false;
408 }
409 if (null !== self::$stdinIsInteractive) {
410 return self::$stdinIsInteractive;
411 }
412 if (\function_exists('stream_isatty')) {
413 return self::$stdinIsInteractive = @\stream_isatty(\fopen('php://stdin', 'r'));
414 }
415 if (\function_exists('posix_isatty')) {
416 return self::$stdinIsInteractive = @\posix_isatty(\fopen('php://stdin', 'r'));
417 }
418 if (!\function_exists('shell_exec')) {
419 return self::$stdinIsInteractive = \true;
420 }
421 return self::$stdinIsInteractive = (bool) \shell_exec('stty 2> ' . ('\\' === \DIRECTORY_SEPARATOR ? 'NUL' : '/dev/null'));
422 }
423 /**
424 * Reads one or more lines of input and returns what is read.
425 *
426 * @param resource $inputStream The handler resource
427 * @param Question $question The question being asked
428 *
429 * @return string|false The input received, false in case input could not be read
430 */
431 private function readInput($inputStream, Question $question)
432 {
433 if (!$question->isMultiline()) {
434 $cp = $this->setIOCodepage();
435 $ret = \fgets($inputStream, 4096);
436 return $this->resetIOCodepage($cp, $ret);
437 }
438 $multiLineStreamReader = $this->cloneInputStream($inputStream);
439 if (null === $multiLineStreamReader) {
440 return \false;
441 }
442 $ret = '';
443 $cp = $this->setIOCodepage();
444 while (\false !== ($char = \fgetc($multiLineStreamReader))) {
445 if (\PHP_EOL === "{$ret}{$char}") {
446 break;
447 }
448 $ret .= $char;
449 }
450 return $this->resetIOCodepage($cp, $ret);
451 }
452 /**
453 * Sets console I/O to the host code page.
454 *
455 * @return int Previous code page in IBM/EBCDIC format
456 */
457 private function setIOCodepage() : int
458 {
459 if (\function_exists('sapi_windows_cp_set')) {
460 $cp = \sapi_windows_cp_get();
461 \sapi_windows_cp_set(\sapi_windows_cp_get('oem'));
462 return $cp;
463 }
464 return 0;
465 }
466 /**
467 * Sets console I/O to the specified code page and converts the user input.
468 *
469 * @param string|false $input
470 *
471 * @return string|false
472 */
473 private function resetIOCodepage(int $cp, $input)
474 {
475 if (0 !== $cp) {
476 \sapi_windows_cp_set($cp);
477 if (\false !== $input && '' !== $input) {
478 $input = \sapi_windows_cp_conv(\sapi_windows_cp_get('oem'), $cp, $input);
479 }
480 }
481 return $input;
482 }
483 /**
484 * Clones an input stream in order to act on one instance of the same
485 * stream without affecting the other instance.
486 *
487 * @param resource $inputStream The handler resource
488 *
489 * @return resource|null The cloned resource, null in case it could not be cloned
490 */
491 private function cloneInputStream($inputStream)
492 {
493 $streamMetaData = \stream_get_meta_data($inputStream);
494 $seekable = $streamMetaData['seekable'] ?? \false;
495 $mode = $streamMetaData['mode'] ?? 'rb';
496 $uri = $streamMetaData['uri'] ?? null;
497 if (null === $uri) {
498 return null;
499 }
500 $cloneStream = \fopen($uri, $mode);
501 // For seekable and writable streams, add all the same data to the
502 // cloned stream and then seek to the same offset.
503 if (\true === $seekable && !\in_array($mode, ['r', 'rb', 'rt'])) {
504 $offset = \ftell($inputStream);
505 \rewind($inputStream);
506 \stream_copy_to_stream($inputStream, $cloneStream);
507 \fseek($inputStream, $offset);
508 \fseek($cloneStream, $offset);
509 }
510 return $cloneStream;
511 }
512 }
513