CalDavBackend   F
last analyzed

Complexity

Total Complexity 169

Size/Duplication

Total Lines 1614
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 19

Importance

Changes 0
Metric Value
wmc 169
lcom 1
cbo 19
dl 0
loc 1614
rs 0.8
c 0
b 0
f 0

38 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 11 1
F getCalendarsForUser() 0 100 15
B getUsersOwnCalendars() 0 47 8
B getPublicCalendars() 0 51 8
B getPublicCalendar() 0 52 7
B getCalendarByUri() 0 44 7
B getCalendarById() 0 43 7
B createCalendar() 0 38 8
A updateCalendar() 0 31 5
A deleteCalendar() 0 12 1
A deleteAllSharesForUser() 0 3 1
A getCalendarObjects() 0 23 2
A getCalendarObject() 0 25 2
A getMultipleCalendarObjects() 0 36 3
A createCalendarObject() 0 24 1
A updateCalendarObject() 0 22 1
A setClassification() 0 12 2
A deleteCalendarObject() 0 6 1
F calendarQuery() 0 63 25
A getCalendarObjectByUID() 0 16 2
B getChangesForCalendar() 0 55 10
A getSubscriptionsForUser() 0 38 4
B createSubscription() 0 37 6
A updateSubscription() 0 28 4
A deleteSubscription() 0 6 1
A getSchedulingObject() 0 22 2
A getSchedulingObjects() 0 20 2
A deleteSchedulingObject() 0 7 1
A createSchedulingObject() 0 13 1
A addChange() 0 13 1
D getDenormalizedData() 0 72 17
A readBlob() 0 7 2
A updateShares() 0 3 1
A getShares() 0 3 1
A setPublishStatus() 0 21 2
A getPublishStatus() 0 12 2
A applyShareAcl() 0 3 1
A convertPrincipal() 0 11 4

How to fix   Complexity   

Complex Class

Complex classes like CalDavBackend often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CalDavBackend, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @author Joas Schilling <[email protected]>
4
 * @author Stefan Weil <[email protected]>
5
 * @author Thomas Citharel <[email protected]>
6
 * @author Thomas Müller <[email protected]>
7
 *
8
 * @copyright Copyright (c) 2018, ownCloud GmbH
9
 * @license AGPL-3.0
10
 *
11
 * This code is free software: you can redistribute it and/or modify
12
 * it under the terms of the GNU Affero General Public License, version 3,
13
 * as published by the Free Software Foundation.
14
 *
15
 * This program is distributed in the hope that it will be useful,
16
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18
 * GNU Affero General Public License for more details.
19
 *
20
 * You should have received a copy of the GNU Affero General Public License, version 3,
21
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
22
 *
23
 */
24
25
namespace OCA\DAV\CalDAV;
26
27
use Doctrine\DBAL\Connection;
28
use OCA\DAV\Connector\Sabre\Principal;
29
use OCA\DAV\DAV\GroupPrincipalBackend;
30
use OCA\DAV\DAV\Sharing\Backend;
31
use OCA\DAV\DAV\Sharing\IShareable;
32
use OCP\DB\QueryBuilder\IQueryBuilder;
33
use OCP\IConfig;
34
use OCP\IDBConnection;
35
use OCP\Security\ISecureRandom;
36
use Sabre\CalDAV\Backend\AbstractBackend;
37
use Sabre\CalDAV\Backend\SchedulingSupport;
38
use Sabre\CalDAV\Backend\SubscriptionSupport;
39
use Sabre\CalDAV\Backend\SyncSupport;
40
use Sabre\CalDAV\Plugin;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, OCA\DAV\CalDAV\Plugin.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
41
use Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp;
42
use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet;
43
use Sabre\DAV;
44
use Sabre\DAV\Exception\Forbidden;
45
use Sabre\DAV\Exception\NotFound;
46
use Sabre\DAV\PropPatch;
47
use Sabre\VObject\DateTimeParser;
48
use Sabre\VObject\Reader;
49
use Sabre\VObject\Recur\EventIterator;
50
51
/**
52
 * Class CalDavBackend
53
 *
54
 * Code is heavily inspired by https://github.com/fruux/sabre-dav/blob/master/lib/CalDAV/Backend/PDO.php
55
 *
56
 * @package OCA\DAV\CalDAV
57
 */
58
class CalDavBackend extends AbstractBackend implements SyncSupport, SubscriptionSupport, SchedulingSupport {
59
60
	/**
61
	 * We need to specify a max date, because we need to stop *somewhere*
62
	 *
63
	 * On 32 bit system the maximum for a signed integer is 2147483647, so
64
	 * MAX_DATE cannot be higher than date('Y-m-d', 2147483647) which results
65
	 * in 2038-01-19 to avoid problems when the date is converted
66
	 * to a unix timestamp.
67
	 */
68
	public const MAX_DATE = '2038-01-01';
69
70
	public const ACCESS_PUBLIC = 4;
71
	public const CLASSIFICATION_PUBLIC = 0;
72
	public const CLASSIFICATION_PRIVATE = 1;
73
	public const CLASSIFICATION_CONFIDENTIAL = 2;
74
75
	/**
76
	 * List of CalDAV properties, and how they map to database field names
77
	 * Add your own properties by simply adding on to this array.
78
	 *
79
	 * Note that only string-based properties are supported here.
80
	 *
81
	 * @var array
82
	 */
83
	public $propertyMap = [
84
		'{DAV:}displayname'                          => 'displayname',
85
		'{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description',
86
		'{urn:ietf:params:xml:ns:caldav}calendar-timezone'    => 'timezone',
87
		'{http://apple.com/ns/ical/}calendar-order'  => 'calendarorder',
88
		'{http://apple.com/ns/ical/}calendar-color'  => 'calendarcolor',
89
	];
90
91
	/**
92
	 * List of subscription properties, and how they map to database field names.
93
	 *
94
	 * @var array
95
	 */
96
	public $subscriptionPropertyMap = [
97
		'{DAV:}displayname'                                           => 'displayname',
98
		'{http://apple.com/ns/ical/}refreshrate'                      => 'refreshrate',
99
		'{http://apple.com/ns/ical/}calendar-order'                   => 'calendarorder',
100
		'{http://apple.com/ns/ical/}calendar-color'                   => 'calendarcolor',
101
		'{http://calendarserver.org/ns/}subscribed-strip-todos'       => 'striptodos',
102
		'{http://calendarserver.org/ns/}subscribed-strip-alarms'      => 'stripalarms',
103
		'{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments',
104
	];
105
106
	/** @var IDBConnection */
107
	private $db;
108
109
	/** @var Backend */
110
	private $sharingBackend;
111
112
	/** @var Principal */
113
	private $principalBackend;
114
115
	/** @var ISecureRandom */
116
	private $random;
117
118
	/** @var bool */
119
	private $legacyMode;
120
121
	/**
122
	 * CalDavBackend constructor.
123
	 *
124
	 * @param IDBConnection $db
125
	 * @param Principal $principalBackend
126
	 * @param GroupPrincipalBackend $groupPrincipalBackend
127
	 * @param ISecureRandom $random
128
	 * @param bool $legacyMode
129
	 */
130
	public function __construct(IDBConnection $db,
131
								Principal $principalBackend,
132
								GroupPrincipalBackend $groupPrincipalBackend,
133
								ISecureRandom $random,
134
								$legacyMode = false) {
135
		$this->db = $db;
136
		$this->principalBackend = $principalBackend;
137
		$this->sharingBackend = new Backend($this->db, $principalBackend, $groupPrincipalBackend, 'calendar');
138
		$this->random = $random;
139
		$this->legacyMode = $legacyMode;
140
	}
141
142
	/**
143
	 * Returns a list of calendars for a principal.
144
	 *
145
	 * Every project is an array with the following keys:
146
	 *  * id, a unique id that will be used by other functions to modify the
147
	 *    calendar. This can be the same as the uri or a database key.
148
	 *  * uri, which the basename of the uri with which the calendar is
149
	 *    accessed.
150
	 *  * principaluri. The owner of the calendar. Almost always the same as
151
	 *    principalUri passed to this method.
152
	 *
153
	 * Furthermore it can contain webdav properties in clark notation. A very
154
	 * common one is '{DAV:}displayname'.
155
	 *
156
	 * Many clients also require:
157
	 * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
158
	 * For this property, you can just return an instance of
159
	 * Sabre\CalDAV\Property\SupportedCalendarComponentSet.
160
	 *
161
	 * If you return {http://sabredav.org/ns}read-only and set the value to 1,
162
	 * ACL will automatically be put in read-only mode.
163
	 *
164
	 * @param string $principalUri
165
	 * @return array
166
	 * @throws DAV\Exception
167
	 */
168
	public function getCalendarsForUser($principalUri) {
169
		$principalUriOriginal = $principalUri;
170
		$principalUri = $this->convertPrincipal($principalUri, true);
171
		$fields = \array_values($this->propertyMap);
172
		$fields[] = 'id';
173
		$fields[] = 'uri';
174
		$fields[] = 'synctoken';
175
		$fields[] = 'components';
176
		$fields[] = 'principaluri';
177
		$fields[] = 'transparent';
178
179
		// Making fields a comma-delimited list
180
		$query = $this->db->getQueryBuilder();
181
		$query->select($fields)->from('calendars')
182
				->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
183
				->orderBy('calendarorder', 'ASC');
184
		$stmt = $query->execute();
185
186
		$calendars = [];
187
		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
188
			$components = [];
189
			if ($row['components']) {
190
				$components = \explode(',', $row['components']);
191
			}
192
193
			$calendar = [
194
				'id' => $row['id'],
195
				'uri' => $row['uri'],
196
				'principaluri' => $this->convertPrincipal($row['principaluri']),
197
				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?:'0'),
198
				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?:'0',
199
				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
200
				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
201
			];
202
203
			foreach ($this->propertyMap as $xmlName=>$dbName) {
204
				$calendar[$xmlName] = $row[$dbName];
205
			}
206
207
			if (!isset($calendars[$calendar['id']])) {
208
				$calendars[$calendar['id']] = $calendar;
209
			}
210
		}
