Completed
Push — master ( 0913bc...b754a2 )
by Lukas
13:23
created

CalDavBackend::createCalendarObject()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 49
Code Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 36
nc 2
nop 3
dl 0
loc 49
rs 9.2258
c 0
b 0
f 0
1
<?php
2
/**
3
 * @copyright Copyright (c) 2016, ownCloud, Inc.
4
 * @copyright Copyright (c) 2017 Georg Ehrke
5
 *
6
 * @author Georg Ehrke <[email protected]>
7
 * @author Joas Schilling <[email protected]>
8
 * @author Lukas Reschke <[email protected]>
9
 * @author nhirokinet <[email protected]>
10
 * @author Robin Appelman <[email protected]>
11
 * @author Roeland Jago Douma <[email protected]>
12
 * @author Stefan Weil <[email protected]>
13
 * @author Thomas Citharel <[email protected]>
14
 * @author Thomas Müller <[email protected]>
15
 *
16
 * @license AGPL-3.0
17
 *
18
 * This code is free software: you can redistribute it and/or modify
19
 * it under the terms of the GNU Affero General Public License, version 3,
20
 * as published by the Free Software Foundation.
21
 *
22
 * This program is distributed in the hope that it will be useful,
23
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25
 * GNU Affero General Public License for more details.
26
 *
27
 * You should have received a copy of the GNU Affero General Public License, version 3,
28
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
29
 *
30
 */
31
32
namespace OCA\DAV\CalDAV;
33
34
use OCA\DAV\DAV\Sharing\IShareable;
35
use OCP\DB\QueryBuilder\IQueryBuilder;
36
use OCA\DAV\Connector\Sabre\Principal;
37
use OCA\DAV\DAV\Sharing\Backend;
38
use OCP\IDBConnection;
39
use OCP\IGroupManager;
40
use OCP\IUser;
41
use OCP\IUserManager;
42
use OCP\Security\ISecureRandom;
43
use Sabre\CalDAV\Backend\AbstractBackend;
44
use Sabre\CalDAV\Backend\SchedulingSupport;
45
use Sabre\CalDAV\Backend\SubscriptionSupport;
46
use Sabre\CalDAV\Backend\SyncSupport;
47
use Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp;
48
use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet;
49
use Sabre\DAV;
50
use Sabre\DAV\Exception\Forbidden;
51
use Sabre\DAV\Exception\NotFound;
52
use Sabre\DAV\PropPatch;
53
use Sabre\HTTP\URLUtil;
54
use Sabre\VObject\Component\VCalendar;
55
use Sabre\VObject\DateTimeParser;
56
use Sabre\VObject\Reader;
57
use Sabre\VObject\Recur\EventIterator;
58
use Sabre\Uri;
59
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
60
use Symfony\Component\EventDispatcher\GenericEvent;
61
62
/**
63
 * Class CalDavBackend
64
 *
65
 * Code is heavily inspired by https://github.com/fruux/sabre-dav/blob/master/lib/CalDAV/Backend/PDO.php
66
 *
67
 * @package OCA\DAV\CalDAV
68
 */
69
class CalDavBackend extends AbstractBackend implements SyncSupport, SubscriptionSupport, SchedulingSupport {
70
71
	const PERSONAL_CALENDAR_URI = 'personal';
72
	const PERSONAL_CALENDAR_NAME = 'Personal';
73
74
	/**
75
	 * We need to specify a max date, because we need to stop *somewhere*
76
	 *
77
	 * On 32 bit system the maximum for a signed integer is 2147483647, so
78
	 * MAX_DATE cannot be higher than date('Y-m-d', 2147483647) which results
79
	 * in 2038-01-19 to avoid problems when the date is converted
80
	 * to a unix timestamp.
81
	 */
82
	const MAX_DATE = '2038-01-01';
83
84
	const ACCESS_PUBLIC = 4;
85
	const CLASSIFICATION_PUBLIC = 0;
86
	const CLASSIFICATION_PRIVATE = 1;
87
	const CLASSIFICATION_CONFIDENTIAL = 2;
88
89
	/**
90
	 * List of CalDAV properties, and how they map to database field names
91
	 * Add your own properties by simply adding on to this array.
92
	 *
93
	 * Note that only string-based properties are supported here.
94
	 *
95
	 * @var array
96
	 */
97
	public $propertyMap = [
98
		'{DAV:}displayname'                          => 'displayname',
99
		'{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description',
100
		'{urn:ietf:params:xml:ns:caldav}calendar-timezone'    => 'timezone',
101
		'{http://apple.com/ns/ical/}calendar-order'  => 'calendarorder',
102
		'{http://apple.com/ns/ical/}calendar-color'  => 'calendarcolor',
103
	];
104
105
	/**
106
	 * List of subscription properties, and how they map to database field names.
107
	 *
108
	 * @var array
109
	 */
110
	public $subscriptionPropertyMap = [
111
		'{DAV:}displayname'                                           => 'displayname',
112
		'{http://apple.com/ns/ical/}refreshrate'                      => 'refreshrate',
113
		'{http://apple.com/ns/ical/}calendar-order'                   => 'calendarorder',
114
		'{http://apple.com/ns/ical/}calendar-color'                   => 'calendarcolor',
115
		'{http://calendarserver.org/ns/}subscribed-strip-todos'       => 'striptodos',
116
		'{http://calendarserver.org/ns/}subscribed-strip-alarms'      => 'stripalarms',
117
		'{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments',
118
	];
119
120
	/** @var array properties to index */
121
	public static $indexProperties = ['CATEGORIES', 'COMMENT', 'DESCRIPTION',
122
		'LOCATION', 'RESOURCES', 'STATUS', 'SUMMARY', 'ATTENDEE', 'CONTACT',
123
		'ORGANIZER'];
124
125
	/** @var array parameters to index */
126
	public static $indexParameters = [
127
		'ATTENDEE' => ['CN'],
128
		'ORGANIZER' => ['CN'],
129
	];
130
131
	/**
132
	 * @var string[] Map of uid => display name
133
	 */
134
	protected $userDisplayNames;
135
136
	/** @var IDBConnection */
137
	private $db;
138
139
	/** @var Backend */
140
	private $sharingBackend;
141
142
	/** @var Principal */
143
	private $principalBackend;
144
145
	/** @var IUserManager */
146
	private $userManager;
147
148
	/** @var ISecureRandom */
149
	private $random;
150
151
	/** @var EventDispatcherInterface */
152
	private $dispatcher;
153
154
	/** @var bool */
155
	private $legacyEndpoint;
156
157
	/** @var string */
158
	private $dbObjectPropertiesTable = 'calendarobjects_props';
159
160
	/**
161
	 * CalDavBackend constructor.
162
	 *
163
	 * @param IDBConnection $db
164
	 * @param Principal $principalBackend
165
	 * @param IUserManager $userManager
166
	 * @param IGroupManager $groupManager
167
	 * @param ISecureRandom $random
168
	 * @param EventDispatcherInterface $dispatcher
169
	 * @param bool $legacyEndpoint
170
	 */
171
	public function __construct(IDBConnection $db,
172
								Principal $principalBackend,
173
								IUserManager $userManager,
174
								IGroupManager $groupManager,
175
								ISecureRandom $random,
176
								EventDispatcherInterface $dispatcher,
177
								$legacyEndpoint = false) {
178
		$this->db = $db;
179
		$this->principalBackend = $principalBackend;
180
		$this->userManager = $userManager;
181
		$this->sharingBackend = new Backend($this->db, $this->userManager, $groupManager, $principalBackend, 'calendar');
182
		$this->random = $random;
183
		$this->dispatcher = $dispatcher;
184
		$this->legacyEndpoint = $legacyEndpoint;
185
	}
186
187
	/**
188
	 * Return the number of calendars for a principal
189
	 *
190
	 * By default this excludes the automatically generated birthday calendar
191
	 *
192
	 * @param $principalUri
193
	 * @param bool $excludeBirthday
194
	 * @return int
195
	 */
196 View Code Duplication
	public function getCalendarsForUserCount($principalUri, $excludeBirthday = true) {
197
		$principalUri = $this->convertPrincipal($principalUri, true);
198
		$query = $this->db->getQueryBuilder();
199
		$query->select($query->createFunction('COUNT(*)'))
200
			->from('calendars')
201
			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
202
203
		if ($excludeBirthday) {
204
			$query->andWhere($query->expr()->neq('uri', $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI)));
205
		}
206
207
		return (int)$query->execute()->fetchColumn();
208
	}
209
210
	/**
211
	 * Returns a list of calendars for a principal.
212
	 *
213
	 * Every project is an array with the following keys:
214
	 *  * id, a unique id that will be used by other functions to modify the
215
	 *    calendar. This can be the same as the uri or a database key.
216
	 *  * uri, which the basename of the uri with which the calendar is
217
	 *    accessed.
218
	 *  * principaluri. The owner of the calendar. Almost always the same as
219
	 *    principalUri passed to this method.
220
	 *
221
	 * Furthermore it can contain webdav properties in clark notation. A very
222
	 * common one is '{DAV:}displayname'.
223
	 *
224
	 * Many clients also require:
225
	 * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
226
	 * For this property, you can just return an instance of
227
	 * Sabre\CalDAV\Property\SupportedCalendarComponentSet.
228
	 *
229
	 * If you return {http://sabredav.org/ns}read-only and set the value to 1,
230
	 * ACL will automatically be put in read-only mode.
231
	 *
232
	 * @param string $principalUri
233
	 * @return array
234
	 */
235
	function getCalendarsForUser($principalUri) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
236
		$principalUriOriginal = $principalUri;
237
		$principalUri = $this->convertPrincipal($principalUri, true);
238
		$fields = array_values($this->propertyMap);
239
		$fields[] = 'id';
240
		$fields[] = 'uri';
241
		$fields[] = 'synctoken';
