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