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