242
		$fields[] = 'components';
243
		$fields[] = 'principaluri';
244
		$fields[] = 'transparent';
245
246
		// Making fields a comma-delimited list
247
		$query = $this->db->getQueryBuilder();
248
		$query->select($fields)->from('calendars')
249
				->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
250
				->orderBy('calendarorder', 'ASC');
251
		$stmt = $query->execute();
252
253
		$calendars = [];
254
		while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
255
256
			$components = [];
257
			if ($row['components']) {
258
				$components = explode(',',$row['components']);
259
			}
260
261
			$calendar = [
262
				'id' => $row['id'],
263
				'uri' => $row['uri'],
264
				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
265
				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
266
				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
267
				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
268
				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
269
				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
270
			];
271
272
			foreach($this->propertyMap as $xmlName=>$dbName) {
273
				$calendar[$xmlName] = $row[$dbName];
274
			}
275
276
			$this->addOwnerPrincipal($calendar);
277
278
			if (!isset($calendars[$calendar['id']])) {
279
				$calendars[$calendar['id']] = $calendar;
280
			}
281
		}
282
283
		$stmt->closeCursor();
284
285
		// query for shared calendars
286
		$principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
287
		$principals = array_map(function($principal) {
288
			return urldecode($principal);
289
		}, $principals);
290
		$principals[]= $principalUri;
291
292
		$fields = array_values($this->propertyMap);
293
		$fields[] = 'a.id';
294
		$fields[] = 'a.uri';
295
		$fields[] = 'a.synctoken';
296
		$fields[] = 'a.components';
297
		$fields[] = 'a.principaluri';
298
		$fields[] = 'a.transparent';
299
		$fields[] = 's.access';
300
		$query = $this->db->getQueryBuilder();
301
		$result = $query->select($fields)
302
			->from('dav_shares', 's')
303
			->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
304
			->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
305
			->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
306
			->setParameter('type', 'calendar')
307
			->setParameter('principaluri', $principals, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY)
308
			->execute();
309
310
		$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
311
		while($row = $result->fetch()) {
312
			if ($row['principaluri'] === $principalUri) {
313
				continue;
314
			}
315
316
			$readOnly = (int) $row['access'] === Backend::ACCESS_READ;
317 View Code Duplication
			if (isset($calendars[$row['id']])) {
318
				if ($readOnly) {
319
					// New share can not have more permissions then the old one.
320
					continue;
321
				}
322
				if (isset($calendars[$row['id']][$readOnlyPropertyName]) &&
323
					$calendars[$row['id']][$readOnlyPropertyName] === 0) {
324
					// Old share is already read-write, no more permissions can be gained
325
					continue;
326
				}
327
			}
328
329
			list(, $name) = Uri\split($row['principaluri']);
330
			$uri = $row['uri'] . '_shared_by_' . $name;
331
			$row['displayname'] = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
332
			$components = [];
333
			if ($row['components']) {
334
				$components = explode(',',$row['components']);
335
			}
336
			$calendar = [
337
				'id' => $row['id'],
338
				'uri' => $uri,
339
				'principaluri' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
340
				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
341
				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
342
				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
343
				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
344
				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
345
				$readOnlyPropertyName => $readOnly,
346
			];
347
348
			foreach($this->propertyMap as $xmlName=>$dbName) {
349
				$calendar[$xmlName] = $row[$dbName];
350
			}
351
352
			$this->addOwnerPrincipal($calendar);
353
354
			$calendars[$calendar['id']] = $calendar;
355
		}
356
		$result->closeCursor();
357
358
		return array_values($calendars);
359
	}
360
361
	public function getUsersOwnCalendars($principalUri) {
362
		$principalUri = $this->convertPrincipal($principalUri, true);
363
		$fields = array_values($this->propertyMap);
364
		$fields[] = 'id';
365
		$fields[] = 'uri';
366
		$fields[] = 'synctoken';
367
		$fields[] = 'components';
368
		$fields[] = 'principaluri';
369
		$fields[] = 'transparent';
370
		// Making fields a comma-delimited list
371
		$query = $this->db->getQueryBuilder();
372
		$query->select($fields)->from('calendars')
373
			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
374
			->orderBy('calendarorder', 'ASC');
375
		$stmt = $query->execute();
376
		$calendars = [];
377
		while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
378
			$components = [];
379
			if ($row['components']) {
380
				$components = explode(',',$row['components']);
381
			}
382
			$calendar = [
383
				'id' => $row['id'],
384
				'uri' => $row['uri'],
385
				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
386
				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
387
				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
388
				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
389
				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
390
			];
391
			foreach($this->propertyMap as $xmlName=>$dbName) {
392
				$calendar[$xmlName] = $row[$dbName];
393
			}
394
395
			$this->addOwnerPrincipal($calendar);
396
397
			if (!isset($calendars[$calendar['id']])) {
398
				$calendars[$calendar['id']] = $calendar;
399
			}
400
		}
401
		$stmt->closeCursor();
402
		return array_values($calendars);
403
	}
404
405
406 View Code Duplication
	private function getUserDisplayName($uid) {
407
		if (!isset($this->userDisplayNames[$uid])) {
408
			$user = $this->userManager->get($uid);
409
410
			if ($user instanceof IUser) {
411
				$this->userDisplayNames[$uid] = $user->getDisplayName();
412
			} else {
413
				$this->userDisplayNames[$uid] = $uid;
414
			}
415
		}
416
417
		return $this->userDisplayNames[$uid];
418
	}
419
	
420
	/**
421
	 * @return array
422
	 */
423
	public function getPublicCalendars() {
424
		$fields = array_values($this->propertyMap);
425
		$fields[] = 'a.id';
426
		$fields[] = 'a.uri';
427
		$fields[] = 'a.synctoken';
428
		$fields[] = 'a.components';
429
		$fields[] = 'a.principaluri';
430
		$fields[] = 'a.transparent';
431
		$fields[] = 's.access';
432
		$fields[] = 's.publicuri';
433
		$calendars = [];
434
		$query = $this->db->getQueryBuilder();
435
		$result = $query->select($fields)
436
			->from('dav_shares', 's')
437
			->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
438
			->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
439
			->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar')))
440
			->execute();
441
442
		while($row = $result->fetch()) {
443
			list(, $name) = Uri\split($row['principaluri']);
444
			$row['displayname'] = $row['displayname'] . "($name)";
445
			$components = [];
446
			if ($row['components']) {
447
				$components = explode(',',$row['components']);
448
			}
449
			$calendar = [
450
				'id' => $row['id'],
451
				'uri' => $row['publicuri'],
452
				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
453
				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
454
				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
455
				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
456
				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
457
				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], $this->legacyEndpoint),
458
				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
459
				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
460
			];
461
462
			foreach($this->propertyMap as $xmlName=>$dbName) {
463
				$calendar[$xmlName] = $row[$dbName];
464
			}
465
466
			$this->addOwnerPrincipal($calendar);
467
468
			if (!isset($calendars[$calendar['id']])) {
469
				$calendars[$calendar['id']] = $calendar;
470
			}
471
		}
472
		$result->closeCursor();
473
474
		return array_values($calendars);
475
	}
476
477
	/**
478
	 * @param string $uri
479
	 * @return array
480
	 * @throws NotFound
481
	 */
482
	public function getPublicCalendar($uri) {
483
		$fields = array_values($this->propertyMap);
484
		$fields[] = 'a.id';
485
		$fields[] = 'a.uri';
486
		$fields[] = 'a.synctoken';
487
		$fields[] = 'a.components';
488
		$fields[] = 'a.principaluri';
489
		$fields[] = 'a.transparent';
490
		$fields[] = 's.access';
491
		$fields[] = 's.publicuri';
492
		$query = $this->db->getQueryBuilder();
493
		$result = $query->select($fields)
494
			->from('dav_shares', 's')
495
			->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
496
			->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
497
			->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar')))
498
			->andWhere($query->expr()->eq('s.publicuri', $query->createNamedParameter($uri)))
499
			->execute();
500
501
		$row = $result->fetch(\PDO::FETCH_ASSOC);
502
503
		$result->closeCursor();
504
505
		if ($row === false) {
506
			throw new NotFound('Node with name \'' . $uri . '\' could not be found');
507
		}
508
509
		list(, $name) = Uri\split($row['principaluri']);
510
		$row['displayname'] = $row['displayname'] . ' ' . "($name)";
511
		$components = [];
512
		if ($row['components']) {
513
			$components = explode(',',$row['components']);
514
		}
515
		$calendar = [
516
			'id' => $row['id'],
517
			'uri' => $row['publicuri'],
518
			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
519
			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
520
			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
521
			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
522
			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
523
			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
524
			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
525
			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
526
		];
527
528
		foreach($this->propertyMap as $xmlName=>$dbName) {
529
			$calendar[$xmlName] = $row[$dbName];
530
		}
531
532
		$this->addOwnerPrincipal($calendar);
533
534
		return $calendar;
535
536
	}
537
538
	/**
539
	 * @param string $principal
540
	 * @param string $uri
541
	 * @return array|null
542
	 */
543
	public function getCalendarByUri($principal, $uri) {
544
		$fields = array_values($this->propertyMap);
545
		$fields[] = 'id';
546
		$fields[] = 'uri';
547
		$fields[] = 'synctoken';
548
		$fields[] = 'components';
549
		$fields[] = 'principaluri';
550
		$fields[] = 'transparent';
551
552
		// Making fields a comma-delimited list
553
		$query = $this->db->getQueryBuilder();
554
		$query->select($fields)->from('calendars')
555
			->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
556
			->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
557
			->setMaxResults(1);
558
		$stmt = $query->execute();
559
560
		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
561
		$stmt->closeCursor();
562
		if ($row === false) {
563
			return null;
564
		}
565
566
		$components = [];
567
		if ($row['components']) {
568
			$components = explode(',',$row['components']);
569
		}
570
571
		$calendar = [
572
			'id' => $row['id'],
573
			'uri' => $row['uri'],
574
			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
575
			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
576
			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
577
			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
578
			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
579
		];
580
581
		foreach($this->propertyMap as $xmlName=>$dbName) {
582
			$calendar[$xmlName] = $row[$dbName];
583
		}
584
585
		$this->addOwnerPrincipal($calendar);
586
587
		return $calendar;
588
	}