211
212
		$stmt->closeCursor();
213
214
		// query for shared calendars
215
		$principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
216
		$principals[]= $principalUri;
217
218
		$fields = \array_values($this->propertyMap);
219
		$fields[] = 'a.id';
220
		$fields[] = 'a.uri';
221
		$fields[] = 'a.synctoken';
222
		$fields[] = 'a.components';
223
		$fields[] = 'a.principaluri';
224
		$fields[] = 'a.transparent';
225
		$fields[] = 's.access';
226
		$query = $this->db->getQueryBuilder();
227
		$result = $query->select($fields)
228
			->from('dav_shares', 's')
229
			->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
230
			->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
231
			->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
232
			->setParameter('type', 'calendar')
233
			->setParameter('principaluri', $principals, Connection::PARAM_STR_ARRAY)
234
			->execute();
235
236
		while ($row = $result->fetch()) {
237
			list(, $name) = \Sabre\Uri\split($row['principaluri']);
238
			$uri = $row['uri'] . '_shared_by_' . $name;
239
			$row['displayname'] .= " ($name)";
240
			$components = [];
241
			if ($row['components']) {
242
				$components = \explode(',', $row['components']);
243
			}
244
			$calendar = [
245
				'id' => $row['id'],
246
				'uri' => $uri,
247
				'principaluri' => $this->convertPrincipal($principalUri),
248
				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?:'0'),
249
				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?:'0',
250
				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
251
				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
252
				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri']),
253
				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
254
			];
255
256
			foreach ($this->propertyMap as $xmlName=>$dbName) {
257
				$calendar[$xmlName] = $row[$dbName];
258
			}
259
260
			if (!isset($calendars[$calendar['id']])) {
261
				$calendars[$calendar['id']] = $calendar;
262
			}
263
		}
264
		$result->closeCursor();
265
266
		return \array_values($calendars);
267
	}
268
269
	public function getUsersOwnCalendars($principalUri) {
270
		$principalUri = $this->convertPrincipal($principalUri, true);
271
		$fields = \array_values($this->propertyMap);
272
		$fields[] = 'id';
273
		$fields[] = 'uri';
274
		$fields[] = 'synctoken';
275
		$fields[] = 'components';
276
		$fields[] = 'principaluri';
277
		$fields[] = 'transparent';
278
279
		// Making fields a comma-delimited list
280
		$query = $this->db->getQueryBuilder();
281
		$query->select($fields)->from('calendars')
282
			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
283
			->orderBy('calendarorder', 'ASC');
284
		$stmt = $query->execute();
285
286
		$calendars = [];
287
		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
288
			$components = [];
289
			if ($row['components']) {
290
				$components = \explode(',', $row['components']);
291
			}
292
293
			$calendar = [
294
				'id' => $row['id'],
295
				'uri' => $row['uri'],
296
				'principaluri' => $this->convertPrincipal($row['principaluri']),
297
				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?:'0'),
298
				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?:'0',
299
				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
300
				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
301
			];
302
303
			foreach ($this->propertyMap as $xmlName=>$dbName) {
304
				$calendar[$xmlName] = $row[$dbName];
305
			}
306
307
			if (!isset($calendars[$calendar['id']])) {
308
				$calendars[$calendar['id']] = $calendar;
309
			}
310
		}
311
312
		$stmt->closeCursor();
313
314
		return \array_values($calendars);
315
	}
316
317
	/**
318
	 * @return array
319
	 */
320
	public function getPublicCalendars() {
321
		$fields = \array_values($this->propertyMap);
322
		$fields[] = 'a.id';
323
		$fields[] = 'a.uri';
324
		$fields[] = 'a.synctoken';
325
		$fields[] = 'a.components';
326
		$fields[] = 'a.principaluri';
327
		$fields[] = 'a.transparent';
328
		$fields[] = 's.access';
329
		$fields[] = 's.publicuri';
330
		$calendars = [];
331
		$query = $this->db->getQueryBuilder();
332
		$result = $query->select($fields)
333
			->from('dav_shares', 's')
334
			->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
335
			->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
336
			->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar')))
337
			->execute();
338
339
		while ($row = $result->fetch()) {
340
			list(, $name) = \Sabre\Uri\split($row['principaluri']);
341
			$row['displayname'] .= "($name)";
342
			$components = [];
343
			if ($row['components']) {
344
				$components = \explode(',', $row['components']);
345
			}
346
			$calendar = [
347
				'id' => $row['id'],
348
				'uri' => $row['publicuri'],
349
				'principaluri' => $this->convertPrincipal($row['principaluri']),
350
				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?:'0'),
351
				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?:'0',
352
				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
353
				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
354
				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri']),
355
				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
356
				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
357
			];
358
359
			foreach ($this->propertyMap as $xmlName=>$dbName) {
360
				$calendar[$xmlName] = $row[$dbName];
361
			}
362
363
			if (!isset($calendars[$calendar['id']])) {
364
				$calendars[$calendar['id']] = $calendar;
365
			}
366
		}
367
		$result->closeCursor();
368
369
		return \array_values($calendars);
370
	}
371
372
	/**
373
	 * @param string $uri
374
	 * @return array
375
	 * @throws NotFound
376
	 */
