1<?php
2
3namespace Sabre\CalDAV\Xml\Property;
4
5use Sabre\Xml\Element;
6use Sabre\Xml\Reader;
7use Sabre\Xml\Writer;
8use Sabre\CalDAV\Plugin;
9use Sabre\CalDAV\SharingPlugin;
10
11/**
12 * Invite property
13 *
14 * This property encodes the 'invite' property, as defined by
15 * the 'caldav-sharing-02' spec, in the http://calendarserver.org/ns/
16 * namespace.
17 *
18 * @see https://trac.calendarserver.org/browser/CalendarServer/trunk/doc/Extensions/caldav-sharing-02.txt
19 * @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/).
20 * @author Evert Pot (http://evertpot.com/)
21 * @license http://sabre.io/license/ Modified BSD License
22 */
23class Invite implements Element {
24
25    /**
26     * The list of users a calendar has been shared to.
27     *
28     * @var array
29     */
30    protected $users;
31
32    /**
33     * The organizer contains information about the person who shared the
34     * object.
35     *
36     * @var array
37     */
38    protected $organizer;
39
40    /**
41     * Creates the property.
42     *
43     * Users is an array. Each element of the array has the following
44     * properties:
45     *
46     *   * href - Often a mailto: address
47     *   * commonName - Optional, for example a first and lastname for a user.
48     *   * status - One of the SharingPlugin::STATUS_* constants.
49     *   * readOnly - true or false
50     *   * summary - Optional, description of the share
51     *
52     * The organizer key is optional to specify. It's only useful when a
53     * 'sharee' requests the sharing information.
54     *
55     * The organizer may have the following properties:
56     *   * href - Often a mailto: address.
57     *   * commonName - Optional human-readable name.
58     *   * firstName - Optional first name.
59     *   * lastName - Optional last name.
60     *
61     * If you wonder why these two structures are so different, I guess a
62     * valid answer is that the current spec is still a draft.
63     *
64     * @param array $users
65     */
66    function __construct(array $users, array $organizer = null) {
67
68        $this->users = $users;
69        $this->organizer = $organizer;
70
71    }
72
73    /**
74     * Returns the list of users, as it was passed to the constructor.
75     *
76     * @return array
77     */
78    function getValue() {
79
80        return $this->users;
81
82    }
83
84    /**
85     * The xmlSerialize metod is called during xml writing.
86     *
87     * Use the $writer argument to write its own xml serialization.
88     *
89     * An important note: do _not_ create a parent element. Any element
90     * implementing XmlSerializble should only ever write what's considered
91     * its 'inner xml'.
92     *
93     * The parent of the current element is responsible for writing a
94     * containing element.
95     *
96     * This allows serializers to be re-used for different element names.
97     *
98     * If you are opening new elements, you must also close them again.
99     *
100     * @param Writer $writer
101     * @return void
102     */
103    function xmlSerialize(Writer $writer) {
104
105        $cs = '{' . Plugin::NS_CALENDARSERVER . '}';
106
107        if (!is_null($this->organizer)) {
108
109            $writer->startElement($cs . 'organizer');
110            $writer->writeElement('{DAV:}href', $this->organizer['href']);
111
112            if (isset($this->organizer['commonName']) && $this->organizer['commonName']) {
113                $writer->writeElement($cs . 'common-name', $this->organizer['commonName']);
114            }
115            if (isset($this->organizer['firstName']) && $this->organizer['firstName']) {
116                $writer->writeElement($cs . 'first-name', $this->organizer['firstName']);
117            }
118            if (isset($this->organizer['lastName']) && $this->organizer['lastName']) {
119                $writer->writeElement($cs . 'last-name', $this->organizer['lastName']);
120            }
121            $writer->endElement(); // organizer
122
123        }
124
125        foreach ($this->users as $user) {
126
127            $writer->startElement($cs . 'user');
128            $writer->writeElement('{DAV:}href', $user['href']);
129            if (isset($user['commonName']) && $user['commonName']) {
130                $writer->writeElement($cs . 'common-name', $user['commonName']);
131            }
132            switch ($user['status']) {
133
134                case SharingPlugin::STATUS_ACCEPTED :
135                    $writer->writeElement($cs . 'invite-accepted');
136                    break;
137                case SharingPlugin::STATUS_DECLINED :
138                    $writer->writeElement($cs . 'invite-declined');
139                    break;
140                case SharingPlugin::STATUS_NORESPONSE :
141                    $writer->writeElement($cs . 'invite-noresponse');
142                    break;
143                case SharingPlugin::STATUS_INVALID :
144                    $writer->writeElement($cs . 'invite-invalid');
145                    break;
146            }
147
148            $writer->startElement($cs . 'access');
149            if ($user['readOnly']) {
150                $writer->writeElement($cs . 'read');
151            } else {
152                $writer->writeElement($cs . 'read-write');
153            }
154            $writer->endElement(); // access
155
156            if (isset($user['summary']) && $user['summary']) {
157                $writer->writeElement($cs . 'summary', $user['summary']);
158            }
159
160            $writer->endElement(); //user
161
162        }
163
164    }
165
166    /**
167     * The deserialize method is called during xml parsing.
168     *
169     * This method is called statictly, this is because in theory this method
170     * may be used as a type of constructor, or factory method.
171     *
172     * Often you want to return an instance of the current class, but you are
173     * free to return other data as well.
174     *
175     * You are responsible for advancing the reader to the next element. Not
176     * doing anything will result in a never-ending loop.
177     *
178     * If you just want to skip parsing for this element altogether, you can
179     * just call $reader->next();
180     *
181     * $reader->parseInnerTree() will parse the entire sub-tree, and advance to
182     * the next element.
183     *
184     * @param Reader $reader
185     * @return mixed
186     */
187    static function xmlDeserialize(Reader $reader) {
188
189        $cs = '{' . Plugin::NS_CALENDARSERVER . '}';
190
191        $users = [];
192
193        foreach ($reader->parseInnerTree() as $elem) {
194
195            if ($elem['name'] !== $cs . 'user')
196                continue;
197
198            $user = [
199                'href'       => null,
200                'commonName' => null,
201                'readOnly'   => null,
202                'summary'    => null,
203                'status'     => null,
204            ];
205
206            foreach ($elem['value'] as $userElem) {
207
208                switch ($userElem['name']) {
209                    case $cs . 'invite-accepted' :
210                        $user['status'] = SharingPlugin::STATUS_ACCEPTED;
211                        break;
212                    case $cs . 'invite-declined' :
213                        $user['status'] = SharingPlugin::STATUS_DECLINED;
214                        break;
215                    case $cs . 'invite-noresponse' :
216                        $user['status'] = SharingPlugin::STATUS_NORESPONSE;
217                        break;
218                    case $cs . 'invite-invalid' :
219                        $user['status'] = SharingPlugin::STATUS_INVALID;
220                        break;
221                    case '{DAV:}href' :
222                        $user['href'] = $userElem['value'];
223                        break;
224                    case $cs . 'common-name' :
225                        $user['commonName'] = $userElem['value'];
226                        break;
227                    case $cs . 'access' :
228                        foreach ($userElem['value'] as $accessHref) {
229                            if ($accessHref['name'] === $cs . 'read') {
230                                $user['readOnly'] = true;
231                            }
232                        }
233                        break;
234                    case $cs . 'summary' :
235                        $user['summary'] = $userElem['value'];
236                        break;
237
238                }
239
240            }
241            if (!$user['status']) {
242                throw new \InvalidArgumentException('Every user must have one of cs:invite-accepted, cs:invite-declined, cs:invite-noresponse or cs:invite-invalid');
243            }
244
245            $users[] = $user;
246
247        }
248
249        return new self($users);
250
251    }
252}
253