589
590
	public function getCalendarById($calendarId) {
591
		$fields = array_values($this->propertyMap);
592
		$fields[] = 'id';
593
		$fields[] = 'uri';
594
		$fields[] = 'synctoken';
595
		$fields[] = 'components';
596
		$fields[] = 'principaluri';
597
		$fields[] = 'transparent';
598
599
		// Making fields a comma-delimited list
600
		$query = $this->db->getQueryBuilder();
601
		$query->select($fields)->from('calendars')
602
			->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)))
603
			->setMaxResults(1);
604
		$stmt = $query->execute();
605
606
		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
607
		$stmt->closeCursor();
608
		if ($row === false) {
609
			return null;
610
		}
611
612
		$components = [];
613
		if ($row['components']) {
614
			$components = explode(',',$row['components']);
615
		}
616
617
		$calendar = [
618
			'id' => $row['id'],
619
			'uri' => $row['uri'],
620
			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
621
			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
622
			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
623
			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
624
			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
625
		];
626
627
		foreach($this->propertyMap as $xmlName=>$dbName) {
628
			$calendar[$xmlName] = $row[$dbName];
629
		}
630
631
		$this->addOwnerPrincipal($calendar);
632
633
		return $calendar;
634
	}
635
636
	/**
637
	 * Creates a new calendar for a principal.
638
	 *
639
	 * If the creation was a success, an id must be returned that can be used to reference
640
	 * this calendar in other methods, such as updateCalendar.
641
	 *
642
	 * @param string $principalUri
643
	 * @param string $calendarUri
644
	 * @param array $properties
645
	 * @return int
646
	 * @suppress SqlInjectionChecker
647
	 */
648
	function createCalendar($principalUri, $calendarUri, array $properties) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
649
		$values = [
650
			'principaluri' => $this->convertPrincipal($principalUri, true),
651
			'uri'          => $calendarUri,
652
			'synctoken'    => 1,
653
			'transparent'  => 0,
654
			'components'   => 'VEVENT,VTODO',
655
			'displayname'  => $calendarUri
656
		];
657
658
		// Default value
659
		$sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
660
		if (isset($properties[$sccs])) {
661
			if (!($properties[$sccs] instanceof SupportedCalendarComponentSet)) {
0 ignored issues
show
Bug introduced by
The class Sabre\CalDAV\Xml\Propert...tedCalendarComponentSet does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
662
				throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet');
663
			}
664
			$values['components'] = implode(',',$properties[$sccs]->getValue());
665
		}
666
		$transp = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
667
		if (isset($properties[$transp])) {
668
			$values['transparent'] = (int) ($properties[$transp]->getValue() === 'transparent');
669
		}
670
671
		foreach($this->propertyMap as $xmlName=>$dbName) {
672
			if (isset($properties[$xmlName])) {
673
				$values[$dbName] = $properties[$xmlName];
674
			}
675
		}
676
677
		$query = $this->db->getQueryBuilder();
678
		$query->insert('calendars');
679
		foreach($values as $column => $value) {
680
			$query->setValue($column, $query->createNamedParameter($value));
681
		}
682
		$query->execute();
683
		$calendarId = $query->getLastInsertId();
684
685
		$this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendar', new GenericEvent(
686
			'\OCA\DAV\CalDAV\CalDavBackend::createCalendar',
687
			[
688
				'calendarId' => $calendarId,
689
				'calendarData' => $this->getCalendarById($calendarId),
690
		]));
691
692
		return $calendarId;
693
	}
694
695
	/**
696
	 * Updates properties for a calendar.
697
	 *
698
	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
699
	 * To do the actual updates, you must tell this object which properties
700
	 * you're going to process with the handle() method.
701
	 *
702
	 * Calling the handle method is like telling the PropPatch object "I
703
	 * promise I can handle updating this property".
704
	 *
705
	 * Read the PropPatch documentation for more info and examples.
706
	 *
707
	 * @param mixed $calendarId
708
	 * @param PropPatch $propPatch
709
	 * @return void
710
	 */
711
	function updateCalendar($calendarId, PropPatch $propPatch) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
712
		$supportedProperties = array_keys($this->propertyMap);
713
		$supportedProperties[] = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
714
715
		/**
716
		 * @suppress SqlInjectionChecker
717
		 */
718
		$propPatch->handle($supportedProperties, function($mutations) use ($calendarId) {
719
			$newValues = [];
720
			foreach ($mutations as $propertyName => $propertyValue) {
721
722
				switch ($propertyName) {
723
					case '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
724
						$fieldName = 'transparent';
725
						$newValues[$fieldName] = (int) ($propertyValue->getValue() === 'transparent');
726
						break;
727
					default :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a DEFAULT statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in the default statement.

switch ($expr) {
    default : //wrong
        doSomething();
        break;
}

switch ($expr) {
    default: //right
        doSomething();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
728
						$fieldName = $this->propertyMap[$propertyName];
729
						$newValues[$fieldName] = $propertyValue;
730
						break;
731
				}
732
733
			}
734
			$query = $this->db->getQueryBuilder();
735
			$query->update('calendars');
736
			foreach ($newValues as $fieldName => $value) {
737
				$query->set($fieldName, $query->createNamedParameter($value));
738
			}
739
			$query->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
740
			$query->execute();
741
742
			$this->addChange($calendarId, "", 2);
743
744
			$this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendar', new GenericEvent(
745
				'\OCA\DAV\CalDAV\CalDavBackend::updateCalendar',
746
				[
747
					'calendarId' => $calendarId,
748
					'calendarData' => $this->getCalendarById($calendarId),
749
					'shares' => $this->getShares($calendarId),
750
					'propertyMutations' => $mutations,
751
			]));
752
753
			return true;
754
		});
755
	}
756
757
	/**
758
	 * Delete a calendar and all it's objects
759
	 *
760
	 * @param mixed $calendarId
761
	 * @return void
762
	 */
763
	function deleteCalendar($calendarId) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
764
		$this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar', new GenericEvent(
765
			'\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar',
766
			[
767
				'calendarId' => $calendarId,
768
				'calendarData' => $this->getCalendarById($calendarId),
769
				'shares' => $this->getShares($calendarId),
770
		]));
771
772
		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ?');
773
		$stmt->execute([$calendarId]);
774
775
		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendars` WHERE `id` = ?');
776
		$stmt->execute([$calendarId]);
777
778
		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarchanges` WHERE `calendarid` = ?');
779
		$stmt->execute([$calendarId]);
780
781
		$this->sharingBackend->deleteAllShares($calendarId);
782
783
		$query = $this->db->getQueryBuilder();
784
		$query->delete($this->dbObjectPropertiesTable)
785
			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
786
			->execute();
787
	}
788
789
	/**
790
	 * Delete all of an user's shares
791
	 *
792
	 * @param string $principaluri
793
	 * @return void
794
	 */
795
	function deleteAllSharesByUser($principaluri) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
796
		$this->sharingBackend->deleteAllSharesByUser($principaluri);
797
	}
798
799
	/**
800
	 * Returns all calendar objects within a calendar.
801
	 *
802
	 * Every item contains an array with the following keys:
803
	 *   * calendardata - The iCalendar-compatible calendar data
804
	 *   * uri - a unique key which will be used to construct the uri. This can
805
	 *     be any arbitrary string, but making sure it ends with '.ics' is a
806
	 *     good idea. This is only the basename, or filename, not the full
807
	 *     path.
808
	 *   * lastmodified - a timestamp of the last modification time
809
	 *   * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
810
	 *   '"abcdef"')
811
	 *   * size - The size of the calendar objects, in bytes.
812
	 *   * component - optional, a string containing the type of object, such
813
	 *     as 'vevent' or 'vtodo'. If specified, this will be used to populate
814
	 *     the Content-Type header.
815
	 *
816
	 * Note that the etag is optional, but it's highly encouraged to return for
817
	 * speed reasons.
818
	 *
819
	 * The calendardata is also optional. If it's not returned
820
	 * 'getCalendarObject' will be called later, which *is* expected to return
821
	 * calendardata.
822
	 *
823
	 * If neither etag or size are specified, the calendardata will be
824
	 * used/fetched to determine these numbers. If both are specified the
825
	 * amount of times this is needed is reduced by a great degree.
826
	 *
827
	 * @param mixed $calendarId
828
	 * @return array
829
	 */
830
	function getCalendarObjects($calendarId) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
831
		$query = $this->db->getQueryBuilder();
832
		$query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'componenttype', 'classification'])
833
			->from('calendarobjects')
834
			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)));
835
		$stmt = $query->execute();
836
837
		$result = [];
838
		foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
839
			$result[] = [
840
					'id'           => $row['id'],
841
					'uri'          => $row['uri'],
842
					'lastmodified' => $row['lastmodified'],
843
					'etag'         => '"' . $row['etag'] . '"',
844
					'calendarid'   => $row['calendarid'],
845
					'size'         => (int)$row['size'],
846
					'component'    => strtolower($row['componenttype']),
847
					'classification'=> (int)$row['classification']
848
			];
849
		}
850
851
		return $result;
852
	}
853
854
	/**
855
	 * Returns information from a single calendar object, based on it's object
856
	 * uri.
857
	 *
858
	 * The object uri is only the basename, or filename and not a full path.
859
	 *
860
	 * The returned array must have the same keys as getCalendarObjects. The
861
	 * 'calendardata' object is required here though, while it's not required
862
	 * for getCalendarObjects.
863
	 *
864
	 * This method must return null if the object did not exist.
865
	 *
866
	 * @param mixed $calendarId
867
	 * @param string $objectUri
868
	 * @return array|null
869
	 */