377
	public function getPublicCalendar($uri) {
378
		$fields = \array_values($this->propertyMap);
379
		$fields[] = 'a.id';
380
		$fields[] = 'a.uri';
381
		$fields[] = 'a.synctoken';
382
		$fields[] = 'a.components';
383
		$fields[] = 'a.principaluri';
384
		$fields[] = 'a.transparent';
385
		$fields[] = 's.access';
386
		$fields[] = 's.publicuri';
387
		$query = $this->db->getQueryBuilder();
388
		$result = $query->select($fields)
389
			->from('dav_shares', 's')
390
			->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
391
			->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
392
			->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar')))
393
			->andWhere($query->expr()->eq('s.publicuri', $query->createNamedParameter($uri)))
394
			->execute();
395
396
		$row = $result->fetch(\PDO::FETCH_ASSOC);
397
398
		$result->closeCursor();
399
400
		if ($row === false) {
401
			throw new NotFound('Node with name \'' . $uri . '\' could not be found');
402
		}
403
404
		list(, $name) = \Sabre\Uri\split($row['principaluri']);
405
		$row['displayname'] = $row['displayname'] . ' ' . "($name)";
406
		$components = [];
407
		if ($row['components']) {
408
			$components = \explode(',', $row['components']);
409
		}
410
		$calendar = [
411
			'id' => $row['id'],
412
			'uri' => $row['publicuri'],
413
			'principaluri' => $this->convertPrincipal($row['principaluri']),
414
			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?:'0'),
415
			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?:'0',
416
			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
417
			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
418
			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri']),
419
			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
420
			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
421
		];
422
423
		foreach ($this->propertyMap as $xmlName=>$dbName) {
424
			$calendar[$xmlName] = $row[$dbName];
425
		}
426
427
		return $calendar;
428
	}
429
430
	/**
431
	 * @param string $principal
432
	 * @param string $uri
433
	 * @return array|null
434
	 */
435
	public function getCalendarByUri($principal, $uri) {
436
		$fields = \array_values($this->propertyMap);
437
		$fields[] = 'id';
438
		$fields[] = 'uri';
439
		$fields[] = 'synctoken';
440
		$fields[] = 'components';
441
		$fields[] = 'principaluri';
442
		$fields[] = 'transparent';
443
444
		// Making fields a comma-delimited list
445
		$query = $this->db->getQueryBuilder();
446
		$query->select($fields)->from('calendars')
447
			->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
448
			->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
449
			->setMaxResults(1);
450
		$stmt = $query->execute();
451
452
		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
453
		$stmt->closeCursor();
454
		if ($row === false) {
455
			return null;
456
		}
457
458
		$components = [];
459
		if ($row['components']) {
460
			$components = \explode(',', $row['components']);
461
		}
462
463
		$calendar = [
464
			'id' => $row['id'],
465
			'uri' => $row['uri'],
466
			'principaluri' => $this->convertPrincipal($row['principaluri']),
467
			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?:'0'),
468
			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?:'0',
469
			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
470
			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
471
		];
472
473
		foreach ($this->propertyMap as $xmlName=>$dbName) {
474
			$calendar[$xmlName] = $row[$dbName];
475
		}
476
477
		return $calendar;
478
	}
479
480
	public function getCalendarById($calendarId) {
481
		$fields = \array_values($this->propertyMap);
482
		$fields[] = 'id';
483
		$fields[] = 'uri';
484
		$fields[] = 'synctoken';
485
		$fields[] = 'components';
486
		$fields[] = 'principaluri';
487
		$fields[] = 'transparent';
488
489
		// Making fields a comma-delimited list
490
		$query = $this->db->getQueryBuilder();
491
		$query->select($fields)->from('calendars')
492
			->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)))
493
			->setMaxResults(1);
494
		$stmt = $query->execute();
495
496
		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
497
		$stmt->closeCursor();
498
		if ($row === false) {
499
			return null;
500
		}
501
502
		$components = [];
503
		if ($row['components']) {
504
			$components = \explode(',', $row['components']);
505
		}
506
507
		$calendar = [
508
			'id' => $row['id'],
509
			'uri' => $row['uri'],
510
			'principaluri' => $this->convertPrincipal($row['principaluri']),
511
			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?:'0'),
512
			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?:'0',
513
			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
514
			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
515
		];
516
517
		foreach ($this->propertyMap as $xmlName=>$dbName) {
518
			$calendar[$xmlName] = $row[$dbName];
519
		}
520
521
		return $calendar;
522
	}
523
524
	/**
525
	 * Creates a new calendar for a principal.
526
	 *
527
	 * If the creation was a success, an id must be returned that can be used to reference
528
	 * this calendar in other methods, such as updateCalendar.
529
	 *
530
	 * @param string $principalUri
531
	 * @param string $calendarUri
532
	 * @param array $properties (keys must be CalDAV properties not db names)
533
	 * @return int
534
	 * @throws DAV\Exception
535
	 */
536
	public function createCalendar($principalUri, $calendarUri, array $properties) {
537
		$principalUri = $this->convertPrincipal($principalUri, true);
538
		$values = [
539
			'principaluri' => $principalUri,
540
			'uri'          => $calendarUri,
541
			'synctoken'    => 1,
542
			'transparent'  => 0,
543
			'components'   => 'VEVENT,VTODO',
544
			'displayname'  => $calendarUri,
545
		];
546
547
		// Default value
548
		$sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
549
		if (isset($properties[$sccs])) {
550
			if (!($properties[$sccs] instanceof SupportedCalendarComponentSet)) {
551
				throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet');
552
			}
553
			$values['components'] = \implode(',', $properties[$sccs]->getValue());
554
		}
555
		$transp = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
556
		if (isset($properties[$transp])) {
557
			$values['transparent'] = $properties[$transp]->getValue() === 'transparent' ? 1 : 0;
558
		}
559
560
		foreach ($this->propertyMap as $xmlName=>$dbName) {
561
			if (isset($properties[$xmlName])) {
562
				$values[$dbName] = $properties[$xmlName];
563
			}
564
		}
565
566
		$query = $this->db->getQueryBuilder();
567
		$query->insert('calendars');
568
		foreach ($values as $column => $value) {
569
			$query->setValue($column, $query->createNamedParameter($value));
570
		}
571
		$query->execute();
572
		return $query->getLastInsertId();
573
	}
574
575
	/**
576
	 * Updates properties for a calendar.
577
	 *
578
	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
579
	 * To do the actual updates, you must tell this object which properties
580
	 * you're going to process with the handle() method.
581
	 *
582
	 * Calling the handle method is like telling the PropPatch object "I
583
	 * promise I can handle updating this property".
584
	 *
585
	 * Read the PropPatch documentation for more info and examples.
586
	 *
587
	 * @param mixed $calendarId
588
	 * @param PropPatch $propPatch
589
	 * @return void
590
	 */
591
	public function updateCalendar($calendarId, PropPatch $propPatch) {
592
		$supportedProperties = \array_keys($this->propertyMap);
593
		$supportedProperties[] = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
594
595
		$propPatch->handle($supportedProperties, function ($mutations) use ($calendarId) {
596
			$newValues = [];
597
			foreach ($mutations as $propertyName => $propertyValue) {
598
				switch ($propertyName) {
599
					case '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp':
600
						$fieldName = 'transparent';
601
						$newValues[$fieldName] = $propertyValue->getValue() === 'transparent' ? 1 : 0;
602
						break;
603
					default:
604
						$fieldName = $this->propertyMap[$propertyName];
605
						$newValues[$fieldName] = $propertyValue;
606
						break;
607
				}
608
			}
609
			$query = $this->db->getQueryBuilder();
610
			$query->update('calendars');
611
			foreach ($newValues as $fieldName => $value) {
612
				$query->set($fieldName, $query->createNamedParameter($value));
613
			}
614
			$query->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
615
			$query->execute();
616
617
			$this->addChange($calendarId, '', 2);
618
619
			return true;
620
		});
621
	}
622
623
	/**
624
	 * Delete a calendar and all it's objects
625
	 *
626
	 * @param mixed $calendarId
627
	 * @return void
628
	 */
629
	public function deleteCalendar($calendarId) {
630
		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ?');
631
		$stmt->execute([$calendarId]);
632
633
		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendars` WHERE `id` = ?');
634
		$stmt->execute([$calendarId]);
635
636
		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarchanges` WHERE `calendarid` = ?');
637
		$stmt->execute([$calendarId]);
638
639
		$this->sharingBackend->deleteAllShares($calendarId);
640
	}
