PluginProbe ʕ •ᴥ•ʔ
Booking for Appointments and Events Calendar – Amelia / 1.2.20
Booking for Appointments and Events Calendar – Amelia v1.2.20
2.4.4 2.4.3 2.4.2 2.4.1 2.4 trunk 1.2.1 1.2.10 1.2.11 1.2.12 1.2.13 1.2.14 1.2.15 1.2.16 1.2.17 1.2.18 1.2.19 1.2.2 1.2.20 1.2.21 1.2.22 1.2.23 1.2.24 1.2.25 1.2.26 1.2.27 1.2.28 1.2.29 1.2.3 1.2.30 1.2.31 1.2.32 1.2.33 1.2.34 1.2.35 1.2.36 1.2.37 1.2.38 1.2.4 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 2.0 2.0.1 2.0.2 2.1 2.1.1 2.1.2 2.1.3 2.2 2.2.1 2.3
ameliabooking / vendor / sabre / vobject / lib / Cli.php
ameliabooking / vendor / sabre / vobject / lib Last commit date
Component 1 year ago ITip 1 year ago Parser 1 year ago Property 1 year ago Recur 1 year ago Splitter 1 year ago TimezoneGuesser 1 year ago timezonedata 1 year ago BirthdayCalendarGenerator.php 1 year ago Cli.php 1 year ago Component.php 1 year ago DateTimeParser.php 1 year ago Document.php 1 year ago ElementList.php 1 year ago EofException.php 1 year ago FreeBusyData.php 1 year ago FreeBusyGenerator.php 1 year ago InvalidDataException.php 1 year ago Node.php 1 year ago PHPUnitAssertions.php 1 year ago Parameter.php 1 year ago ParseException.php 1 year ago Property.php 1 year ago Reader.php 1 year ago Settings.php 1 year ago StringUtil.php 1 year ago TimeZoneUtil.php 1 year ago UUIDUtil.php 1 year ago VCardConverter.php 1 year ago Version.php 1 year ago Writer.php 1 year ago
Cli.php
706 lines
1 <?php
2
3 namespace Sabre\VObject;
4
5 use InvalidArgumentException;
6
7 /**
8 * This is the CLI interface for sabre-vobject.
9 *
10 * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
11 * @author Evert Pot (http://evertpot.com/)
12 * @license http://sabre.io/license/ Modified BSD License
13 */
14 class Cli
15 {
16 /**
17 * No output.
18 *
19 * @var bool
20 */
21 protected $quiet = false;
22
23 /**
24 * Help display.
25 *
26 * @var bool
27 */
28 protected $showHelp = false;
29
30 /**
31 * Whether to spit out 'mimedir' or 'json' format.
32 *
33 * @var string
34 */
35 protected $format;
36
37 /**
38 * JSON pretty print.
39 *
40 * @var bool
41 */
42 protected $pretty;
43
44 /**
45 * Source file.
46 *
47 * @var string
48 */
49 protected $inputPath;
50
51 /**
52 * Destination file.
53 *
54 * @var string
55 */
56 protected $outputPath;
57
58 /**
59 * output stream.
60 *
61 * @var resource
62 */
63 protected $stdout;
64
65 /**
66 * stdin.
67 *
68 * @var resource
69 */
70 protected $stdin;
71
72 /**
73 * stderr.
74 *
75 * @var resource
76 */
77 protected $stderr;
78
79 /**
80 * Input format (one of json or mimedir).
81 *
82 * @var string
83 */
84 protected $inputFormat;
85
86 /**
87 * Makes the parser less strict.
88 *
89 * @var bool
90 */
91 protected $forgiving = false;
92
93 /**
94 * Main function.
95 *
96 * @return int
97 */
98 public function main(array $argv)
99 {
100 // @codeCoverageIgnoreStart
101 // We cannot easily test this, so we'll skip it. Pretty basic anyway.
102
103 if (!$this->stderr) {
104 $this->stderr = fopen('php://stderr', 'w');
105 }
106 if (!$this->stdout) {
107 $this->stdout = fopen('php://stdout', 'w');
108 }
109 if (!$this->stdin) {
110 $this->stdin = fopen('php://stdin', 'r');
111 }
112
113 // @codeCoverageIgnoreEnd
114
115 try {
116 list($options, $positional) = $this->parseArguments($argv);
117
118 if (isset($options['q'])) {
119 $this->quiet = true;
120 }
121 $this->log($this->colorize('green', 'sabre/vobject ').$this->colorize('yellow', Version::VERSION));
122
123 foreach ($options as $name => $value) {
124 switch ($name) {
125 case 'q':
126 // Already handled earlier.
127 break;
128 case 'h':
129 case 'help':
130 $this->showHelp();
131
132 return 0;
133 break;
134 case 'format':
135 switch ($value) {
136 // jcard/jcal documents
137 case 'jcard':
138 case 'jcal':
139 // specific document versions
140 case 'vcard21':
141 case 'vcard30':
142 case 'vcard40':
143 case 'icalendar20':
144 // specific formats
145 case 'json':
146 case 'mimedir':
147 // icalendar/vcad
148 case 'icalendar':
149 case 'vcard':
150 $this->format = $value;
151 break;
152
153 default:
154 throw new InvalidArgumentException('Unknown format: '.$value);
155 }
156 break;
157 case 'pretty':
158 if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
159 $this->pretty = true;
160 }
161 break;
162 case 'forgiving':
163 $this->forgiving = true;
164 break;
165 case 'inputformat':
166 switch ($value) {
167 // json formats
168 case 'jcard':
169 case 'jcal':
170 case 'json':
171 $this->inputFormat = 'json';
172 break;
173
174 // mimedir formats
175 case 'mimedir':
176 case 'icalendar':
177 case 'vcard':
178 case 'vcard21':
179 case 'vcard30':
180 case 'vcard40':
181 case 'icalendar20':
182 $this->inputFormat = 'mimedir';
183 break;
184
185 default:
186 throw new InvalidArgumentException('Unknown format: '.$value);
187 }
188 break;
189 default:
190 throw new InvalidArgumentException('Unknown option: '.$name);
191 }
192 }
193
194 if (0 === count($positional)) {
195 $this->showHelp();
196
197 return 1;
198 }
199
200 if (1 === count($positional)) {
201 throw new InvalidArgumentException('Inputfile is a required argument');
202 }
203
204 if (count($positional) > 3) {
205 throw new InvalidArgumentException('Too many arguments');
206 }
207
208 if (!in_array($positional[0], ['validate', 'repair', 'convert', 'color'])) {
209 throw new InvalidArgumentException('Unknown command: '.$positional[0]);
210 }
211 } catch (InvalidArgumentException $e) {
212 $this->showHelp();
213 $this->log('Error: '.$e->getMessage(), 'red');
214
215 return 1;
216 }
217
218 $command = $positional[0];
219
220 $this->inputPath = $positional[1];
221 $this->outputPath = isset($positional[2]) ? $positional[2] : '-';
222
223 if ('-' !== $this->outputPath) {
224 $this->stdout = fopen($this->outputPath, 'w');
225 }
226
227 if (!$this->inputFormat) {
228 if ('.json' === substr($this->inputPath, -5)) {
229 $this->inputFormat = 'json';
230 } else {
231 $this->inputFormat = 'mimedir';
232 }
233 }
234 if (!$this->format) {
235 if ('.json' === substr($this->outputPath, -5)) {
236 $this->format = 'json';
237 } else {
238 $this->format = 'mimedir';
239 }
240 }
241
242 $realCode = 0;
243
244 try {
245 while ($input = $this->readInput()) {
246 $returnCode = $this->$command($input);
247 if (0 !== $returnCode) {
248 $realCode = $returnCode;
249 }
250 }
251 } catch (EofException $e) {
252 // end of file
253 } catch (\Exception $e) {
254 $this->log('Error: '.$e->getMessage(), 'red');
255
256 return 2;
257 }
258
259 return $realCode;
260 }
261
262 /**
263 * Shows the help message.
264 */
265 protected function showHelp()
266 {
267 $this->log('Usage:', 'yellow');
268 $this->log(' vobject [options] command [arguments]');
269 $this->log('');
270 $this->log('Options:', 'yellow');
271 $this->log($this->colorize('green', ' -q ')."Don't output anything.");
272 $this->log($this->colorize('green', ' -help -h ').'Display this help message.');
273 $this->log($this->colorize('green', ' --format ').'Convert to a specific format. Must be one of: vcard, vcard21,');
274 $this->log($this->colorize('green', ' --forgiving ').'Makes the parser less strict.');
275 $this->log(' vcard30, vcard40, icalendar20, jcal, jcard, json, mimedir.');
276 $this->log($this->colorize('green', ' --inputformat ').'If the input format cannot be guessed from the extension, it');
277 $this->log(' must be specified here.');
278 // Only PHP 5.4 and up
279 if (version_compare(PHP_VERSION, '5.4.0') >= 0) {
280 $this->log($this->colorize('green', ' --pretty ').'json pretty-print.');
281 }
282 $this->log('');
283 $this->log('Commands:', 'yellow');
284 $this->log($this->colorize('green', ' validate').' source_file Validates a file for correctness.');
285 $this->log($this->colorize('green', ' repair').' source_file [output_file] Repairs a file.');
286 $this->log($this->colorize('green', ' convert').' source_file [output_file] Converts a file.');
287 $this->log($this->colorize('green', ' color').' source_file Colorize a file, useful for debugging.');
288 $this->log(
289 <<<HELP
290
291 If source_file is set as '-', STDIN will be used.
292 If output_file is omitted, STDOUT will be used.
293 All other output is sent to STDERR.
294
295 HELP
296 );
297
298 $this->log('Examples:', 'yellow');
299 $this->log(' vobject convert contact.vcf contact.json');
300 $this->log(' vobject convert --format=vcard40 old.vcf new.vcf');
301 $this->log(' vobject convert --inputformat=json --format=mimedir - -');
302 $this->log(' vobject color calendar.ics');
303 $this->log('');
304 $this->log('https://github.com/fruux/sabre-vobject', 'purple');
305 }
306
307 /**
308 * Validates a VObject file.
309 *
310 * @return int
311 */
312 protected function validate(Component $vObj)
313 {
314 $returnCode = 0;
315
316 switch ($vObj->name) {
317 case 'VCALENDAR':
318 $this->log('iCalendar: '.(string) $vObj->VERSION);
319 break;
320 case 'VCARD':
321 $this->log('vCard: '.(string) $vObj->VERSION);
322 break;
323 }
324
325 $warnings = $vObj->validate();
326 if (!count($warnings)) {
327 $this->log(' No warnings!');
328 } else {
329 $levels = [
330 1 => 'REPAIRED',
331 2 => 'WARNING',
332 3 => 'ERROR',
333 ];
334 $returnCode = 2;
335 foreach ($warnings as $warn) {
336 $extra = '';
337 if ($warn['node'] instanceof Property) {
338 $extra = ' (property: "'.$warn['node']->name.'")';
339 }
340 $this->log(' ['.$levels[$warn['level']].'] '.$warn['message'].$extra);
341 }
342 }
343
344 return $returnCode;
345 }
346
347 /**
348 * Repairs a VObject file.
349 *
350 * @return int
351 */
352 protected function repair(Component $vObj)
353 {
354 $returnCode = 0;
355
356 switch ($vObj->name) {
357 case 'VCALENDAR':
358 $this->log('iCalendar: '.(string) $vObj->VERSION);
359 break;
360 case 'VCARD':
361 $this->log('vCard: '.(string) $vObj->VERSION);
362 break;
363 }
364
365 $warnings = $vObj->validate(Node::REPAIR);
366 if (!count($warnings)) {
367 $this->log(' No warnings!');
368 } else {
369 $levels = [
370 1 => 'REPAIRED',
371 2 => 'WARNING',
372 3 => 'ERROR',
373 ];
374 $returnCode = 2;
375 foreach ($warnings as $warn) {
376 $extra = '';
377 if ($warn['node'] instanceof Property) {
378 $extra = ' (property: "'.$warn['node']->name.'")';
379 }
380 $this->log(' ['.$levels[$warn['level']].'] '.$warn['message'].$extra);
381 }
382 }
383 fwrite($this->stdout, $vObj->serialize());
384
385 return $returnCode;
386 }
387
388 /**
389 * Converts a vObject file to a new format.
390 *
391 * @param Component $vObj
392 *
393 * @return int
394 */
395 protected function convert($vObj)
396 {
397 $json = false;
398 $convertVersion = null;
399 $forceInput = null;
400
401 switch ($this->format) {
402 case 'json':
403 $json = true;
404 if ('VCARD' === $vObj->name) {
405 $convertVersion = Document::VCARD40;
406 }
407 break;
408 case 'jcard':
409 $json = true;
410 $forceInput = 'VCARD';
411 $convertVersion = Document::VCARD40;
412 break;
413 case 'jcal':
414 $json = true;
415 $forceInput = 'VCALENDAR';
416 break;
417 case 'mimedir':
418 case 'icalendar':
419 case 'icalendar20':
420 case 'vcard':
421 break;
422 case 'vcard21':
423 $convertVersion = Document::VCARD21;
424 break;
425 case 'vcard30':
426 $convertVersion = Document::VCARD30;
427 break;
428 case 'vcard40':
429 $convertVersion = Document::VCARD40;
430 break;
431 }
432
433 if ($forceInput && $vObj->name !== $forceInput) {
434 throw new \Exception('You cannot convert a '.strtolower($vObj->name).' to '.$this->format);
435 }
436 if ($convertVersion) {
437 $vObj = $vObj->convert($convertVersion);
438 }
439 if ($json) {
440 $jsonOptions = 0;
441 if ($this->pretty) {
442 $jsonOptions = JSON_PRETTY_PRINT;
443 }
444 fwrite($this->stdout, json_encode($vObj->jsonSerialize(), $jsonOptions));
445 } else {
446 fwrite($this->stdout, $vObj->serialize());
447 }
448
449 return 0;
450 }
451
452 /**
453 * Colorizes a file.
454 *
455 * @param Component $vObj
456 */
457 protected function color($vObj)
458 {
459 $this->serializeComponent($vObj);
460 }
461
462 /**
463 * Returns an ansi color string for a color name.
464 *
465 * @param string $color
466 *
467 * @return string
468 */
469 protected function colorize($color, $str, $resetTo = 'default')
470 {
471 $colors = [
472 'cyan' => '1;36',
473 'red' => '1;31',
474 'yellow' => '1;33',
475 'blue' => '0;34',
476 'green' => '0;32',
477 'default' => '0',
478 'purple' => '0;35',
479 ];
480
481 return "\033[".$colors[$color].'m'.$str."\033[".$colors[$resetTo].'m';
482 }
483
484 /**
485 * Writes out a string in specific color.
486 *
487 * @param string $color
488 * @param string $str
489 */
490 protected function cWrite($color, $str)
491 {
492 fwrite($this->stdout, $this->colorize($color, $str));
493 }
494
495 protected function serializeComponent(Component $vObj)
496 {
497 $this->cWrite('cyan', 'BEGIN');
498 $this->cWrite('red', ':');
499 $this->cWrite('yellow', $vObj->name."\n");
500
501 /**
502 * Gives a component a 'score' for sorting purposes.
503 *
504 * This is solely used by the childrenSort method.
505 *
506 * A higher score means the item will be lower in the list.
507 * To avoid score collisions, each "score category" has a reasonable
508 * space to accommodate elements. The $key is added to the $score to
509 * preserve the original relative order of elements.
510 *
511 * @param int $key
512 * @param array $array
513 *
514 * @return int
515 */
516 $sortScore = function ($key, $array) {
517 if ($array[$key] instanceof Component) {
518 // We want to encode VTIMEZONE first, this is a personal
519 // preference.
520 if ('VTIMEZONE' === $array[$key]->name) {
521 $score = 300000000;
522
523 return $score + $key;
524 } else {
525 $score = 400000000;
526
527 return $score + $key;
528 }
529 } else {
530 // Properties get encoded first
531 // VCARD version 4.0 wants the VERSION property to appear first
532 if ($array[$key] instanceof Property) {
533 if ('VERSION' === $array[$key]->name) {
534 $score = 100000000;
535
536 return $score + $key;
537 } else {
538 // All other properties
539 $score = 200000000;
540
541 return $score + $key;
542 }
543 }
544 }
545 };
546
547 $children = $vObj->children();
548 $tmp = $children;
549 uksort(
550 $children,
551 function ($a, $b) use ($sortScore, $tmp) {
552 $sA = $sortScore($a, $tmp);
553 $sB = $sortScore($b, $tmp);
554
555 return $sA - $sB;
556 }
557 );
558
559 foreach ($children as $child) {
560 if ($child instanceof Component) {
561 $this->serializeComponent($child);
562 } else {
563 $this->serializeProperty($child);
564 }
565 }
566
567 $this->cWrite('cyan', 'END');
568 $this->cWrite('red', ':');
569 $this->cWrite('yellow', $vObj->name."\n");
570 }
571
572 /**
573 * Colorizes a property.
574 */
575 protected function serializeProperty(Property $property)
576 {
577 if ($property->group) {
578 $this->cWrite('default', $property->group);
579 $this->cWrite('red', '.');
580 }
581
582 $this->cWrite('yellow', $property->name);
583
584 foreach ($property->parameters as $param) {
585 $this->cWrite('red', ';');
586 $this->cWrite('blue', $param->serialize());
587 }
588 $this->cWrite('red', ':');
589
590 if ($property instanceof Property\Binary) {
591 $this->cWrite('default', 'embedded binary stripped. ('.strlen($property->getValue()).' bytes)');
592 } else {
593 $parts = $property->getParts();
594 $first1 = true;
595 // Looping through property values
596 foreach ($parts as $part) {
597 if ($first1) {
598 $first1 = false;
599 } else {
600 $this->cWrite('red', $property->delimiter);
601 }
602 $first2 = true;
603 // Looping through property sub-values
604 foreach ((array) $part as $subPart) {
605 if ($first2) {
606 $first2 = false;
607 } else {
608 // The sub-value delimiter is always comma
609 $this->cWrite('red', ',');
610 }
611
612 $subPart = strtr(
613 $subPart,
614 [
615 '\\' => $this->colorize('purple', '\\\\', 'green'),
616 ';' => $this->colorize('purple', '\;', 'green'),
617 ',' => $this->colorize('purple', '\,', 'green'),
618 "\n" => $this->colorize('purple', "\\n\n\t", 'green'),
619 "\r" => '',
620 ]
621 );
622
623 $this->cWrite('green', $subPart);
624 }
625 }
626 }
627 $this->cWrite('default', "\n");
628 }
629
630 /**
631 * Parses the list of arguments.
632 */
633 protected function parseArguments(array $argv)
634 {
635 $positional = [];
636 $options = [];
637
638 for ($ii = 0; $ii < count($argv); ++$ii) {
639 // Skipping the first argument.
640 if (0 === $ii) {
641 continue;
642 }
643
644 $v = $argv[$ii];
645
646 if ('--' === substr($v, 0, 2)) {
647 // This is a long-form option.
648 $optionName = substr($v, 2);
649 $optionValue = true;
650 if (strpos($optionName, '=')) {
651 list($optionName, $optionValue) = explode('=', $optionName);
652 }
653 $options[$optionName] = $optionValue;
654 } elseif ('-' === substr($v, 0, 1) && strlen($v) > 1) {
655 // This is a short-form option.
656 foreach (str_split(substr($v, 1)) as $option) {
657 $options[$option] = true;
658 }
659 } else {
660 $positional[] = $v;
661 }
662 }
663
664 return [$options, $positional];
665 }
666
667 protected $parser;
668
669 /**
670 * Reads the input file.
671 *
672 * @return Component
673 */
674 protected function readInput()
675 {
676 if (!$this->parser) {
677 if ('-' !== $this->inputPath) {
678 $this->stdin = fopen($this->inputPath, 'r');
679 }
680
681 if ('mimedir' === $this->inputFormat) {
682 $this->parser = new Parser\MimeDir($this->stdin, ($this->forgiving ? Reader::OPTION_FORGIVING : 0));
683 } else {
684 $this->parser = new Parser\Json($this->stdin, ($this->forgiving ? Reader::OPTION_FORGIVING : 0));
685 }
686 }
687
688 return $this->parser->parse();
689 }
690
691 /**
692 * Sends a message to STDERR.
693 *
694 * @param string $msg
695 */
696 protected function log($msg, $color = 'default')
697 {
698 if (!$this->quiet) {
699 if ('default' !== $color) {
700 $msg = $this->colorize($color, $msg);
701 }
702 fwrite($this->stderr, $msg."\n");
703 }
704 }
705 }
706