870
	function getCalendarObject($calendarId, $objectUri) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
871
872
		$query = $this->db->getQueryBuilder();
873
		$query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification'])
874
				->from('calendarobjects')
875
				->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
876
				->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)));
877
		$stmt = $query->execute();
878
		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
879
880
		if(!$row) return null;
881
882
		return [
883
				'id'            => $row['id'],
884
				'uri'           => $row['uri'],
885
				'lastmodified'  => $row['lastmodified'],
886
				'etag'          => '"' . $row['etag'] . '"',
887
				'calendarid'    => $row['calendarid'],
888
				'size'          => (int)$row['size'],
889
				'calendardata'  => $this->readBlob($row['calendardata']),
890
				'component'     => strtolower($row['componenttype']),
891
				'classification'=> (int)$row['classification']
892
		];
893
	}
894
895
	/**
896
	 * Returns a list of calendar objects.
897
	 *
898
	 * This method should work identical to getCalendarObject, but instead
899
	 * return all the calendar objects in the list as an array.
900
	 *
901
	 * If the backend supports this, it may allow for some speed-ups.
902
	 *
903
	 * @param mixed $calendarId
904
	 * @param string[] $uris
905
	 * @return array
906
	 */
907
	function getMultipleCalendarObjects($calendarId, array $uris) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
908
		if (empty($uris)) {
909
			return [];
910
		}
911
912
		$chunks = array_chunk($uris, 100);
913
		$objects = [];
914
915
		$query = $this->db->getQueryBuilder();
916
		$query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification'])
917
			->from('calendarobjects')
918
			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
919
			->andWhere($query->expr()->in('uri', $query->createParameter('uri')));
920
921
		foreach ($chunks as $uris) {
922
			$query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
923
			$result = $query->execute();
924
925
			while ($row = $result->fetch()) {
926
				$objects[] = [
927
					'id'           => $row['id'],
928
					'uri'          => $row['uri'],
929
					'lastmodified' => $row['lastmodified'],
930
					'etag'         => '"' . $row['etag'] . '"',
931
					'calendarid'   => $row['calendarid'],
932
					'size'         => (int)$row['size'],
933
					'calendardata' => $this->readBlob($row['calendardata']),
934
					'component'    => strtolower($row['componenttype']),
935
					'classification' => (int)$row['classification']
936
				];
937
			}
938
			$result->closeCursor();
939
		}
940
		return $objects;
941
	}
942
943
	/**
944
	 * Creates a new calendar object.
945
	 *
946
	 * The object uri is only the basename, or filename and not a full path.
947
	 *
948
	 * It is possible return an etag from this function, which will be used in
949
	 * the response to this PUT request. Note that the ETag must be surrounded
950
	 * by double-quotes.
951
	 *
952
	 * However, you should only really return this ETag if you don't mangle the
953
	 * calendar-data. If the result of a subsequent GET to this object is not
954
	 * the exact same as this request body, you should omit the ETag.
955
	 *
956
	 * @param mixed $calendarId
957
	 * @param string $objectUri
958
	 * @param string $calendarData
959
	 * @return string
960
	 */
961
	function createCalendarObject($calendarId, $objectUri, $calendarData) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
962
		$extraData = $this->getDenormalizedData($calendarData);
963
964
		$q = $this->db->getQueryBuilder();
965
		$q->select($q->createFunction('COUNT(*)'))
966
			->from('calendarobjects')
967
			->where($q->expr()->eq('calendarid', $q->createNamedParameter($calendarId)))
968
			->andWhere($q->expr()->eq('uid', $q->createNamedParameter($extraData['uid'])));
969
970
		$result = $q->execute();
971
		$count = (int) $result->fetchColumn();
972
		$result->closeCursor();
973
974
		if ($count !== 0) {
975
			throw new \Sabre\DAV\Exception\BadRequest('Calendar object with uid already exists in this calendar collection.');
976
		}
977
978
		$query = $this->db->getQueryBuilder();
979
		$query->insert('calendarobjects')
980
			->values([
981
				'calendarid' => $query->createNamedParameter($calendarId),
982
				'uri' => $query->createNamedParameter($objectUri),
983
				'calendardata' => $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB),
984
				'lastmodified' => $query->createNamedParameter(time()),
985
				'etag' => $query->createNamedParameter($extraData['etag']),
986
				'size' => $query->createNamedParameter($extraData['size']),
987
				'componenttype' => $query->createNamedParameter($extraData['componentType']),
988
				'firstoccurence' => $query->createNamedParameter($extraData['firstOccurence']),
989
				'lastoccurence' => $query->createNamedParameter($extraData['lastOccurence']),
990
				'classification' => $query->createNamedParameter($extraData['classification']),
991
				'uid' => $query->createNamedParameter($extraData['uid']),
992
			])
993
			->execute();
994
995
		$this->updateProperties($calendarId, $objectUri, $calendarData);
996
997
		$this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject', new GenericEvent(
998
			'\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject',
999
			[
1000
				'calendarId' => $calendarId,
1001
				'calendarData' => $this->getCalendarById($calendarId),
1002
				'shares' => $this->getShares($calendarId),
1003
				'objectData' => $this->getCalendarObject($calendarId, $objectUri),
1004
			]
1005
		));
1006
		$this->addChange($calendarId, $objectUri, 1);
1007
1008
		return '"' . $extraData['etag'] . '"';
1009
	}
1010
1011
	/**
1012
	 * Updates an existing calendarobject, based on it's uri.
1013
	 *
1014
	 * The object uri is only the basename, or filename and not a full path.
1015
	 *
1016
	 * It is possible return an etag from this function, which will be used in
1017
	 * the response to this PUT request. Note that the ETag must be surrounded
1018
	 * by double-quotes.
1019
	 *
1020
	 * However, you should only really return this ETag if you don't mangle the
1021
	 * calendar-data. If the result of a subsequent GET to this object is not
1022
	 * the exact same as this request body, you should omit the ETag.
1023
	 *
1024
	 * @param mixed $calendarId
1025
	 * @param string $objectUri
1026
	 * @param string $calendarData
1027
	 * @return string
1028
	 */
1029
	function updateCalendarObject($calendarId, $objectUri, $calendarData) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1030
		$extraData = $this->getDenormalizedData($calendarData);
1031
1032
		$query = $this->db->getQueryBuilder();
1033
		$query->update('calendarobjects')
1034
				->set('calendardata', $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB))
1035
				->set('lastmodified', $query->createNamedParameter(time()))
1036
				->set('etag', $query->createNamedParameter($extraData['etag']))
1037
				->set('size', $query->createNamedParameter($extraData['size']))
1038
				->set('componenttype', $query->createNamedParameter($extraData['componentType']))
1039
				->set('firstoccurence', $query->createNamedParameter($extraData['firstOccurence']))
1040
				->set('lastoccurence', $query->createNamedParameter($extraData['lastOccurence']))
1041
				->set('classification', $query->createNamedParameter($extraData['classification']))
1042
				->set('uid', $query->createNamedParameter($extraData['uid']))
1043
			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1044
			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
1045
			->execute();
1046
1047
		$this->updateProperties($calendarId, $objectUri, $calendarData);
1048
1049
		$data = $this->getCalendarObject($calendarId, $objectUri);
1050 View Code Duplication
		if (is_array($data)) {
1051
			$this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject', new GenericEvent(
1052
				'\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject',
1053
				[
1054
					'calendarId' => $calendarId,
1055
					'calendarData' => $this->getCalendarById($calendarId),
1056
					'shares' => $this->getShares($calendarId),
1057
					'objectData' => $data,
1058
				]
1059
			));
1060
		}
1061
		$this->addChange($calendarId, $objectUri, 2);
1062
1063
		return '"' . $extraData['etag'] . '"';
1064
	}
1065
1066
	/**
1067
	 * @param int $calendarObjectId
1068
	 * @param int $classification
1069
	 */
1070
	public function setClassification($calendarObjectId, $classification) {
1071
		if (!in_array($classification, [
1072
			self::CLASSIFICATION_PUBLIC, self::CLASSIFICATION_PRIVATE, self::CLASSIFICATION_CONFIDENTIAL
1073
		])) {
1074
			throw new \InvalidArgumentException();
1075
		}
1076
		$query = $this->db->getQueryBuilder();
1077
		$query->update('calendarobjects')
1078
			->set('classification', $query->createNamedParameter($classification))
1079
			->where($query->expr()->eq('id', $query->createNamedParameter($calendarObjectId)))
1080
			->execute();
1081
	}
1082
1083
	/**
1084
	 * Deletes an existing calendar object.
1085
	 *
1086
	 * The object uri is only the basename, or filename and not a full path.
1087
	 *
1088
	 * @param mixed $calendarId
1089
	 * @param string $objectUri
1090
	 * @return void
1091
	 */
1092
	function deleteCalendarObject($calendarId, $objectUri) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1093
		$data = $this->getCalendarObject($calendarId, $objectUri);
1094 View Code Duplication
		if (is_array($data)) {
1095
			$this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject', new GenericEvent(
1096
				'\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject',
1097
				[
1098
					'calendarId' => $calendarId,
1099
					'calendarData' => $this->getCalendarById($calendarId),
1100
					'shares' => $this->getShares($calendarId),
1101
					'objectData' => $data,
1102
				]
1103
			));
1104
		}
1105
1106
		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `uri` = ?');
1107
		$stmt->execute([$calendarId, $objectUri]);
1108
1109
		$this->purgeProperties($calendarId, $data['id']);
1110
1111
		$this->addChange($calendarId, $objectUri, 3);
1112
	}
