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