641
642
	/**
643
	 * Delete all of an user's shares
644
	 *
645
	 * @param string $principalUri
646
	 * @return void
647
	 */
648
	public function deleteAllSharesForUser($principalUri) {
649
		$this->sharingBackend->deleteAllSharesByUser($principalUri);
650
	}
651
652
	/**
653
	 * Returns all calendar objects within a calendar.
654
	 *
655
	 * Every item contains an array with the following keys:
656
	 *   * calendardata - The iCalendar-compatible calendar data
657
	 *   * uri - a unique key which will be used to construct the uri. This can
658
	 *     be any arbitrary string, but making sure it ends with '.ics' is a
659
	 *     good idea. This is only the basename, or filename, not the full
660
	 *     path.
661
	 *   * lastmodified - a timestamp of the last modification time
662
	 *   * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
663
	 *   '"abcdef"')
664
	 *   * size - The size of the calendar objects, in bytes.
665
	 *   * component - optional, a string containing the type of object, such
666
	 *     as 'vevent' or 'vtodo'. If specified, this will be used to populate
667
	 *     the Content-Type header.
668
	 *
669
	 * Note that the etag is optional, but it's highly encouraged to return for
670
	 * speed reasons.
671
	 *
672
	 * The calendardata is also optional. If it's not returned
673
	 * 'getCalendarObject' will be called later, which *is* expected to return
674
	 * calendardata.
675
	 *
676
	 * If neither etag or size are specified, the calendardata will be
677
	 * used/fetched to determine these numbers. If both are specified the
678
	 * amount of times this is needed is reduced by a great degree.
679
	 *
680
	 * @param mixed $calendarId
681
	 * @return array
682
	 */
683
	public function getCalendarObjects($calendarId) {
684
		$query = $this->db->getQueryBuilder();
685
		$query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'componenttype', 'classification'])
686
			->from('calendarobjects')
687
			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)));
688
		$stmt = $query->execute();
689
690
		$result = [];
691
		foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
692
			$result[] = [
693
					'id'           => $row['id'],
694
					'uri'          => $row['uri'],
695
					'lastmodified' => $row['lastmodified'],
696
					'etag'         => '"' . $row['etag'] . '"',
697
					'calendarid'   => $row['calendarid'],
698
					'size'         => (int)$row['size'],
699
					'component'    => \strtolower($row['componenttype']),
700
					'classification'=> (int)$row['classification']
701
			];
702
		}
703
704
		return $result;
705
	}
706
707
	/**
708
	 * Returns information from a single calendar object, based on it's object
709
	 * uri.
710
	 *
711
	 * The object uri is only the basename, or filename and not a full path.
712
	 *
713
	 * The returned array must have the same keys as getCalendarObjects. The
714
	 * 'calendardata' object is required here though, while it's not required
715
	 * for getCalendarObjects.
716
	 *
717
	 * This method must return null if the object did not exist.
718
	 *
719
	 * @param mixed $calendarId
720
	 * @param string $objectUri
721
	 * @return array|null
722
	 */
723
	public function getCalendarObject($calendarId, $objectUri) {
724
		$query = $this->db->getQueryBuilder();
725
		$query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification'])
726
				->from('calendarobjects')
727
				->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
728
				->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)));
729
		$stmt = $query->execute();
730
		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
731
732
		if (!$row) {
733
			return null;
734
		}
735
736
		return [
737
				'id'            => $row['id'],
738
				'uri'           => $row['uri'],
739
				'lastmodified'  => $row['lastmodified'],
740
				'etag'          => '"' . $row['etag'] . '"',
741
				'calendarid'    => $row['calendarid'],
742
				'size'          => (int)$row['size'],
743
				'calendardata'  => $this->readBlob($row['calendardata']),
744
				'component'     => \strtolower($row['componenttype']),
745
				'classification'=> (int)$row['classification']
746
		];
747
	}
748
749
	/**
750
	 * Returns a list of calendar objects.
751
	 *
752
	 * This method should work identical to getCalendarObject, but instead
753
	 * return all the calendar objects in the list as an array.
754
	 *
755
	 * If the backend supports this, it may allow for some speed-ups.
756
	 *
757
	 * @param mixed $calendarId
758
	 * @param string[] $uris
759
	 * @return array
760
	 */
761
	public function getMultipleCalendarObjects($calendarId, array $uris) {
762
		$chunkSize = 998;
763
		if (\count($uris) <= $chunkSize) {
764
			$query = $this->db->getQueryBuilder();
765
			$query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification'])
766
				->from('calendarobjects')
767
				->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
768
				->andWhere($query->expr()->in('uri', $query->createParameter('uri')))
769
				->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
770
771
			$stmt = $query->execute();
772
773
			$result = [];
774
			while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
775
				$result[] = [
776
					'id'           => $row['id'],
777
					'uri'          => $row['uri'],
778
					'lastmodified' => $row['lastmodified'],
779
					'etag'         => '"' . $row['etag'] . '"',
780
					'calendarid'   => $row['calendarid'],
781
					'size'         => (int)$row['size'],
782
					'calendardata' => $this->readBlob($row['calendardata']),
783
					'component'    => \strtolower($row['componenttype']),
784
					'classification' => (int)$row['classification']
785
				];
786
			}
787
			$stmt->closeCursor();
788
			return $result;
789
		}
790
		$chunks = \array_chunk($uris, $chunkSize);
791
		$results = \array_map(function ($chunk) use ($calendarId) {
792
			return $this->getMultipleCalendarObjects($calendarId, $chunk);
793
		}, $chunks);
794
795
		return \array_merge(...$results);
796
	}
797
798
	/**
799
	 * Creates a new calendar object.
800
	 *
801
	 * The object uri is only the basename, or filename and not a full path.
802
	 *
803
	 * It is possible return an etag from this function, which will be used in
804
	 * the response to this PUT request. Note that the ETag must be surrounded
805
	 * by double-quotes.
806
	 *
807
	 * However, you should only really return this ETag if you don't mangle the
808
	 * calendar-data. If the result of a subsequent GET to this object is not
809
	 * the exact same as this request body, you should omit the ETag.
810
	 *
811
	 * @param mixed $calendarId
812
	 * @param string $objectUri
813
	 * @param string $calendarData
814
	 * @return string
815
	 * @throws DAV\Exception\BadRequest
816
	 * @throws \Sabre\VObject\Recur\MaxInstancesExceededException
817
	 * @throws \Sabre\VObject\Recur\NoInstancesException
818
	 */
819
	public function createCalendarObject($calendarId, $objectUri, $calendarData) {
820
		$extraData = $this->getDenormalizedData($calendarData);
821
822
		$query = $this->db->getQueryBuilder();
823
		$query->insert('calendarobjects')
824
			->values([
825
				'calendarid' => $query->createNamedParameter($calendarId),
826
				'uri' => $query->createNamedParameter($objectUri),
827
				'calendardata' => $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB),
828
				'lastmodified' => $query->createNamedParameter(\time()),
829
				'etag' => $query->createNamedParameter($extraData['etag']),
830
				'size' => $query->createNamedParameter($extraData['size']),
831
				'componenttype' => $query->createNamedParameter($extraData['componentType']),
832
				'firstoccurence' => $query->createNamedParameter($extraData['firstOccurence']),
833
				'lastoccurence' => $query->createNamedParameter($extraData['lastOccurence']),
834
				'classification' => $query->createNamedParameter($extraData['classification']),
835
				'uid' => $query->createNamedParameter($extraData['uid']),
836
			])
837
			->execute();
838
839
		$this->addChange($calendarId, $objectUri, 1);
840
841
		return '"' . $extraData['etag'] . '"';
842
	}