1113
1114
	/**
1115
	 * Performs a calendar-query on the contents of this calendar.
1116
	 *
1117
	 * The calendar-query is defined in RFC4791 : CalDAV. Using the
1118
	 * calendar-query it is possible for a client to request a specific set of
1119
	 * object, based on contents of iCalendar properties, date-ranges and
1120
	 * iCalendar component types (VTODO, VEVENT).
1121
	 *
1122
	 * This method should just return a list of (relative) urls that match this
1123
	 * query.
1124
	 *
1125
	 * The list of filters are specified as an array. The exact array is
1126
	 * documented by Sabre\CalDAV\CalendarQueryParser.
1127
	 *
1128
	 * Note that it is extremely likely that getCalendarObject for every path
1129
	 * returned from this method will be called almost immediately after. You
1130
	 * may want to anticipate this to speed up these requests.
1131
	 *
1132
	 * This method provides a default implementation, which parses *all* the
1133
	 * iCalendar objects in the specified calendar.
1134
	 *
1135
	 * This default may well be good enough for personal use, and calendars
1136
	 * that aren't very large. But if you anticipate high usage, big calendars
1137
	 * or high loads, you are strongly advised to optimize certain paths.
1138
	 *
1139
	 * The best way to do so is override this method and to optimize
1140
	 * specifically for 'common filters'.
1141
	 *
1142
	 * Requests that are extremely common are:
1143
	 *   * requests for just VEVENTS
1144
	 *   * requests for just VTODO
1145
	 *   * requests with a time-range-filter on either VEVENT or VTODO.
1146
	 *
1147
	 * ..and combinations of these requests. It may not be worth it to try to
1148
	 * handle every possible situation and just rely on the (relatively
1149
	 * easy to use) CalendarQueryValidator to handle the rest.
1150
	 *
1151
	 * Note that especially time-range-filters may be difficult to parse. A
1152
	 * time-range filter specified on a VEVENT must for instance also handle
1153
	 * recurrence rules correctly.
1154
	 * A good example of how to interprete all these filters can also simply
1155
	 * be found in Sabre\CalDAV\CalendarQueryFilter. This class is as correct
1156
	 * as possible, so it gives you a good idea on what type of stuff you need
1157
	 * to think of.
1158
	 *
1159
	 * @param mixed $calendarId
1160
	 * @param array $filters
1161
	 * @return array
1162
	 */
1163
	function calendarQuery($calendarId, array $filters) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1164
		$componentType = null;
1165
		$requirePostFilter = true;
1166
		$timeRange = null;
1167
1168
		// if no filters were specified, we don't need to filter after a query
1169
		if (!$filters['prop-filters'] && !$filters['comp-filters']) {
1170
			$requirePostFilter = false;
1171
		}
1172
1173
		// Figuring out if there's a component filter
1174
		if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) {
1175
			$componentType = $filters['comp-filters'][0]['name'];
1176
1177
			// Checking if we need post-filters
1178 View Code Duplication
			if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['time-range'] && !$filters['comp-filters'][0]['prop-filters']) {
1179
				$requirePostFilter = false;
1180
			}
1181
			// There was a time-range filter
1182
			if ($componentType === 'VEVENT' && isset($filters['comp-filters'][0]['time-range'])) {
1183
				$timeRange = $filters['comp-filters'][0]['time-range'];
1184
1185
				// If start time OR the end time is not specified, we can do a
1186
				// 100% accurate mysql query.
1187 View Code Duplication
				if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) {
1188
					$requirePostFilter = false;
1189
				}
1190
			}
1191
1192
		}
1193
		$columns = ['uri'];
1194
		if ($requirePostFilter) {
1195
			$columns = ['uri', 'calendardata'];
1196
		}
1197
		$query = $this->db->getQueryBuilder();
1198
		$query->select($columns)
1199
			->from('calendarobjects')
1200
			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)));
1201
1202
		if ($componentType) {
1203
			$query->andWhere($query->expr()->eq('componenttype', $query->createNamedParameter($componentType)));
1204
		}
1205
1206
		if ($timeRange && $timeRange['start']) {
1207
			$query->andWhere($query->expr()->gt('lastoccurence', $query->createNamedParameter($timeRange['start']->getTimeStamp())));
1208
		}
1209
		if ($timeRange && $timeRange['end']) {
1210
			$query->andWhere($query->expr()->lt('firstoccurence', $query->createNamedParameter($timeRange['end']->getTimeStamp())));
1211
		}
1212
1213
		$stmt = $query->execute();
1214
1215
		$result = [];
1216
		while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1217
			if ($requirePostFilter) {
1218
				if (!$this->validateFilterForObject($row, $filters)) {
1219
					continue;
1220
				}
1221
			}
1222
			$result[] = $row['uri'];
1223
		}
1224
1225
		return $result;
1226
	}
1227
1228
	/**
1229
	 * custom Nextcloud search extension for CalDAV
1230
	 *
1231
	 * @param string $principalUri
1232
	 * @param array $filters
1233
	 * @param integer|null $limit
1234
	 * @param integer|null $offset
1235
	 * @return array
1236
	 */
1237
	public function calendarSearch($principalUri, array $filters, $limit=null, $offset=null) {
1238
		$calendars = $this->getCalendarsForUser($principalUri);
1239
		$ownCalendars = [];
1240
		$sharedCalendars = [];
1241
1242
		$uriMapper = [];
1243
1244
		foreach($calendars as $calendar) {
1245
			if ($calendar['{http://owncloud.org/ns}owner-principal'] === $principalUri) {
1246
				$ownCalendars[] = $calendar['id'];
1247
			} else {
1248
				$sharedCalendars[] = $calendar['id'];
1249
			}
1250
			$uriMapper[$calendar['id']] = $calendar['uri'];
1251
		}
1252
		if (count($ownCalendars) === 0 && count($sharedCalendars) === 0) {
1253
			return [];
1254
		}
1255
1256
		$query = $this->db->getQueryBuilder();
1257
		// Calendar id expressions
1258
		$calendarExpressions = [];
1259
		foreach($ownCalendars as $id) {
1260
			$calendarExpressions[] = $query->expr()
1261
				->eq('c.calendarid', $query->createNamedParameter($id));
1262
		}
1263
		foreach($sharedCalendars as $id) {
1264
			$calendarExpressions[] = $query->expr()->andX(
1265
				$query->expr()->eq('c.calendarid',
1266
					$query->createNamedParameter($id)),
1267
				$query->expr()->eq('c.classification',
1268
					$query->createNamedParameter(self::CLASSIFICATION_PUBLIC))
1269
			);
1270
		}
1271
1272
		if (count($calendarExpressions) === 1) {
1273
			$calExpr = $calendarExpressions[0];
1274
		} else {
1275
			$calExpr = call_user_func_array([$query->expr(), 'orX'], $calendarExpressions);
1276
		}
1277
1278
		// Component expressions
1279
		$compExpressions = [];
1280
		foreach($filters['comps'] as $comp) {
1281
			$compExpressions[] = $query->expr()
1282
				->eq('c.componenttype', $query->createNamedParameter($comp));
1283
		}
1284
1285
		if (count($compExpressions) === 1) {
1286
			$compExpr = $compExpressions[0];
1287
		} else {
1288
			$compExpr = call_user_func_array([$query->expr(), 'orX'], $compExpressions);
1289
		}
1290
1291
		if (!isset($filters['props'])) {
1292
			$filters['props'] = [];
1293
		}
1294
		if (!isset($filters['params'])) {
1295
			$filters['params'] = [];
1296
		}
1297
1298
		$propParamExpressions = [];
1299
		foreach($filters['props'] as $prop) {
1300
			$propParamExpressions[] = $query->expr()->andX(
1301
				$query->expr()->eq('i.name', $query->createNamedParameter($prop)),
1302
				$query->expr()->isNull('i.parameter')
1303
			);
1304
		}
1305
		foreach($filters['params'] as $param) {
1306
			$propParamExpressions[] = $query->expr()->andX(
1307
				$query->expr()->eq('i.name', $query->createNamedParameter($param['property'])),
1308
				$query->expr()->eq('i.parameter', $query->createNamedParameter($param['parameter']))
1309
			);
1310
		}
1311
1312
		if (count($propParamExpressions) === 1) {
1313
			$propParamExpr = $propParamExpressions[0];
1314
		} else {
1315
			$propParamExpr = call_user_func_array([$query->expr(), 'orX'], $propParamExpressions);
1316
		}
1317
1318
		$query->select(['c.calendarid', 'c.uri'])
1319
			->from($this->dbObjectPropertiesTable, 'i')
1320
			->join('i', 'calendarobjects', 'c', $query->expr()->eq('i.objectid', 'c.id'))
1321
			->where($calExpr)
1322
			->andWhere($compExpr)
1323
			->andWhere($propParamExpr)
1324
			->andWhere($query->expr()->iLike('i.value',
1325
				$query->createNamedParameter('%'.$this->db->escapeLikeParameter($filters['search-term']).'%')));
1326
1327
		if ($offset) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $offset of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1328
			$query->setFirstResult($offset);
1329
		}
1330
		if ($limit) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $limit of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1331
			$query->setMaxResults($limit);
1332
		}
1333
1334
		$stmt = $query->execute();
1335
1336
		$result = [];
1337
		while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1338
			$path = $uriMapper[$row['calendarid']] . '/' . $row['uri'];
1339
			if (!in_array($path, $result)) {
1340
				$result[] = $path;
1341
			}
1342
		}
1343
1344
		return $result;
1345
	}
