1<?php
2
3namespace Sabre\CardDAV;
4
5use Sabre\DAV;
6use Sabre\DAVACL;
7
8/**
9 * The AddressBook class represents a CardDAV addressbook, owned by a specific user
10 *
11 * The AddressBook can contain multiple vcards
12 *
13 * @copyright Copyright (C) 2007-2015 fruux GmbH (https://fruux.com/).
14 * @author Evert Pot (http://evertpot.com/)
15 * @license http://sabre.io/license/ Modified BSD License
16 */
17class AddressBook extends DAV\Collection implements IAddressBook, DAV\IProperties, DAVACL\IACL, DAV\Sync\ISyncCollection, DAV\IMultiGet {
18
19    /**
20     * This is an array with addressbook information
21     *
22     * @var array
23     */
24    protected $addressBookInfo;
25
26    /**
27     * CardDAV backend
28     *
29     * @var Backend\BackendInterface
30     */
31    protected $carddavBackend;
32
33    /**
34     * Constructor
35     *
36     * @param Backend\BackendInterface $carddavBackend
37     * @param array $addressBookInfo
38     */
39    function __construct(Backend\BackendInterface $carddavBackend, array $addressBookInfo) {
40
41        $this->carddavBackend = $carddavBackend;
42        $this->addressBookInfo = $addressBookInfo;
43
44    }
45
46    /**
47     * Returns the name of the addressbook
48     *
49     * @return string
50     */
51    function getName() {
52
53        return $this->addressBookInfo['uri'];
54
55    }
56
57    /**
58     * Returns a card
59     *
60     * @param string $name
61     * @return \ICard
62     */
63    function getChild($name) {
64
65        $obj = $this->carddavBackend->getCard($this->addressBookInfo['id'], $name);
66        if (!$obj) throw new DAV\Exception\NotFound('Card not found');
67        return new Card($this->carddavBackend, $this->addressBookInfo, $obj);
68
69    }
70
71    /**
72     * Returns the full list of cards
73     *
74     * @return array
75     */
76    function getChildren() {
77
78        $objs = $this->carddavBackend->getCards($this->addressBookInfo['id']);
79        $children = [];
80        foreach ($objs as $obj) {
81            $obj['acl'] = $this->getChildACL();
82            $children[] = new Card($this->carddavBackend, $this->addressBookInfo, $obj);
83        }
84        return $children;
85
86    }
87
88    /**
89     * This method receives a list of paths in it's first argument.
90     * It must return an array with Node objects.
91     *
92     * If any children are not found, you do not have to return them.
93     *
94     * @param string[] $paths
95     * @return array
96     */
97    function getMultipleChildren(array $paths) {
98
99        $objs = $this->carddavBackend->getMultipleCards($this->addressBookInfo['id'], $paths);
100        $children = [];
101        foreach ($objs as $obj) {
102            $obj['acl'] = $this->getChildACL();
103            $children[] = new Card($this->carddavBackend, $this->addressBookInfo, $obj);
104        }
105        return $children;
106
107    }
108
109    /**
110     * Creates a new directory
111     *
112     * We actually block this, as subdirectories are not allowed in addressbooks.
113     *
114     * @param string $name
115     * @return void
116     */
117    function createDirectory($name) {
118
119        throw new DAV\Exception\MethodNotAllowed('Creating collections in addressbooks is not allowed');
120
121    }
122
123    /**
124     * Creates a new file
125     *
126     * The contents of the new file must be a valid VCARD.
127     *
128     * This method may return an ETag.
129     *
130     * @param string $name
131     * @param resource $vcardData
132     * @return string|null
133     */
134    function createFile($name, $vcardData = null) {
135
136        if (is_resource($vcardData)) {
137            $vcardData = stream_get_contents($vcardData);
138        }
139        // Converting to UTF-8, if needed
140        $vcardData = DAV\StringUtil::ensureUTF8($vcardData);
141
142        return $this->carddavBackend->createCard($this->addressBookInfo['id'], $name, $vcardData);
143
144    }
145
146    /**
147     * Deletes the entire addressbook.
148     *
149     * @return void
150     */
151    function delete() {
152
153        $this->carddavBackend->deleteAddressBook($this->addressBookInfo['id']);
154
155    }
156
157    /**
158     * Renames the addressbook
159     *
160     * @param string $newName
161     * @return void
162     */
163    function setName($newName) {
164
165        throw new DAV\Exception\MethodNotAllowed('Renaming addressbooks is not yet supported');
166
167    }
168
169    /**
170     * Returns the last modification date as a unix timestamp.
171     *
172     * @return void
173     */
174    function getLastModified() {
175
176        return null;
177
178    }
179
180    /**
181     * Updates properties on this node.
182     *
183     * This method received a PropPatch object, which contains all the
184     * information about the update.
185     *
186     * To update specific properties, call the 'handle' method on this object.
187     * Read the PropPatch documentation for more information.
188     *
189     * @param DAV\PropPatch $propPatch
190     * @return void
191     */
192    function propPatch(DAV\PropPatch $propPatch) {
193
194        return $this->carddavBackend->updateAddressBook($this->addressBookInfo['id'], $propPatch);
195
196    }
197
198    /**
199     * Returns a list of properties for this nodes.
200     *
201     * The properties list is a list of propertynames the client requested,
202     * encoded in clark-notation {xmlnamespace}tagname
203     *
204     * If the array is empty, it means 'all properties' were requested.
205     *
206     * @param array $properties
207     * @return array
208     */
209    function getProperties($properties) {
210
211        $response = [];
212        foreach ($properties as $propertyName) {
213
214            if (isset($this->addressBookInfo[$propertyName])) {
215
216                $response[$propertyName] = $this->addressBookInfo[$propertyName];
217
218            }
219
220        }
221
222        return $response;
223
224    }
225
226    /**
227     * Returns the owner principal
228     *
229     * This must be a url to a principal, or null if there's no owner
230     *
231     * @return string|null
232     */
233    function getOwner() {
234
235        return $this->addressBookInfo['principaluri'];
236
237    }
238
239    /**
240     * Returns a group principal
241     *
242     * This must be a url to a principal, or null if there's no owner
243     *
244     * @return string|null
245     */
246    function getGroup() {
247
248        return null;
249
250    }
251
252    /**
253     * Returns a list of ACE's for this node.
254     *
255     * Each ACE has the following properties:
256     *   * 'privilege', a string such as {DAV:}read or {DAV:}write. These are
257     *     currently the only supported privileges
258     *   * 'principal', a url to the principal who owns the node
259     *   * 'protected' (optional), indicating that this ACE is not allowed to
260     *      be updated.
261     *
262     * @return array
263     */
264    function getACL() {
265
266        return [
267            [
268                'privilege' => '{DAV:}read',
269                'principal' => $this->getOwner(),
270                'protected' => true,
271            ],
272            [
273                'privilege' => '{DAV:}write',
274                'principal' => $this->getOwner(),
275                'protected' => true,
276            ],
277
278        ];
279
280    }
281
282    /**
283     * This method returns the ACL's for card nodes in this address book.
284     * The result of this method automatically gets passed to the
285     * card nodes in this address book.
286     *
287     * @return array
288     */
289    function getChildACL() {
290
291        return [
292            [
293                'privilege' => '{DAV:}read',
294                'principal' => $this->getOwner(),
295                'protected' => true,
296            ],
297            [
298                'privilege' => '{DAV:}write',
299                'principal' => $this->getOwner(),
300                'protected' => true,
301            ],
302        ];
303
304    }
305
306    /**
307     * Updates the ACL
308     *
309     * This method will receive a list of new ACE's.
310     *
311     * @param array $acl
312     * @return void
313     */
314    function setACL(array $acl) {
315
316        throw new DAV\Exception\MethodNotAllowed('Changing ACL is not yet supported');
317
318    }
319
320    /**
321     * Returns the list of supported privileges for this node.
322     *
323     * The returned data structure is a list of nested privileges.
324     * See Sabre\DAVACL\Plugin::getDefaultSupportedPrivilegeSet for a simple
325     * standard structure.
326     *
327     * If null is returned from this method, the default privilege set is used,
328     * which is fine for most common usecases.
329     *
330     * @return array|null
331     */
332    function getSupportedPrivilegeSet() {
333
334        return null;
335
336    }
337
338    /**
339     * This method returns the current sync-token for this collection.
340     * This can be any string.
341     *
342     * If null is returned from this function, the plugin assumes there's no
343     * sync information available.
344     *
345     * @return string|null
346     */
347    function getSyncToken() {
348
349        if (
350            $this->carddavBackend instanceof Backend\SyncSupport &&
351            isset($this->addressBookInfo['{DAV:}sync-token'])
352        ) {
353            return $this->addressBookInfo['{DAV:}sync-token'];
354        }
355        if (
356            $this->carddavBackend instanceof Backend\SyncSupport &&
357            isset($this->addressBookInfo['{http://sabredav.org/ns}sync-token'])
358        ) {
359            return $this->addressBookInfo['{http://sabredav.org/ns}sync-token'];
360        }
361
362    }
363
364    /**
365     * The getChanges method returns all the changes that have happened, since
366     * the specified syncToken and the current collection.
367     *
368     * This function should return an array, such as the following:
369     *
370     * [
371     *   'syncToken' => 'The current synctoken',
372     *   'added'   => [
373     *      'new.txt',
374     *   ],
375     *   'modified'   => [
376     *      'modified.txt',
377     *   ],
378     *   'deleted' => [
379     *      'foo.php.bak',
380     *      'old.txt'
381     *   ]
382     * ];
383     *
384     * The syncToken property should reflect the *current* syncToken of the
385     * collection, as reported getSyncToken(). This is needed here too, to
386     * ensure the operation is atomic.
387     *
388     * If the syncToken is specified as null, this is an initial sync, and all
389     * members should be reported.
390     *
391     * The modified property is an array of nodenames that have changed since
392     * the last token.
393     *
394     * The deleted property is an array with nodenames, that have been deleted
395     * from collection.
396     *
397     * The second argument is basically the 'depth' of the report. If it's 1,
398     * you only have to report changes that happened only directly in immediate
399     * descendants. If it's 2, it should also include changes from the nodes
400     * below the child collections. (grandchildren)
401     *
402     * The third (optional) argument allows a client to specify how many
403     * results should be returned at most. If the limit is not specified, it
404     * should be treated as infinite.
405     *
406     * If the limit (infinite or not) is higher than you're willing to return,
407     * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
408     *
409     * If the syncToken is expired (due to data cleanup) or unknown, you must
410     * return null.
411     *
412     * The limit is 'suggestive'. You are free to ignore it.
413     *
414     * @param string $syncToken
415     * @param int $syncLevel
416     * @param int $limit
417     * @return array
418     */
419    function getChanges($syncToken, $syncLevel, $limit = null) {
420
421        if (!$this->carddavBackend instanceof Backend\SyncSupport) {
422            return null;
423        }
424
425        return $this->carddavBackend->getChangesForAddressBook(
426            $this->addressBookInfo['id'],
427            $syncToken,
428            $syncLevel,
429            $limit
430        );
431
432    }
433}
434