843
844
	/**
845
	 * Updates an existing calendarobject, based on it's uri.
846
	 *
847
	 * The object uri is only the basename, or filename and not a full path.
848
	 *
849
	 * It is possible return an etag from this function, which will be used in
850
	 * the response to this PUT request. Note that the ETag must be surrounded
851
	 * by double-quotes.
852
	 *
853
	 * However, you should only really return this ETag if you don't mangle the
854
	 * calendar-data. If the result of a subsequent GET to this object is not
855
	 * the exact same as this request body, you should omit the ETag.
856
	 *
857
	 * @param mixed $calendarId
858
	 * @param string $objectUri
859
	 * @param string $calendarData
860
	 * @return string
861
	 * @throws DAV\Exception\BadRequest
862
	 * @throws \Sabre\VObject\Recur\MaxInstancesExceededException
863
	 * @throws \Sabre\VObject\Recur\NoInstancesException
864
	 */
865
	public function updateCalendarObject($calendarId, $objectUri, $calendarData) {
866
		$extraData = $this->getDenormalizedData($calendarData);
867
868
		$query = $this->db->getQueryBuilder();
869
		$query->update('calendarobjects')
870
				->set('calendardata', $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB))
871
				->set('lastmodified', $query->createNamedParameter(\time()))
872
				->set('etag', $query->createNamedParameter($extraData['etag']))
873
				->set('size', $query->createNamedParameter($extraData['size']))
874
				->set('componenttype', $query->createNamedParameter($extraData['componentType']))
875
				->set('firstoccurence', $query->createNamedParameter($extraData['firstOccurence']))
876
				->set('lastoccurence', $query->createNamedParameter($extraData['lastOccurence']))
877
				->set('classification', $query->createNamedParameter($extraData['classification']))
878
				->set('uid', $query->createNamedParameter($extraData['uid']))
879
			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
880
			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
881
			->execute();
882
883
		$this->addChange($calendarId, $objectUri, 2);
884
885
		return '"' . $extraData['etag'] . '"';
886
	}
887
888
	/**
889
	 * @param int $calendarObjectId
890
	 * @param int $classification
891
	 */
892
	public function setClassification($calendarObjectId, $classification) {
893
		if (!\in_array($classification, [
894
			self::CLASSIFICATION_PUBLIC, self::CLASSIFICATION_PRIVATE, self::CLASSIFICATION_CONFIDENTIAL
895
		], true)) {
896
			throw new \InvalidArgumentException();
897
		}
898
		$query = $this->db->getQueryBuilder();
899
		$query->update('calendarobjects')
900
			->set('classification', $query->createNamedParameter($classification))
901
			->where($query->expr()->eq('id', $query->createNamedParameter($calendarObjectId)))
902
			->execute();
903
	}
904
905
	/**
906
	 * Deletes an existing calendar object.
907
	 *
908
	 * The object uri is only the basename, or filename and not a full path.
909
	 *
910
	 * @param mixed $calendarId
911
	 * @param string $objectUri
912
	 * @return void
913
	 */
914
	public function deleteCalendarObject($calendarId, $objectUri) {
915
		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `uri` = ?');
916
		$stmt->execute([$calendarId, $objectUri]);
917
918
		$this->addChange($calendarId, $objectUri, 3);
919
	}
920
921
	/**
922
	 * Performs a calendar-query on the contents of this calendar.
923
	 *
924
	 * The calendar-query is defined in RFC4791 : CalDAV. Using the
925
	 * calendar-query it is possible for a client to request a specific set of
926
	 * object, based on contents of iCalendar properties, date-ranges and
927
	 * iCalendar component types (VTODO, VEVENT).
928
	 *
929
	 * This method should just return a list of (relative) urls that match this
930
	 * query.
931
	 *
932
	 * The list of filters are specified as an array. The exact array is
933
	 * documented by Sabre\CalDAV\CalendarQueryParser.
934
	 *
935
	 * Note that it is extremely likely that getCalendarObject for every path
936
	 * returned from this method will be called almost immediately after. You
937
	 * may want to anticipate this to speed up these requests.
938
	 *
939
	 * This method provides a default implementation, which parses *all* the
940
	 * iCalendar objects in the specified calendar.
941
	 *
942
	 * This default may well be good enough for personal use, and calendars
943
	 * that aren't very large. But if you anticipate high usage, big calendars
944
	 * or high loads, you are strongly advised to optimize certain paths.
945
	 *
946
	 * The best way to do so is override this method and to optimize
947
	 * specifically for 'common filters'.
948
	 *
949
	 * Requests that are extremely common are:
950
	 *   * requests for just VEVENTS
951
	 *   * requests for just VTODO
952
	 *   * requests with a time-range-filter on either VEVENT or VTODO.
953
	 *
954
	 * ..and combinations of these requests. It may not be worth it to try to
955
	 * handle every possible situation and just rely on the (relatively
956
	 * easy to use) CalendarQueryValidator to handle the rest.
957
	 *
958
	 * Note that especially time-range-filters may be difficult to parse. A
959
	 * time-range filter specified on a VEVENT must for instance also handle
960
	 * recurrence rules correctly.
961
	 * A good example of how to interprete all these filters can also simply
962
	 * be found in Sabre\CalDAV\CalendarQueryFilter. This class is as correct
963
	 * as possible, so it gives you a good idea on what type of stuff you need
964
	 * to think of.
965
	 *
966
	 * @param mixed $calendarId
967
	 * @param array $filters
968
	 * @return array
969
	 */
970
	public function calendarQuery($calendarId, array $filters) {
971
		$componentType = null;
972
		$requirePostFilter = true;
973
		$timeRange = null;
974
975
		// if no filters were specified, we don't need to filter after a query
976
		if (!$filters['prop-filters'] && !$filters['comp-filters']) {
977
			$requirePostFilter = false;
978
		}
979
980
		// Figuring out if there's a component filter
981
		if (\count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) {
982
			$componentType = $filters['comp-filters'][0]['name'];
983
984
			// Checking if we need post-filters
985
			if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) {
986
				$requirePostFilter = false;
987
			}
988
			// There was a time-range filter
989
			if ($componentType == 'VEVENT' && isset($filters['comp-filters'][0]['time-range'])) {
990
				$timeRange = $filters['comp-filters'][0]['time-range'];
991
992
				// If start time OR the end time is not specified, we can do a
993
				// 100% accurate mysql query.
994
				if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) {
995
					$requirePostFilter = false;
996
				}
997
			}
998
		}
999
		$columns = ['uri'];
1000
		if ($requirePostFilter) {
1001
			$columns = ['uri', 'calendardata'];
1002
		}
1003
		$query = $this->db->getQueryBuilder();
1004
		$query->select($columns)
1005
			->from('calendarobjects')
1006
			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)));
1007
1008
		if ($componentType) {
1009
			$query->andWhere($query->expr()->eq('componenttype', $query->createNamedParameter($componentType)));
1010
		}
1011
1012
		if ($timeRange && $timeRange['start']) {
1013
			$query->andWhere($query->expr()->gt('lastoccurence', $query->createNamedParameter($timeRange['start']->getTimeStamp())));
1014
		}
1015
		if ($timeRange && $timeRange['end']) {
1016
			$query->andWhere($query->expr()->lt('firstoccurence', $query->createNamedParameter($timeRange['end']->getTimeStamp())));
1017
		}
1018
1019
		$stmt = $query->execute();
1020
1021
		$result = [];
1022
		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1023
			if ($requirePostFilter) {
1024
				if (!$this->validateFilterForObject($row, $filters)) {
1025
					continue;
1026
				}
1027
			}
1028
			$result[] = $row['uri'];
1029
		}
1030
1031
		return $result;
1032
	}
1033
1034
	/**
1035
	 * Searches through all of a users calendars and calendar objects to find
1036
	 * an object with a specific UID.
1037
	 *
1038
	 * This method should return the path to this object, relative to the
1039
	 * calendar home, so this path usually only contains two parts:
1040
	 *
1041
	 * calendarpath/objectpath.ics
1042
	 *
1043
	 * If the uid is not found, return null.
1044
	 *
1045
	 * This method should only consider * objects that the principal owns, so
1046
	 * any calendars owned by other principals that also appear in this
1047
	 * collection should be ignored.
1048
	 *
1049
	 * @param string $principalUri
1050
	 * @param string $uid
1051
	 * @return string|null
1052
	 */