1346
1347
	/**
1348
	 * Searches through all of a users calendars and calendar objects to find
1349
	 * an object with a specific UID.
1350
	 *
1351
	 * This method should return the path to this object, relative to the
1352
	 * calendar home, so this path usually only contains two parts:
1353
	 *
1354
	 * calendarpath/objectpath.ics
1355
	 *
1356
	 * If the uid is not found, return null.
1357
	 *
1358
	 * This method should only consider * objects that the principal owns, so
1359
	 * any calendars owned by other principals that also appear in this
1360
	 * collection should be ignored.
1361
	 *
1362
	 * @param string $principalUri
1363
	 * @param string $uid
1364
	 * @return string|null
1365
	 */
1366
	function getCalendarObjectByUID($principalUri, $uid) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1367
1368
		$query = $this->db->getQueryBuilder();
1369
		$query->selectAlias('c.uri', 'calendaruri')->selectAlias('co.uri', 'objecturi')
1370
			->from('calendarobjects', 'co')
1371
			->leftJoin('co', 'calendars', 'c', $query->expr()->eq('co.calendarid', 'c.id'))
1372
			->where($query->expr()->eq('c.principaluri', $query->createNamedParameter($principalUri)))
1373
			->andWhere($query->expr()->eq('co.uid', $query->createNamedParameter($uid)));
1374
1375
		$stmt = $query->execute();
1376
1377
		if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1378
			return $row['calendaruri'] . '/' . $row['objecturi'];
1379
		}
1380
1381
		return null;
1382
	}
1383
1384
	/**
1385
	 * The getChanges method returns all the changes that have happened, since
1386
	 * the specified syncToken in the specified calendar.
1387
	 *
1388
	 * This function should return an array, such as the following:
1389
	 *
1390
	 * [
1391
	 *   'syncToken' => 'The current synctoken',
1392
	 *   'added'   => [
1393
	 *      'new.txt',
1394
	 *   ],
1395
	 *   'modified'   => [
1396
	 *      'modified.txt',
1397
	 *   ],
1398
	 *   'deleted' => [
1399
	 *      'foo.php.bak',
1400
	 *      'old.txt'
1401
	 *   ]
1402
	 * );
1403
	 *
1404
	 * The returned syncToken property should reflect the *current* syncToken
1405
	 * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
1406
	 * property This is * needed here too, to ensure the operation is atomic.
1407
	 *
1408
	 * If the $syncToken argument is specified as null, this is an initial
1409
	 * sync, and all members should be reported.
1410
	 *
1411
	 * The modified property is an array of nodenames that have changed since
1412
	 * the last token.
1413
	 *
1414
	 * The deleted property is an array with nodenames, that have been deleted
1415
	 * from collection.
1416
	 *
1417
	 * The $syncLevel argument is basically the 'depth' of the report. If it's
1418
	 * 1, you only have to report changes that happened only directly in
1419
	 * immediate descendants. If it's 2, it should also include changes from
1420
	 * the nodes below the child collections. (grandchildren)
1421
	 *
1422
	 * The $limit argument allows a client to specify how many results should
1423
	 * be returned at most. If the limit is not specified, it should be treated
1424
	 * as infinite.
1425
	 *
1426
	 * If the limit (infinite or not) is higher than you're willing to return,
1427
	 * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
1428
	 *
1429
	 * If the syncToken is expired (due to data cleanup) or unknown, you must
1430
	 * return null.
1431
	 *
1432
	 * The limit is 'suggestive'. You are free to ignore it.
1433
	 *
1434
	 * @param string $calendarId
1435
	 * @param string $syncToken
1436
	 * @param int $syncLevel
1437
	 * @param int $limit
1438
	 * @return array
1439
	 */
1440 View Code Duplication
	function getChangesForCalendar($calendarId, $syncToken, $syncLevel, $limit = null) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1441
		// Current synctoken
1442
		$stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*calendars` WHERE `id` = ?');
1443
		$stmt->execute([ $calendarId ]);
1444
		$currentToken = $stmt->fetchColumn(0);
1445
1446
		if (is_null($currentToken)) {
1447
			return null;
1448
		}
1449
1450
		$result = [
1451
			'syncToken' => $currentToken,
1452
			'added'     => [],
1453
			'modified'  => [],
1454
			'deleted'   => [],
1455
		];
1456
1457
		if ($syncToken) {
1458
1459
			$query = "SELECT `uri`, `operation` FROM `*PREFIX*calendarchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `calendarid` = ? ORDER BY `synctoken`";
1460
			if ($limit>0) {
1461
				$query.= " LIMIT " . (int)$limit;
1462
			}
1463
1464
			// Fetching all changes
1465
			$stmt = $this->db->prepare($query);
1466
			$stmt->execute([$syncToken, $currentToken, $calendarId]);
1467
1468
			$changes = [];
1469
1470
			// This loop ensures that any duplicates are overwritten, only the
1471
			// last change on a node is relevant.
1472
			while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1473
1474
				$changes[$row['uri']] = $row['operation'];
1475
1476
			}
1477
1478
			foreach($changes as $uri => $operation) {
1479
1480
				switch($operation) {
1481
					case 1 :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1482
						$result['added'][] = $uri;
1483
						break;
1484
					case 2 :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1485
						$result['modified'][] = $uri;
1486
						break;
1487
					case 3 :
0 ignored issues
show
Coding Style introduced by
There must be no space before the colon in a CASE statement

As per the PSR-2 coding standard, there must not be a space in front of the colon in case statements.

switch ($selector) {
    case "A": //right
        doSomething();
        break;
    case "B" : //wrong
        doSomethingElse();
        break;
}

To learn more about the PSR-2 coding standard, please refer to the PHP-Fig.

Loading history...
1488
						$result['deleted'][] = $uri;
1489
						break;
1490
				}
1491
1492
			}
1493
		} else {
1494
			// No synctoken supplied, this is the initial sync.
1495
			$query = "SELECT `uri` FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ?";
1496
			$stmt = $this->db->prepare($query);
1497
			$stmt->execute([$calendarId]);
1498
1499
			$result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
1500
		}
1501
		return $result;
1502
1503
	}
1504
1505
	/**
1506
	 * Returns a list of subscriptions for a principal.
1507
	 *
1508
	 * Every subscription is an array with the following keys:
1509
	 *  * id, a unique id that will be used by other functions to modify the
1510
	 *    subscription. This can be the same as the uri or a database key.
1511
	 *  * uri. This is just the 'base uri' or 'filename' of the subscription.
1512
	 *  * principaluri. The owner of the subscription. Almost always the same as
1513
	 *    principalUri passed to this method.
1514
	 *
1515
	 * Furthermore, all the subscription info must be returned too:
1516
	 *
1517
	 * 1. {DAV:}displayname
1518
	 * 2. {http://apple.com/ns/ical/}refreshrate
1519
	 * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
1520
	 *    should not be stripped).
1521
	 * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
1522
	 *    should not be stripped).
1523
	 * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
1524
	 *    attachments should not be stripped).
1525
	 * 6. {http://calendarserver.org/ns/}source (Must be a
1526
	 *     Sabre\DAV\Property\Href).
1527
	 * 7. {http://apple.com/ns/ical/}calendar-color
1528
	 * 8. {http://apple.com/ns/ical/}calendar-order
1529
	 * 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
1530
	 *    (should just be an instance of
1531
	 *    Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of
1532
	 *    default components).
1533
	 *
1534
	 * @param string $principalUri
1535
	 * @return array
1536
	 */
1537
	function getSubscriptionsForUser($principalUri) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1538
		$fields = array_values($this->subscriptionPropertyMap);
1539
		$fields[] = 'id';
1540
		$fields[] = 'uri';
1541
		$fields[] = 'source';
1542
		$fields[] = 'principaluri';
1543
		$fields[] = 'lastmodified';
1544
1545
		$query = $this->db->getQueryBuilder();
1546
		$query->select($fields)
1547
			->from('calendarsubscriptions')
1548
			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
1549
			->orderBy('calendarorder', 'asc');
1550
		$stmt =$query->execute();
1551
1552
		$subscriptions = [];
1553
		while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1554
1555
			$subscription = [
1556
				'id'           => $row['id'],
1557
				'uri'          => $row['uri'],
1558
				'principaluri' => $row['principaluri'],
1559
				'source'       => $row['source'],
1560
				'lastmodified' => $row['lastmodified'],
1561
1562
				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
1563
			];
1564
1565
			foreach($this->subscriptionPropertyMap as $xmlName=>$dbName) {
1566
				if (!is_null($row[$dbName])) {
1567
					$subscription[$xmlName] = $row[$dbName];
1568
				}
1569
			}
1570
1571
			$subscriptions[] = $subscription;
1572
1573
		}
1574
1575
		return $subscriptions;
1576
	}
1577
1578
	/**
1579
	 * Creates a new subscription for a principal.
1580
	 *
1581
	 * If the creation was a success, an id must be returned that can be used to reference
1582
	 * this subscription in other methods, such as updateSubscription.
1583
	 *
1584
	 * @param string $principalUri
1585
	 * @param string $uri
1586
	 * @param array $properties
1587
	 * @return mixed
1588
	 */
1589
	function createSubscription($principalUri, $uri, array $properties) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1590
1591
		if (!isset($properties['{http://calendarserver.org/ns/}source'])) {
1592
			throw new Forbidden('The {http://calendarserver.org/ns/}source property is required when creating subscriptions');
1593
		}
1594
1595
		$values = [
1596
			'principaluri' => $principalUri,
1597
			'uri'          => $uri,
1598
			'source'       => $properties['{http://calendarserver.org/ns/}source']->getHref(),
1599
			'lastmodified' => time(),
1600
		];
1601
1602
		$propertiesBoolean = ['striptodos', 'stripalarms', 'stripattachments'];
1603
1604
		foreach($this->subscriptionPropertyMap as $xmlName=>$dbName) {
1605
			if (array_key_exists($xmlName, $properties)) {
1606
					$values[$dbName] = $properties[$xmlName];
1607
					if (in_array($dbName, $propertiesBoolean)) {
1608
						$values[$dbName] = true;
1609
				}
1610
			}
1611
		}
