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