1053
	public function getCalendarObjectByUID($principalUri, $uid) {
1054
		$query = $this->db->getQueryBuilder();
1055
		$query->selectAlias('c.uri', 'calendaruri')->selectAlias('co.uri', 'objecturi')
1056
			->from('calendarobjects', 'co')
1057
			->leftJoin('co', 'calendars', 'c', $query->expr()->eq('co.calendarid', 'c.id'))
1058
			->where($query->expr()->eq('c.principaluri', $query->createNamedParameter($principalUri)))
1059
			->andWhere($query->expr()->eq('co.uid', $query->createNamedParameter($uid)));
1060
1061
		$stmt = $query->execute();
1062
1063
		if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1064
			return $row['calendaruri'] . '/' . $row['objecturi'];
1065
		}
1066
1067
		return null;
1068
	}
1069
1070
	/**
1071
	 * The getChanges method returns all the changes that have happened, since
1072
	 * the specified syncToken in the specified calendar.
1073
	 *
1074
	 * This function should return an array, such as the following:
1075
	 *
1076
	 * [
1077
	 *   'syncToken' => 'The current synctoken',
1078
	 *   'added'   => [
1079
	 *      'new.txt',
1080
	 *   ],
1081
	 *   'modified'   => [
1082
	 *      'modified.txt',
1083
	 *   ],
1084
	 *   'deleted' => [
1085
	 *      'foo.php.bak',
1086
	 *      'old.txt'
1087
	 *   ]
1088
	 * );
1089
	 *
1090
	 * The returned syncToken property should reflect the *current* syncToken
1091
	 * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
1092
	 * property This is * needed here too, to ensure the operation is atomic.
1093
	 *
1094
	 * If the $syncToken argument is specified as null, this is an initial
1095
	 * sync, and all members should be reported.
1096
	 *
1097
	 * The modified property is an array of nodenames that have changed since
1098
	 * the last token.
1099
	 *
1100
	 * The deleted property is an array with nodenames, that have been deleted
1101
	 * from collection.
1102
	 *
1103
	 * The $syncLevel argument is basically the 'depth' of the report. If it's
1104
	 * 1, you only have to report changes that happened only directly in
1105
	 * immediate descendants. If it's 2, it should also include changes from
1106
	 * the nodes below the child collections. (grandchildren)
1107
	 *
1108
	 * The $limit argument allows a client to specify how many results should
1109
	 * be returned at most. If the limit is not specified, it should be treated
1110
	 * as infinite.
1111
	 *
1112
	 * If the limit (infinite or not) is higher than you're willing to return,
1113
	 * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
1114
	 *
1115
	 * If the syncToken is expired (due to data cleanup) or unknown, you must
1116
	 * return null.
1117
	 *
1118
	 * The limit is 'suggestive'. You are free to ignore it.
1119
	 *
1120
	 * @param string $calendarId
1121
	 * @param string $syncToken
1122
	 * @param int $syncLevel
1123
	 * @param int $limit
1124
	 * @return array
1125
	 */
1126
	public function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null) {
1127
		// Current synctoken
1128
		$stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*calendars` WHERE `id` = ?');
1129
		$stmt->execute([ $calendarId ]);
1130
		$currentToken = $stmt->fetchColumn(0);
1131
1132
		if ($currentToken === null) {
1133
			return null;
1134
		}
1135
1136
		$result = [
1137
			'syncToken' => $currentToken,
1138
			'added'     => [],
1139
			'modified'  => [],
1140
			'deleted'   => [],
1141
		];
1142
1143
		if ($syncToken) {
1144
			$query = 'SELECT `uri`, `operation` FROM `*PREFIX*calendarchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `calendarid` = ? ORDER BY `synctoken`';
1145
1146
			// Fetching all changes
1147
			$stmt = $this->db->prepare($query, $limit ?: null, $limit ? 0 : null);
1148
			$stmt->execute([$syncToken, $currentToken, $calendarId]);
1149
1150
			$changes = [];
1151
1152
			// This loop ensures that any duplicates are overwritten, only the
1153
			// last change on a node is relevant.
1154
			while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1155
				$changes[$row['uri']] = $row['operation'];
1156
			}
1157
1158
			foreach ($changes as $uri => $operation) {
1159
				switch ($operation) {
1160
					case 1:
1161
						$result['added'][] = $uri;
1162
						break;
1163
					case 2:
1164
						$result['modified'][] = $uri;
1165
						break;
1166
					case 3:
1167
						$result['deleted'][] = $uri;
1168
						break;
1169
				}
1170
			}
1171
		} else {
1172
			// No synctoken supplied, this is the initial sync.
1173
			$query = 'SELECT `uri` FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ?';
1174
			$stmt = $this->db->prepare($query);
1175
			$stmt->execute([$calendarId]);
1176
1177
			$result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
1178
		}
1179
		return $result;
1180
	}
1181
1182
	/**
1183
	 * Returns a list of subscriptions for a principal.
1184
	 *
1185
	 * Every subscription is an array with the following keys:
1186
	 *  * id, a unique id that will be used by other functions to modify the
1187
	 *    subscription. This can be the same as the uri or a database key.
1188
	 *  * uri. This is just the 'base uri' or 'filename' of the subscription.
1189
	 *  * principaluri. The owner of the subscription. Almost always the same as
1190
	 *    principalUri passed to this method.
1191
	 *
1192
	 * Furthermore, all the subscription info must be returned too:
1193
	 *
1194
	 * 1. {DAV:}displayname
1195
	 * 2. {http://apple.com/ns/ical/}refreshrate
1196
	 * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
1197
	 *    should not be stripped).
1198
	 * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
1199
	 *    should not be stripped).
1200
	 * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
1201
	 *    attachments should not be stripped).
1202
	 * 6. {http://calendarserver.org/ns/}source (Must be a
1203
	 *     Sabre\DAV\Property\Href).
1204
	 * 7. {http://apple.com/ns/ical/}calendar-color
1205
	 * 8. {http://apple.com/ns/ical/}calendar-order
1206
	 * 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
1207
	 *    (should just be an instance of
1208
	 *    Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of
1209
	 *    default components).
1210
	 *
1211
	 * @param string $principalUri
1212
	 * @return array
1213
	 */
1214
	public function getSubscriptionsForUser($principalUri) {
1215
		$fields = \array_values($this->subscriptionPropertyMap);
1216
		$fields[] = 'id';
1217
		$fields[] = 'uri';
1218
		$fields[] = 'source';
1219
		$fields[] = 'principaluri';
1220
		$fields[] = 'lastmodified';
1221
1222
		$query = $this->db->getQueryBuilder();
1223
		$query->select($fields)
1224
			->from('calendarsubscriptions')
1225
			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
1226
			->orderBy('calendarorder', 'asc');
1227
		$stmt =$query->execute();
1228
1229
		$subscriptions = [];
1230
		while ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1231
			$subscription = [
1232
				'id'           => $row['id'],
1233
				'uri'          => $row['uri'],
1234
				'principaluri' => $row['principaluri'],
1235
				'source'       => $row['source'],
1236
				'lastmodified' => $row['lastmodified'],
1237
1238
				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
1239
			];
1240
1241
			foreach ($this->subscriptionPropertyMap as $xmlName=>$dbName) {
1242
				if ($row[$dbName] !== null) {
1243
					$subscription[$xmlName] = $row[$dbName];
1244
				}
1245
			}
1246
1247
			$subscriptions[] = $subscription;
1248
		}
1249
1250
		return $subscriptions;
1251
	}
1252
1253
	/**
1254
	 * Creates a new subscription for a principal.
1255
	 *
1256
	 * If the creation was a success, an id must be returned that can be used to reference
1257
	 * this subscription in other methods, such as updateSubscription.
1258
	 *
1259
	 * @param string $principalUri
1260
	 * @param string $uri
1261
	 * @param array $properties
1262
	 * @return mixed
1263
	 * @throws Forbidden
1264
	 */