1612
1613
		$valuesToInsert = array();
1614
1615
		$query = $this->db->getQueryBuilder();
1616
1617
		foreach (array_keys($values) as $name) {
1618
			$valuesToInsert[$name] = $query->createNamedParameter($values[$name]);
1619
		}
1620
1621
		$query->insert('calendarsubscriptions')
1622
			->values($valuesToInsert)
1623
			->execute();
1624
1625
		return $this->db->lastInsertId('*PREFIX*calendarsubscriptions');
1626
	}
1627
1628
	/**
1629
	 * Updates a subscription
1630
	 *
1631
	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
1632
	 * To do the actual updates, you must tell this object which properties
1633
	 * you're going to process with the handle() method.
1634
	 *
1635
	 * Calling the handle method is like telling the PropPatch object "I
1636
	 * promise I can handle updating this property".
1637
	 *
1638
	 * Read the PropPatch documentation for more info and examples.
1639
	 *
1640
	 * @param mixed $subscriptionId
1641
	 * @param PropPatch $propPatch
1642
	 * @return void
1643
	 */
1644
	function updateSubscription($subscriptionId, PropPatch $propPatch) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1645
		$supportedProperties = array_keys($this->subscriptionPropertyMap);
1646
		$supportedProperties[] = '{http://calendarserver.org/ns/}source';
1647
1648
		/**
1649
		 * @suppress SqlInjectionChecker
1650
		 */
1651
		$propPatch->handle($supportedProperties, function($mutations) use ($subscriptionId) {
1652
1653
			$newValues = [];
1654
1655
			foreach($mutations as $propertyName=>$propertyValue) {
1656
				if ($propertyName === '{http://calendarserver.org/ns/}source') {
1657
					$newValues['source'] = $propertyValue->getHref();
1658
				} else {
1659
					$fieldName = $this->subscriptionPropertyMap[$propertyName];
1660
					$newValues[$fieldName] = $propertyValue;
1661
				}
1662
			}
1663
1664
			$query = $this->db->getQueryBuilder();
1665
			$query->update('calendarsubscriptions')
1666
				->set('lastmodified', $query->createNamedParameter(time()));
1667
			foreach($newValues as $fieldName=>$value) {
1668
				$query->set($fieldName, $query->createNamedParameter($value));
1669
			}
1670
			$query->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
1671
				->execute();
1672
1673
			return true;
1674
1675
		});
1676
	}
1677
1678
	/**
1679
	 * Deletes a subscription.
1680
	 *
1681
	 * @param mixed $subscriptionId
1682
	 * @return void
1683
	 */
1684
	function deleteSubscription($subscriptionId) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1685
		$query = $this->db->getQueryBuilder();
1686
		$query->delete('calendarsubscriptions')
1687
			->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
1688
			->execute();
1689
	}
1690
1691
	/**
1692
	 * Returns a single scheduling object for the inbox collection.
1693
	 *
1694
	 * The returned array should contain the following elements:
1695
	 *   * uri - A unique basename for the object. This will be used to
1696
	 *           construct a full uri.
1697
	 *   * calendardata - The iCalendar object
1698
	 *   * lastmodified - The last modification date. Can be an int for a unix
1699
	 *                    timestamp, or a PHP DateTime object.
1700
	 *   * etag - A unique token that must change if the object changed.
1701
	 *   * size - The size of the object, in bytes.
1702
	 *
1703
	 * @param string $principalUri
1704
	 * @param string $objectUri
1705
	 * @return array
1706
	 */
1707
	function getSchedulingObject($principalUri, $objectUri) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1708
		$query = $this->db->getQueryBuilder();
1709
		$stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
1710
			->from('schedulingobjects')
1711
			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
1712
			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
1713
			->execute();
1714
1715
		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
1716
1717
		if(!$row) {
1718
			return null;
1719
		}
1720
1721
		return [
1722
				'uri'          => $row['uri'],
1723
				'calendardata' => $row['calendardata'],
1724
				'lastmodified' => $row['lastmodified'],
1725
				'etag'         => '"' . $row['etag'] . '"',
1726
				'size'         => (int)$row['size'],
1727
		];
1728
	}
1729
1730
	/**
1731
	 * Returns all scheduling objects for the inbox collection.
1732
	 *
1733
	 * These objects should be returned as an array. Every item in the array
1734
	 * should follow the same structure as returned from getSchedulingObject.
1735
	 *
1736
	 * The main difference is that 'calendardata' is optional.
1737
	 *
1738
	 * @param string $principalUri
1739
	 * @return array
1740
	 */
1741
	function getSchedulingObjects($principalUri) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1742
		$query = $this->db->getQueryBuilder();
1743
		$stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
1744
				->from('schedulingobjects')
1745
				->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
1746
				->execute();
1747
1748
		$result = [];
1749
		foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
1750
			$result[] = [
1751
					'calendardata' => $row['calendardata'],
1752
					'uri'          => $row['uri'],
1753
					'lastmodified' => $row['lastmodified'],
1754
					'etag'         => '"' . $row['etag'] . '"',
1755
					'size'         => (int)$row['size'],
1756
			];
1757
		}
1758
1759
		return $result;
1760
	}
1761
1762
	/**
1763
	 * Deletes a scheduling object from the inbox collection.
1764
	 *
1765
	 * @param string $principalUri
1766
	 * @param string $objectUri
1767
	 * @return void
1768
	 */
1769
	function deleteSchedulingObject($principalUri, $objectUri) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1770
		$query = $this->db->getQueryBuilder();
1771
		$query->delete('schedulingobjects')
1772
				->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
1773
				->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
1774
				->execute();
1775
	}
1776
1777
	/**
1778
	 * Creates a new scheduling object. This should land in a users' inbox.
1779
	 *
1780
	 * @param string $principalUri
1781
	 * @param string $objectUri
1782
	 * @param string $objectData
1783
	 * @return void
1784
	 */
1785
	function createSchedulingObject($principalUri, $objectUri, $objectData) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1786
		$query = $this->db->getQueryBuilder();
1787
		$query->insert('schedulingobjects')
1788
			->values([
1789
				'principaluri' => $query->createNamedParameter($principalUri),
1790
				'calendardata' => $query->createNamedParameter($objectData),
1791
				'uri' => $query->createNamedParameter($objectUri),
1792
				'lastmodified' => $query->createNamedParameter(time()),
1793
				'etag' => $query->createNamedParameter(md5($objectData)),
1794
				'size' => $query->createNamedParameter(strlen($objectData))
1795
			])
1796
			->execute();
1797
	}
1798
1799
	/**
1800
	 * Adds a change record to the calendarchanges table.
1801
	 *
1802
	 * @param mixed $calendarId
1803
	 * @param string $objectUri
1804
	 * @param int $operation 1 = add, 2 = modify, 3 = delete.
1805
	 * @return void
1806
	 */
1807 View Code Duplication
	protected function addChange($calendarId, $objectUri, $operation) {
1808
1809
		$stmt = $this->db->prepare('INSERT INTO `*PREFIX*calendarchanges` (`uri`, `synctoken`, `calendarid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*calendars` WHERE `id` = ?');
1810
		$stmt->execute([
1811
			$objectUri,
1812
			$calendarId,
1813
			$operation,
1814
			$calendarId
1815
		]);
1816
		$stmt = $this->db->prepare('UPDATE `*PREFIX*calendars` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?');
1817
		$stmt->execute([
1818
			$calendarId
1819
		]);
1820
1821
	}
1822
1823
	/**
1824
	 * Parses some information from calendar objects, used for optimized
1825
	 * calendar-queries.
1826
	 *
1827
	 * Returns an array with the following keys:
1828
	 *   * etag - An md5 checksum of the object without the quotes.
1829
	 *   * size - Size of the object in bytes
1830
	 *   * componentType - VEVENT, VTODO or VJOURNAL
1831
	 *   * firstOccurence
1832
	 *   * lastOccurence
1833
	 *   * uid - value of the UID property
1834
	 *
1835
	 * @param string $calendarData
1836
	 * @return array
1837
	 */
1838
	public function getDenormalizedData($calendarData) {
1839
1840
		$vObject = Reader::read($calendarData);
1841
		$componentType = null;
1842
		$component = null;
1843
		$firstOccurrence = null;
1844
		$lastOccurrence = null;
1845
		$uid = null;
1846
		$classification = self::CLASSIFICATION_PUBLIC;
1847
		foreach($vObject->getComponents() as $component) {
1848
			if ($component->name!=='VTIMEZONE') {
1849
				$componentType = $component->name;
1850
				$uid = (string)$component->UID;
1851
				break;
1852
			}
1853
		}
1854
		if (!$componentType) {
1855
			throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
1856
		}
1857
		if ($componentType === 'VEVENT' && $component->DTSTART) {
1858
			$firstOccurrence = $component->DTSTART->getDateTime()->getTimeStamp();
1859
			// Finding the last occurrence is a bit harder
1860 View Code Duplication
			if (!isset($component->RRULE)) {
1861
				if (isset($component->DTEND)) {
1862
					$lastOccurrence = $component->DTEND->getDateTime()->getTimeStamp();
1863
				} elseif (isset($component->DURATION)) {
1864
					$endDate = clone $component->DTSTART->getDateTime();
1865
					$endDate->add(DateTimeParser::parse($component->DURATION->getValue()));
1866
					$lastOccurrence = $endDate->getTimeStamp();
1867
				} elseif (!$component->DTSTART->hasTime()) {
1868
					$endDate = clone $component->DTSTART->getDateTime();
1869
					$endDate->modify('+1 day');
1870
					$lastOccurrence = $endDate->getTimeStamp();
1871
				} else {
1872
					$lastOccurrence = $firstOccurrence;
1873
				}
1874
			} else {
1875
				$it = new EventIterator($vObject, (string)$component->UID);
1876
				$maxDate = new \DateTime(self::MAX_DATE);
1877
				if ($it->isInfinite()) {
1878
					$lastOccurrence = $maxDate->getTimestamp();
1879
				} else {
1880
					$end = $it->getDtEnd();
1881
					while($it->valid() && $end < $maxDate) {
1882
						$end = $it->getDtEnd();
1883
						$it->next();
1884
1885
					}
1886
					$lastOccurrence = $end->getTimestamp();
1887
				}
1888
1889
			}
1890
		}
1891
1892
		if ($component->CLASS) {
1893
			$classification = CalDavBackend::CLASSIFICATION_PRIVATE;
1894
			switch ($component->CLASS->getValue()) {
1895
				case 'PUBLIC':
1896
					$classification = CalDavBackend::CLASSIFICATION_PUBLIC;
1897
					break;
1898
				case 'CONFIDENTIAL':
1899
					$classification = CalDavBackend::CLASSIFICATION_CONFIDENTIAL;
1900
					break;
1901
			}
1902
		}
1903
		return [
1904
			'etag' => md5($calendarData),
1905
			'size' => strlen($calendarData),
1906
			'componentType' => $componentType,
1907
			'firstOccurence' => is_null($firstOccurrence) ? null : max(0, $firstOccurrence),
1908
			'lastOccurence'  => $lastOccurrence,
1909
			'uid' => $uid,
1910
			'classification' => $classification
1911
		];
1912
1913
	}
