1<?php 2 3namespace Sabre\VObject\Parser; 4 5use Sabre\VObject\Component\VCalendar; 6use Sabre\VObject\Component\VCard; 7use Sabre\VObject\Document; 8use Sabre\VObject\EofException; 9use Sabre\VObject\ParseException; 10 11/** 12 * Json Parser. 13 * 14 * This parser parses both the jCal and jCard formats. 15 * 16 * @copyright Copyright (C) fruux GmbH (https://fruux.com/) 17 * @author Evert Pot (http://evertpot.com/) 18 * @license http://sabre.io/license/ Modified BSD License 19 */ 20class Json extends Parser 21{ 22 /** 23 * The input data. 24 * 25 * @var array 26 */ 27 protected $input; 28 29 /** 30 * Root component. 31 * 32 * @var Document 33 */ 34 protected $root; 35 36 /** 37 * This method starts the parsing process. 38 * 39 * If the input was not supplied during construction, it's possible to pass 40 * it here instead. 41 * 42 * If either input or options are not supplied, the defaults will be used. 43 * 44 * @param resource|string|array|null $input 45 * @param int $options 46 * 47 * @return \Sabre\VObject\Document 48 */ 49 public function parse($input = null, $options = 0) 50 { 51 if (!is_null($input)) { 52 $this->setInput($input); 53 } 54 if (is_null($this->input)) { 55 throw new EofException('End of input stream, or no input supplied'); 56 } 57 58 if (0 !== $options) { 59 $this->options = $options; 60 } 61 62 switch ($this->input[0]) { 63 case 'vcalendar': 64 $this->root = new VCalendar([], false); 65 break; 66 case 'vcard': 67 $this->root = new VCard([], false); 68 break; 69 default: 70 throw new ParseException('The root component must either be a vcalendar, or a vcard'); 71 } 72 foreach ($this->input[1] as $prop) { 73 $this->root->add($this->parseProperty($prop)); 74 } 75 if (isset($this->input[2])) { 76 foreach ($this->input[2] as $comp) { 77 $this->root->add($this->parseComponent($comp)); 78 } 79 } 80 81 // Resetting the input so we can throw an feof exception the next time. 82 $this->input = null; 83 84 return $this->root; 85 } 86 87 /** 88 * Parses a component. 89 * 90 * @param array $jComp 91 * 92 * @return \Sabre\VObject\Component 93 */ 94 public function parseComponent(array $jComp) 95 { 96 // We can remove $self from PHP 5.4 onward. 97 $self = $this; 98 99 $properties = array_map( 100 function ($jProp) use ($self) { 101 return $self->parseProperty($jProp); 102 }, 103 $jComp[1] 104 ); 105 106 if (isset($jComp[2])) { 107 $components = array_map( 108 function ($jComp) use ($self) { 109 return $self->parseComponent($jComp); 110 }, 111 $jComp[2] 112 ); 113 } else { 114 $components = []; 115 } 116 117 return $this->root->createComponent( 118 $jComp[0], 119 array_merge($properties, $components), 120 $defaults = false 121 ); 122 } 123 124 /** 125 * Parses properties. 126 * 127 * @param array $jProp 128 * 129 * @return \Sabre\VObject\Property 130 */ 131 public function parseProperty(array $jProp) 132 { 133 list( 134 $propertyName, 135 $parameters, 136 $valueType 137 ) = $jProp; 138 139 $propertyName = strtoupper($propertyName); 140 141 // This is the default class we would be using if we didn't know the 142 // value type. We're using this value later in this function. 143 $defaultPropertyClass = $this->root->getClassNameForPropertyName($propertyName); 144 145 $parameters = (array) $parameters; 146 147 $value = array_slice($jProp, 3); 148 149 $valueType = strtoupper($valueType); 150 151 if (isset($parameters['group'])) { 152 $propertyName = $parameters['group'].'.'.$propertyName; 153 unset($parameters['group']); 154 } 155 156 $prop = $this->root->createProperty($propertyName, null, $parameters, $valueType); 157 $prop->setJsonValue($value); 158 159 // We have to do something awkward here. FlatText as well as Text 160 // represents TEXT values. We have to normalize these here. In the 161 // future we can get rid of FlatText once we're allowed to break BC 162 // again. 163 if ('Sabre\VObject\Property\FlatText' === $defaultPropertyClass) { 164 $defaultPropertyClass = 'Sabre\VObject\Property\Text'; 165 } 166 167 // If the value type we received (e.g.: TEXT) was not the default value 168 // type for the given property (e.g.: BDAY), we need to add a VALUE= 169 // parameter. 170 if ($defaultPropertyClass !== get_class($prop)) { 171 $prop['VALUE'] = $valueType; 172 } 173 174 return $prop; 175 } 176 177 /** 178 * Sets the input data. 179 * 180 * @param resource|string|array $input 181 */ 182 public function setInput($input) 183 { 184 if (is_resource($input)) { 185 $input = stream_get_contents($input); 186 } 187 if (is_string($input)) { 188 $input = json_decode($input); 189 } 190 $this->input = $input; 191 } 192} 193