1265
	public function createSubscription($principalUri, $uri, array $properties) {
1266
		if (!isset($properties['{http://calendarserver.org/ns/}source'])) {
1267
			throw new Forbidden('The {http://calendarserver.org/ns/}source property is required when creating subscriptions');
1268
		}
1269
1270
		$values = [
1271
			'principaluri' => $principalUri,
1272
			'uri'          => $uri,
1273
			'source'       => $properties['{http://calendarserver.org/ns/}source']->getHref(),
1274
			'lastmodified' => \time(),
1275
		];
1276
1277
		$propertiesBoolean = ['striptodos', 'stripalarms', 'stripattachments'];
1278
1279
		foreach ($this->subscriptionPropertyMap as $xmlName=>$dbName) {
1280
			if (\array_key_exists($xmlName, $properties)) {
1281
				$values[$dbName] = $properties[$xmlName];
1282
				if (\in_array($dbName, $propertiesBoolean, true)) {
1283
					$values[$dbName] = true;
1284
				}
1285
			}
1286
		}
1287
1288
		$valuesToInsert = [];
1289
1290
		$query = $this->db->getQueryBuilder();
1291
1292
		foreach (\array_keys($values) as $name) {
1293
			$valuesToInsert[$name] = $query->createNamedParameter($values[$name]);
1294
		}
1295
1296
		$query->insert('calendarsubscriptions')
1297
			->values($valuesToInsert)
1298
			->execute();
1299
1300
		return $this->db->lastInsertId('*PREFIX*calendarsubscriptions');
1301
	}
1302
1303
	/**
1304
	 * Updates a subscription
1305
	 *
1306
	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
1307
	 * To do the actual updates, you must tell this object which properties
1308
	 * you're going to process with the handle() method.
1309
	 *
1310
	 * Calling the handle method is like telling the PropPatch object "I
1311
	 * promise I can handle updating this property".
1312
	 *
1313
	 * Read the PropPatch documentation for more info and examples.
1314
	 *
1315
	 * @param mixed $subscriptionId
1316
	 * @param PropPatch $propPatch
1317
	 * @return void
1318
	 */
1319
	public function updateSubscription($subscriptionId, PropPatch $propPatch) {
1320
		$supportedProperties = \array_keys($this->subscriptionPropertyMap);
1321
		$supportedProperties[] = '{http://calendarserver.org/ns/}source';
1322
1323
		$propPatch->handle($supportedProperties, function ($mutations) use ($subscriptionId) {
1324
			$newValues = [];
1325
1326
			foreach ($mutations as $propertyName=>$propertyValue) {
1327
				if ($propertyName === '{http://calendarserver.org/ns/}source') {
1328
					$newValues['source'] = $propertyValue->getHref();
1329
				} else {
1330
					$fieldName = $this->subscriptionPropertyMap[$propertyName];
1331
					$newValues[$fieldName] = $propertyValue;
1332
				}
1333
			}
1334
1335
			$query = $this->db->getQueryBuilder();
1336
			$query->update('calendarsubscriptions')
1337
				->set('lastmodified', $query->createNamedParameter(\time()));
1338
			foreach ($newValues as $fieldName=>$value) {
1339
				$query->set($fieldName, $query->createNamedParameter($value));
1340
			}
1341
			$query->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
1342
				->execute();
1343
1344
			return true;
1345
		});
1346
	}
1347
1348
	/**
1349
	 * Deletes a subscription.
1350
	 *
1351
	 * @param mixed $subscriptionId
1352
	 * @return void
1353
	 */
1354
	public function deleteSubscription($subscriptionId) {
1355
		$query = $this->db->getQueryBuilder();
1356
		$query->delete('calendarsubscriptions')
1357
			->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
1358
			->execute();
1359
	}
1360
1361
	/**
1362
	 * Returns a single scheduling object for the inbox collection.
1363
	 *
1364
	 * The returned array should contain the following elements:
1365
	 *   * uri - A unique basename for the object. This will be used to
1366
	 *           construct a full uri.
1367
	 *   * calendardata - The iCalendar object
1368
	 *   * lastmodified - The last modification date. Can be an int for a unix
1369
	 *                    timestamp, or a PHP DateTime object.
1370
	 *   * etag - A unique token that must change if the object changed.
1371
	 *   * size - The size of the object, in bytes.
1372
	 *
1373
	 * @param string $principalUri
1374
	 * @param string $objectUri
1375
	 * @return array
1376
	 */
1377
	public function getSchedulingObject($principalUri, $objectUri) {
1378
		$query = $this->db->getQueryBuilder();
1379
		$stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
1380
			->from('schedulingobjects')
1381
			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
1382
			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
1383
			->execute();
1384
1385
		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
1386
1387
		if (!$row) {
1388
			return null;
1389
		}
1390
1391
		return [
1392
				'uri'          => $row['uri'],
1393
				'calendardata' => $row['calendardata'],
1394
				'lastmodified' => $row['lastmodified'],
1395
				'etag'         => '"' . $row['etag'] . '"',
1396
				'size'         => (int)$row['size'],
1397
		];
1398
	}
1399
1400
	/**
1401
	 * Returns all scheduling objects for the inbox collection.
1402
	 *
1403
	 * These objects should be returned as an array. Every item in the array
1404
	 * should follow the same structure as returned from getSchedulingObject.
1405
	 *
1406
	 * The main difference is that 'calendardata' is optional.
1407
	 *
1408
	 * @param string $principalUri
1409
	 * @return array
1410
	 */
1411
	public function getSchedulingObjects($principalUri) {
1412
		$query = $this->db->getQueryBuilder();
1413
		$stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
1414
				->from('schedulingobjects')
1415
				->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
1416
				->execute();
1417
1418
		$result = [];
1419
		foreach ($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
1420
			$result[] = [
1421
					'calendardata' => $row['calendardata'],
1422
					'uri'          => $row['uri'],
1423
					'lastmodified' => $row['lastmodified'],
1424
					'etag'         => '"' . $row['etag'] . '"',
1425
					'size'         => (int)$row['size'],
1426
			];
1427
		}
1428
1429
		return $result;
1430
	}
1431
1432
	/**
1433
	 * Deletes a scheduling object from the inbox collection.
1434
	 *
1435
	 * @param string $principalUri
1436
	 * @param string $objectUri
1437
	 * @return void
1438
	 */
1439
	public function deleteSchedulingObject($principalUri, $objectUri) {
1440
		$query = $this->db->getQueryBuilder();
1441
		$query->delete('schedulingobjects')
1442
				->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
1443
				->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
1444
				->execute();
1445
	}
1446
1447
	/**
1448
	 * Creates a new scheduling object. This should land in a users' inbox.
1449
	 *
1450
	 * @param string $principalUri
1451
	 * @param string $objectUri
1452
	 * @param string $objectData
1453
	 * @return void
1454
	 */
1455
	public function createSchedulingObject($principalUri, $objectUri, $objectData) {
1456
		$query = $this->db->getQueryBuilder();
1457
		$query->insert('schedulingobjects')
1458
			->values([
1459
				'principaluri' => $query->createNamedParameter($principalUri),
1460
				'calendardata' => $query->createNamedParameter($objectData, IQueryBuilder::PARAM_LOB),
1461
				'uri' => $query->createNamedParameter($objectUri),
1462
				'lastmodified' => $query->createNamedParameter(\time()),
1463
				'etag' => $query->createNamedParameter(\md5($objectData)),
1464
				'size' => $query->createNamedParameter(\strlen($objectData))
1465
			])
1466
			->execute();
1467
	}
1468
1469
	/**
1470
	 * Adds a change record to the calendarchanges table.
1471
	 *
1472
	 * @param mixed $calendarId
1473
	 * @param string $objectUri
1474
	 * @param int $operation 1 = add, 2 = modify, 3 = delete.
1475
	 * @return void
1476
	 */