1914
1915
	private function readBlob($cardData) {
1916
		if (is_resource($cardData)) {
1917
			return stream_get_contents($cardData);
1918
		}
1919
1920
		return $cardData;
1921
	}
1922
1923
	/**
1924
	 * @param IShareable $shareable
1925
	 * @param array $add
1926
	 * @param array $remove
1927
	 */
1928
	public function updateShares($shareable, $add, $remove) {
1929
		$calendarId = $shareable->getResourceId();
1930
		$this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateShares', new GenericEvent(
1931
			'\OCA\DAV\CalDAV\CalDavBackend::updateShares',
1932
			[
1933
				'calendarId' => $calendarId,
1934
				'calendarData' => $this->getCalendarById($calendarId),
1935
				'shares' => $this->getShares($calendarId),
1936
				'add' => $add,
1937
				'remove' => $remove,
1938
			]));
1939
		$this->sharingBackend->updateShares($shareable, $add, $remove);
1940
	}
1941
1942
	/**
1943
	 * @param int $resourceId
1944
	 * @return array
1945
	 */
1946
	public function getShares($resourceId) {
1947
		return $this->sharingBackend->getShares($resourceId);
1948
	}
1949
1950
	/**
1951
	 * @param boolean $value
1952
	 * @param \OCA\DAV\CalDAV\Calendar $calendar
1953
	 * @return string|null
1954
	 */
1955
	public function setPublishStatus($value, $calendar) {
1956
		$query = $this->db->getQueryBuilder();
1957
		if ($value) {
1958
			$publicUri = $this->random->generate(16, ISecureRandom::CHAR_HUMAN_READABLE);
1959
			$query->insert('dav_shares')
1960
				->values([
1961
					'principaluri' => $query->createNamedParameter($calendar->getPrincipalURI()),
1962
					'type' => $query->createNamedParameter('calendar'),
1963
					'access' => $query->createNamedParameter(self::ACCESS_PUBLIC),
1964
					'resourceid' => $query->createNamedParameter($calendar->getResourceId()),
1965
					'publicuri' => $query->createNamedParameter($publicUri)
1966
				]);
1967
			$query->execute();
1968
			return $publicUri;
1969
		}
1970
		$query->delete('dav_shares')
1971
			->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
1972
			->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)));
1973
		$query->execute();
1974
		return null;
1975
	}
1976
1977
	/**
1978
	 * @param \OCA\DAV\CalDAV\Calendar $calendar
1979
	 * @return mixed
1980
	 */
1981 View Code Duplication
	public function getPublishStatus($calendar) {
1982
		$query = $this->db->getQueryBuilder();
1983
		$result = $query->select('publicuri')
1984
			->from('dav_shares')
1985
			->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
1986
			->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
1987
			->execute();
1988
1989
		$row = $result->fetch();
1990
		$result->closeCursor();
1991
		return $row ? reset($row) : false;
1992
	}
1993
1994
	/**
1995
	 * @param int $resourceId
1996
	 * @param array $acl
1997
	 * @return array
1998
	 */
1999
	public function applyShareAcl($resourceId, $acl) {
2000
		return $this->sharingBackend->applyShareAcl($resourceId, $acl);
2001
	}
2002
2003
2004
2005
	/**
2006
	 * update properties table
2007
	 *
2008
	 * @param int $calendarId
2009
	 * @param string $objectUri
2010
	 * @param string $calendarData
2011
	 */
2012
	public function updateProperties($calendarId, $objectUri, $calendarData) {
2013
		$objectId = $this->getCalendarObjectId($calendarId, $objectUri);
2014
2015
		try {
2016
			$vCalendar = $this->readCalendarData($calendarData);
2017
		} catch (\Exception $ex) {
2018
			return;
2019
		}
2020
2021
		$this->purgeProperties($calendarId, $objectId);
2022
2023
		$query = $this->db->getQueryBuilder();
2024
		$query->insert($this->dbObjectPropertiesTable)
2025
			->values(
2026
				[
2027
					'calendarid' => $query->createNamedParameter($calendarId),
2028
					'objectid' => $query->createNamedParameter($objectId),
2029
					'name' => $query->createParameter('name'),
2030
					'parameter' => $query->createParameter('parameter'),
2031
					'value' => $query->createParameter('value'),
2032
				]
2033
			);
2034
2035
		$indexComponents = ['VEVENT', 'VJOURNAL', 'VTODO'];
2036
		foreach ($vCalendar->getComponents() as $component) {
2037
			if (!in_array($component->name, $indexComponents)) {
2038
				continue;
2039
			}
2040
2041
			foreach ($component->children() as $property) {
2042
				if (in_array($property->name, self::$indexProperties)) {
2043
					$value = $property->getValue();
2044
					// is this a shitty db?
2045
					if (!$this->db->supports4ByteText()) {
2046
						$value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value);
2047
					}
2048
					$value = substr($value, 0, 254);
2049
2050
					$query->setParameter('name', $property->name);
2051
					$query->setParameter('parameter', null);
2052
					$query->setParameter('value', $value);
2053
					$query->execute();
2054
				}
2055
2056
				if (in_array($property->name, array_keys(self::$indexParameters))) {
2057
					$parameters = $property->parameters();
2058
					$indexedParametersForProperty = self::$indexParameters[$property->name];
2059
2060
					foreach ($parameters as $key => $value) {
2061
						if (in_array($key, $indexedParametersForProperty)) {
2062
							// is this a shitty db?
2063
							if ($this->db->supports4ByteText()) {
2064
								$value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value);
2065
							}
2066
							$value = substr($value, 0, 254);
2067
2068
							$query->setParameter('name', $property->name);
2069
							$query->setParameter('parameter', substr($key, 0, 254));
2070
							$query->setParameter('value', substr($value, 0, 254));
2071
							$query->execute();
2072
						}
2073
					}
2074
				}
2075
			}
2076
		}
2077
	}
2078
2079
	/**
2080
	 * read VCalendar data into a VCalendar object
2081
	 *
2082
	 * @param string $objectData
2083
	 * @return VCalendar
2084
	 */
2085
	protected function readCalendarData($objectData) {
2086
		return Reader::read($objectData);
2087
	}
2088
2089
	/**
2090
	 * delete all properties from a given calendar object
2091
	 *
2092
	 * @param int $calendarId
2093
	 * @param int $objectId
2094
	 */
2095 View Code Duplication
	protected function purgeProperties($calendarId, $objectId) {
2096
		$query = $this->db->getQueryBuilder();
2097
		$query->delete($this->dbObjectPropertiesTable)
2098
			->where($query->expr()->eq('objectid', $query->createNamedParameter($objectId)))
2099
			->andWhere($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)));
2100
		$query->execute();
2101
	}
2102
2103
	/**
2104
	 * get ID from a given calendar object
2105
	 *
2106
	 * @param int $calendarId
2107
	 * @param string $uri
2108
	 * @return int
2109
	 */
2110 View Code Duplication
	protected function getCalendarObjectId($calendarId, $uri) {
2111
		$query = $this->db->getQueryBuilder();
2112
		$query->select('id')->from('calendarobjects')
2113
			->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
2114
			->andWhere($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)));
2115
2116
		$result = $query->execute();
2117
		$objectIds = $result->fetch();
2118
		$result->closeCursor();
2119
2120
		if (!isset($objectIds['id'])) {
2121
			throw new \InvalidArgumentException('Calendarobject does not exists: ' . $uri);
2122
		}
2123
2124
		return (int)$objectIds['id'];
2125
	}
2126
2127 View Code Duplication
	private function convertPrincipal($principalUri, $toV2) {
2128
		if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
2129
			list(, $name) = Uri\split($principalUri);
2130
			if ($toV2 === true) {
2131
				return "principals/users/$name";
2132
			}
2133
			return "principals/$name";
2134
		}
2135
		return $principalUri;
2136
	}
2137
2138 View Code Duplication
	private function addOwnerPrincipal(&$calendarInfo) {
2139
		$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
2140
		$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
2141
		if (isset($calendarInfo[$ownerPrincipalKey])) {
2142
			$uri = $calendarInfo[$ownerPrincipalKey];
2143
		} else {
2144
			$uri = $calendarInfo['principaluri'];
2145
		}
2146
2147
		$principalInformation = $this->principalBackend->getPrincipalByPath($uri);
2148
		if (isset($principalInformation['{DAV:}displayname'])) {
2149
			$calendarInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
2150
		}
2151
	}
2152
}
2153