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