1477
	protected function addChange($calendarId, $objectUri, $operation) {
1478
		$stmt = $this->db->prepare('INSERT INTO `*PREFIX*calendarchanges` (`uri`, `synctoken`, `calendarid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*calendars` WHERE `id` = ?');
1479
		$stmt->execute([
1480
			$objectUri,
1481
			$calendarId,
1482
			$operation,
1483
			$calendarId
1484
		]);
1485
		$stmt = $this->db->prepare('UPDATE `*PREFIX*calendars` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?');
1486
		$stmt->execute([
1487
			$calendarId
1488
		]);
1489
	}
1490
1491
	/**
1492
	 * Parses some information from calendar objects, used for optimized
1493
	 * calendar-queries.
1494
	 *
1495
	 * Returns an array with the following keys:
1496
	 *   * etag - An md5 checksum of the object without the quotes.
1497
	 *   * size - Size of the object in bytes
1498
	 *   * componentType - VEVENT, VTODO or VJOURNAL
1499
	 *   * firstOccurence
1500
	 *   * lastOccurence
1501
	 *   * uid - value of the UID property
1502
	 *
1503
	 * @param string $calendarData
1504
	 * @return array
1505
	 * @throws DAV\Exception\BadRequest
1506
	 * @throws \Sabre\VObject\Recur\MaxInstancesExceededException
1507
	 * @throws \Sabre\VObject\Recur\NoInstancesException
1508
	 */
1509
	public function getDenormalizedData($calendarData) {
1510
		$vObject = Reader::read($calendarData);
1511
		$componentType = null;
1512
		$component = null;
1513
		$firstOccurrence = null;
1514
		$lastOccurrence = null;
1515
		$uid = null;
1516
		$classification = self::CLASSIFICATION_PUBLIC;
1517
		foreach ($vObject->getComponents() as $component) {
1518
			if ($component->name!=='VTIMEZONE') {
1519
				$componentType = $component->name;
1520
				$uid = (string)$component->UID;
1521
				break;
1522
			}
1523
		}
1524
		if (!$componentType) {
1525
			throw new DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
1526
		}
1527
		if ($componentType === 'VEVENT' && $component->DTSTART) {
1528
			$firstOccurrence = $component->DTSTART->getDateTime()->getTimeStamp();
1529
			// Finding the last occurrence is a bit harder
1530
			if (!isset($component->RRULE)) {
1531
				if (isset($component->DTEND)) {
1532
					$lastOccurrence = $component->DTEND->getDateTime()->getTimeStamp();
1533
				} elseif (isset($component->DURATION)) {
1534
					$endDate = clone $component->DTSTART->getDateTime();
1535
					$endDate->add(DateTimeParser::parse($component->DURATION->getValue()));
1536
					$lastOccurrence = $endDate->getTimeStamp();
1537
				} elseif (!$component->DTSTART->hasTime()) {
1538
					$endDate = clone $component->DTSTART->getDateTime();
1539
					$endDate->modify('+1 day');
1540
					$lastOccurrence = $endDate->getTimeStamp();
1541
				} else {
1542
					$lastOccurrence = $firstOccurrence;
1543
				}
1544
			} else {
1545
				$it = new EventIterator($vObject, (string)$component->UID);
0 ignored issues
show
Bug introduced by
It seems like $vObject defined by \Sabre\VObject\Reader::read($calendarData) on line 1510 can be null; however, Sabre\VObject\Recur\EventIterator::__construct() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
1546
				$maxDate = new \DateTime(self::MAX_DATE);
1547
				if ($it->isInfinite()) {
1548
					$lastOccurrence = $maxDate->getTimestamp();
1549
				} else {
1550
					$end = $it->getDtEnd();
1551
					while ($it->valid() && $end < $maxDate) {
1552
						$end = $it->getDtEnd();
1553
						$it->next();
1554
					}
1555
					$lastOccurrence = $end->getTimestamp();
1556
				}
1557
			}
1558
		}
1559
1560
		if ($component->CLASS) {
1561
			$classification = self::CLASSIFICATION_PRIVATE;
1562
			switch ($component->CLASS->getValue()) {
1563
				case 'PUBLIC':
1564
					$classification = self::CLASSIFICATION_PUBLIC;
1565
					break;
1566
				case 'CONFIDENTIAL':
1567
					$classification = self::CLASSIFICATION_CONFIDENTIAL;
1568
					break;
1569
			}
1570
		}
1571
		return [
1572
			'etag' => \md5($calendarData),
1573
			'size' => \strlen($calendarData),
1574
			'componentType' => $componentType,
1575
			'firstOccurence' => $firstOccurrence === null ? null : \max(0, $firstOccurrence),
1576
			'lastOccurence'  => $lastOccurrence,
1577
			'uid' => $uid,
1578
			'classification' => $classification
1579
		];
1580
	}
1581
1582
	private function readBlob($cardData) {
1583
		if (\is_resource($cardData)) {
1584
			return \stream_get_contents($cardData);
1585
		}
1586
1587
		return $cardData;
1588
	}
1589
1590
	/**
1591
	 * @param IShareable $shareable
1592
	 * @param array $add
1593
	 * @param array $remove
1594
	 */
1595
	public function updateShares($shareable, $add, $remove) {
1596
		$this->sharingBackend->updateShares($shareable, $add, $remove);
1597
	}
1598
1599
	/**
1600
	 * @param int $resourceId
1601
	 * @return array
1602
	 */
1603
	public function getShares($resourceId) {
1604
		return $this->sharingBackend->getShares($resourceId);
1605
	}
1606
1607
	/**
1608
	 * @param boolean $value
1609
	 * @param \OCA\DAV\CalDAV\Calendar $calendar
1610
	 * @return string|null
1611
	 */
1612
	public function setPublishStatus($value, $calendar) {
1613
		$query = $this->db->getQueryBuilder();
1614
		if ($value) {
1615
			$publicUri = $this->random->generate(16, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS);
1616
			$query->insert('dav_shares')
1617
				->values([
1618
					'principaluri' => $query->createNamedParameter($calendar->getPrincipalURI()),
1619
					'type' => $query->createNamedParameter('calendar'),
1620
					'access' => $query->createNamedParameter(self::ACCESS_PUBLIC),
1621
					'resourceid' => $query->createNamedParameter($calendar->getResourceId()),
1622
					'publicuri' => $query->createNamedParameter($publicUri)
1623
				]);
1624
			$query->execute();
1625
			return $publicUri;
1626
		}
1627
		$query->delete('dav_shares')
1628
			->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
1629
			->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)));
1630
		$query->execute();
1631
		return null;
1632
	}
1633
1634
	/**
1635
	 * @param \OCA\DAV\CalDAV\Calendar $calendar
1636
	 * @return mixed
1637
	 */
1638
	public function getPublishStatus($calendar) {
1639
		$query = $this->db->getQueryBuilder();
1640
		$result = $query->select('publicuri')
1641
			->from('dav_shares')
1642
			->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
1643
			->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
1644
			->execute();
1645
1646
		$row = $result->fetch();
1647
		$result->closeCursor();
1648
		return $row ? \reset($row) : false;
1649
	}
1650
1651
	/**
1652
	 * @param int $resourceId
1653
	 * @param array $acl
1654
	 * @return array
1655
	 */
1656
	public function applyShareAcl($resourceId, $acl) {
1657
		return $this->sharingBackend->applyShareAcl($resourceId, $acl);
1658
	}
1659
1660
	private function convertPrincipal($principalUri, $toV2 = null) {
1661
		if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
1662
			list(, $name) = \Sabre\Uri\split($principalUri);
1663
			$toV2 = $toV2 === null ? !$this->legacyMode : $toV2;
1664
			if ($toV2) {
1665
				return "principals/users/$name";
1666
			}
1667
			return "principals/$name";
1668
		}
1669
		return $principalUri;
1670
	}
1671
}
1672