Completed
Push — master ( 424d62...2a7733 )
by Morris
11:53
created

CalDavBackend::deleteCalendar()   B

Complexity

Conditions 1
Paths 1

Size

Total Lines 25
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 17
nc 1
nop 1
dl 0
loc 25
rs 8.8571
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 Joas Schilling <[email protected]>
7
 * @author Stefan Weil <[email protected]>
8
 * @author Thomas Citharel <[email protected]>
9
 * @author Thomas Müller <[email protected]>
10
 * @author Georg Ehrke <[email protected]>
11
 *
12
 * @license AGPL-3.0
13
 *
14
 * This code is free software: you can redistribute it and/or modify
15
 * it under the terms of the GNU Affero General Public License, version 3,
16
 * as published by the Free Software Foundation.
17
 *
18
 * This program is distributed in the hope that it will be useful,
19
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
 * GNU Affero General Public License for more details.
22
 *
23
 * You should have received a copy of the GNU Affero General Public License, version 3,
24
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
25
 *
26
 */
27
28
namespace OCA\DAV\CalDAV;
29
30
use OCA\DAV\DAV\Sharing\IShareable;
31
use OCP\DB\QueryBuilder\IQueryBuilder;
32
use OCA\DAV\Connector\Sabre\Principal;
33
use OCA\DAV\DAV\Sharing\Backend;
34
use OCP\IDBConnection;
35
use OCP\IUser;
36
use OCP\IUserManager;
37
use OCP\Security\ISecureRandom;
38
use Sabre\CalDAV\Backend\AbstractBackend;
39
use Sabre\CalDAV\Backend\SchedulingSupport;
40
use Sabre\CalDAV\Backend\SubscriptionSupport;
41
use Sabre\CalDAV\Backend\SyncSupport;
42
use Sabre\CalDAV\Xml\Property\ScheduleCalendarTransp;
43
use Sabre\CalDAV\Xml\Property\SupportedCalendarComponentSet;
44
use Sabre\DAV;
45
use Sabre\DAV\Exception\Forbidden;
46
use Sabre\DAV\Exception\NotFound;
47
use Sabre\DAV\PropPatch;
48
use Sabre\HTTP\URLUtil;
49
use Sabre\VObject\Component\VCalendar;
50
use Sabre\VObject\DateTimeParser;
51
use Sabre\VObject\Reader;
52
use Sabre\VObject\Recur\EventIterator;
53
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
54
use Symfony\Component\EventDispatcher\GenericEvent;
55
56
/**
57
 * Class CalDavBackend
58
 *
59
 * Code is heavily inspired by https://github.com/fruux/sabre-dav/blob/master/lib/CalDAV/Backend/PDO.php
60
 *
61
 * @package OCA\DAV\CalDAV
62
 */
63
class CalDavBackend extends AbstractBackend implements SyncSupport, SubscriptionSupport, SchedulingSupport {
64
65
	const PERSONAL_CALENDAR_URI = 'personal';
66
	const PERSONAL_CALENDAR_NAME = 'Personal';
67
68
	/**
69
	 * We need to specify a max date, because we need to stop *somewhere*
70
	 *
71
	 * On 32 bit system the maximum for a signed integer is 2147483647, so
72
	 * MAX_DATE cannot be higher than date('Y-m-d', 2147483647) which results
73
	 * in 2038-01-19 to avoid problems when the date is converted
74
	 * to a unix timestamp.
75
	 */
76
	const MAX_DATE = '2038-01-01';
77
78
	const ACCESS_PUBLIC = 4;
79
	const CLASSIFICATION_PUBLIC = 0;
80
	const CLASSIFICATION_PRIVATE = 1;
81
	const CLASSIFICATION_CONFIDENTIAL = 2;
82
83
	/**
84
	 * List of CalDAV properties, and how they map to database field names
85
	 * Add your own properties by simply adding on to this array.
86
	 *
87
	 * Note that only string-based properties are supported here.
88
	 *
89
	 * @var array
90
	 */
91
	public $propertyMap = [
92
		'{DAV:}displayname'                          => 'displayname',
93
		'{urn:ietf:params:xml:ns:caldav}calendar-description' => 'description',
94
		'{urn:ietf:params:xml:ns:caldav}calendar-timezone'    => 'timezone',
95
		'{http://apple.com/ns/ical/}calendar-order'  => 'calendarorder',
96
		'{http://apple.com/ns/ical/}calendar-color'  => 'calendarcolor',
97
	];
98
99
	/**
100
	 * List of subscription properties, and how they map to database field names.
101
	 *
102
	 * @var array
103
	 */
104
	public $subscriptionPropertyMap = [
105
		'{DAV:}displayname'                                           => 'displayname',
106
		'{http://apple.com/ns/ical/}refreshrate'                      => 'refreshrate',
107
		'{http://apple.com/ns/ical/}calendar-order'                   => 'calendarorder',
108
		'{http://apple.com/ns/ical/}calendar-color'                   => 'calendarcolor',
109
		'{http://calendarserver.org/ns/}subscribed-strip-todos'       => 'striptodos',
110
		'{http://calendarserver.org/ns/}subscribed-strip-alarms'      => 'stripalarms',
111
		'{http://calendarserver.org/ns/}subscribed-strip-attachments' => 'stripattachments',
112
	];
113
114
	/** @var array properties to index */
115
	public static $indexProperties = ['CATEGORIES', 'COMMENT', 'DESCRIPTION',
116
		'LOCATION', 'RESOURCES', 'STATUS', 'SUMMARY', 'ATTENDEE', 'CONTACT',
117
		'ORGANIZER'];
118
119
	/** @var array parameters to index */
120
	public static $indexParameters = [
121
		'ATTENDEE' => ['CN'],
122
		'ORGANIZER' => ['CN'],
123
	];
124
125
	/**
126
	 * @var string[] Map of uid => display name
127
	 */
128
	protected $userDisplayNames;
129
130
	/** @var IDBConnection */
131
	private $db;
132
133
	/** @var Backend */
134
	private $sharingBackend;
135
136
	/** @var Principal */
137
	private $principalBackend;
138
139
	/** @var IUserManager */
140
	private $userManager;
141
142
	/** @var ISecureRandom */
143
	private $random;
144
145
	/** @var EventDispatcherInterface */
146
	private $dispatcher;
147
148
	/** @var bool */
149
	private $legacyEndpoint;
150
151
	/** @var string */
152
	private $dbObjectPropertiesTable = 'calendarobjects_props';
153
154
	/**
155
	 * CalDavBackend constructor.
156
	 *
157
	 * @param IDBConnection $db
158
	 * @param Principal $principalBackend
159
	 * @param IUserManager $userManager
160
	 * @param ISecureRandom $random
161
	 * @param EventDispatcherInterface $dispatcher
162
	 * @param bool $legacyEndpoint
163
	 */
164
	public function __construct(IDBConnection $db,
165
								Principal $principalBackend,
166
								IUserManager $userManager,
167
								ISecureRandom $random,
168
								EventDispatcherInterface $dispatcher,
169
								$legacyEndpoint = false) {
170
		$this->db = $db;
171
		$this->principalBackend = $principalBackend;
172
		$this->userManager = $userManager;
173
		$this->sharingBackend = new Backend($this->db, $principalBackend, 'calendar');
174
		$this->random = $random;
175
		$this->dispatcher = $dispatcher;
176
		$this->legacyEndpoint = $legacyEndpoint;
177
	}
178
179
	/**
180
	 * Return the number of calendars for a principal
181
	 *
182
	 * By default this excludes the automatically generated birthday calendar
183
	 *
184
	 * @param $principalUri
185
	 * @param bool $excludeBirthday
186
	 * @return int
187
	 */
188 View Code Duplication
	public function getCalendarsForUserCount($principalUri, $excludeBirthday = true) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
189
		$principalUri = $this->convertPrincipal($principalUri, true);
190
		$query = $this->db->getQueryBuilder();
191
		$query->select($query->createFunction('COUNT(*)'))
192
			->from('calendars')
193
			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)));
194
195
		if ($excludeBirthday) {
196
			$query->andWhere($query->expr()->neq('uri', $query->createNamedParameter(BirthdayService::BIRTHDAY_CALENDAR_URI)));
197
		}
198
199
		return (int)$query->execute()->fetchColumn();
200
	}
201
202
	/**
203
	 * Returns a list of calendars for a principal.
204
	 *
205
	 * Every project is an array with the following keys:
206
	 *  * id, a unique id that will be used by other functions to modify the
207
	 *    calendar. This can be the same as the uri or a database key.
208
	 *  * uri, which the basename of the uri with which the calendar is
209
	 *    accessed.
210
	 *  * principaluri. The owner of the calendar. Almost always the same as
211
	 *    principalUri passed to this method.
212
	 *
213
	 * Furthermore it can contain webdav properties in clark notation. A very
214
	 * common one is '{DAV:}displayname'.
215
	 *
216
	 * Many clients also require:
217
	 * {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
218
	 * For this property, you can just return an instance of
219
	 * Sabre\CalDAV\Property\SupportedCalendarComponentSet.
220
	 *
221
	 * If you return {http://sabredav.org/ns}read-only and set the value to 1,
222
	 * ACL will automatically be put in read-only mode.
223
	 *
224
	 * @param string $principalUri
225
	 * @return array
226
	 */
227
	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...
228
		$principalUriOriginal = $principalUri;
229
		$principalUri = $this->convertPrincipal($principalUri, true);
230
		$fields = array_values($this->propertyMap);
231
		$fields[] = 'id';
232
		$fields[] = 'uri';
233
		$fields[] = 'synctoken';
234
		$fields[] = 'components';
235
		$fields[] = 'principaluri';
236
		$fields[] = 'transparent';
237
238
		// Making fields a comma-delimited list
239
		$query = $this->db->getQueryBuilder();
240
		$query->select($fields)->from('calendars')
241
				->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
242
				->orderBy('calendarorder', 'ASC');
243
		$stmt = $query->execute();
244
245
		$calendars = [];
246
		while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
247
248
			$components = [];
249
			if ($row['components']) {
250
				$components = explode(',',$row['components']);
251
			}
252
253
			$calendar = [
254
				'id' => $row['id'],
255
				'uri' => $row['uri'],
256
				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
257
				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
258
				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
259
				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
260
				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
261
				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
262
			];
263
264
			foreach($this->propertyMap as $xmlName=>$dbName) {
265
				$calendar[$xmlName] = $row[$dbName];
266
			}
267
268
			$this->addOwnerPrincipal($calendar);
269
270
			if (!isset($calendars[$calendar['id']])) {
271
				$calendars[$calendar['id']] = $calendar;
272
			}
273
		}
274
275
		$stmt->closeCursor();
276
277
		// query for shared calendars
278
		$principals = $this->principalBackend->getGroupMembership($principalUriOriginal, true);
279
		$principals[]= $principalUri;
280
281
		$fields = array_values($this->propertyMap);
282
		$fields[] = 'a.id';
283
		$fields[] = 'a.uri';
284
		$fields[] = 'a.synctoken';
285
		$fields[] = 'a.components';
286
		$fields[] = 'a.principaluri';
287
		$fields[] = 'a.transparent';
288
		$fields[] = 's.access';
289
		$query = $this->db->getQueryBuilder();
290
		$result = $query->select($fields)
291
			->from('dav_shares', 's')
292
			->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
293
			->where($query->expr()->in('s.principaluri', $query->createParameter('principaluri')))
294
			->andWhere($query->expr()->eq('s.type', $query->createParameter('type')))
295
			->setParameter('type', 'calendar')
296
			->setParameter('principaluri', $principals, \Doctrine\DBAL\Connection::PARAM_STR_ARRAY)
297
			->execute();
298
299
		$readOnlyPropertyName = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only';
300
		while($row = $result->fetch()) {
301
			if ($row['principaluri'] === $principalUri) {
302
				continue;
303
			}
304
305
			$readOnly = (int) $row['access'] === Backend::ACCESS_READ;
306 View Code Duplication
			if (isset($calendars[$row['id']])) {
307
				if ($readOnly) {
308
					// New share can not have more permissions then the old one.
309
					continue;
310
				}
311
				if (isset($calendars[$row['id']][$readOnlyPropertyName]) &&
312
					$calendars[$row['id']][$readOnlyPropertyName] === 0) {
313
					// Old share is already read-write, no more permissions can be gained
314
					continue;
315
				}
316
			}
317
318
			list(, $name) = URLUtil::splitPath($row['principaluri']);
319
			$uri = $row['uri'] . '_shared_by_' . $name;
320
			$row['displayname'] = $row['displayname'] . ' (' . $this->getUserDisplayName($name) . ')';
321
			$components = [];
322
			if ($row['components']) {
323
				$components = explode(',',$row['components']);
324
			}
325
			$calendar = [
326
				'id' => $row['id'],
327
				'uri' => $uri,
328
				'principaluri' => $this->convertPrincipal($principalUri, !$this->legacyEndpoint),
329
				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
330
				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
331
				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
332
				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
333
				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
334
				$readOnlyPropertyName => $readOnly,
335
			];
336
337
			foreach($this->propertyMap as $xmlName=>$dbName) {
338
				$calendar[$xmlName] = $row[$dbName];
339
			}
340
341
			$this->addOwnerPrincipal($calendar);
342
343
			$calendars[$calendar['id']] = $calendar;
344
		}
345
		$result->closeCursor();
346
347
		return array_values($calendars);
348
	}
349
350
	public function getUsersOwnCalendars($principalUri) {
351
		$principalUri = $this->convertPrincipal($principalUri, true);
352
		$fields = array_values($this->propertyMap);
353
		$fields[] = 'id';
354
		$fields[] = 'uri';
355
		$fields[] = 'synctoken';
356
		$fields[] = 'components';
357
		$fields[] = 'principaluri';
358
		$fields[] = 'transparent';
359
		// Making fields a comma-delimited list
360
		$query = $this->db->getQueryBuilder();
361
		$query->select($fields)->from('calendars')
362
			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
363
			->orderBy('calendarorder', 'ASC');
364
		$stmt = $query->execute();
365
		$calendars = [];
366
		while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
367
			$components = [];
368
			if ($row['components']) {
369
				$components = explode(',',$row['components']);
370
			}
371
			$calendar = [
372
				'id' => $row['id'],
373
				'uri' => $row['uri'],
374
				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
375
				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
376
				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
377
				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
378
				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
379
			];
380
			foreach($this->propertyMap as $xmlName=>$dbName) {
381
				$calendar[$xmlName] = $row[$dbName];
382
			}
383
384
			$this->addOwnerPrincipal($calendar);
385
386
			if (!isset($calendars[$calendar['id']])) {
387
				$calendars[$calendar['id']] = $calendar;
388
			}
389
		}
390
		$stmt->closeCursor();
391
		return array_values($calendars);
392
	}
393
394
395 View Code Duplication
	private function getUserDisplayName($uid) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
396
		if (!isset($this->userDisplayNames[$uid])) {
397
			$user = $this->userManager->get($uid);
398
399
			if ($user instanceof IUser) {
400
				$this->userDisplayNames[$uid] = $user->getDisplayName();
401
			} else {
402
				$this->userDisplayNames[$uid] = $uid;
403
			}
404
		}
405
406
		return $this->userDisplayNames[$uid];
407
	}
408
	
409
	/**
410
	 * @return array
411
	 */
412
	public function getPublicCalendars() {
413
		$fields = array_values($this->propertyMap);
414
		$fields[] = 'a.id';
415
		$fields[] = 'a.uri';
416
		$fields[] = 'a.synctoken';
417
		$fields[] = 'a.components';
418
		$fields[] = 'a.principaluri';
419
		$fields[] = 'a.transparent';
420
		$fields[] = 's.access';
421
		$fields[] = 's.publicuri';
422
		$calendars = [];
423
		$query = $this->db->getQueryBuilder();
424
		$result = $query->select($fields)
425
			->from('dav_shares', 's')
426
			->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
427
			->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
428
			->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar')))
429
			->execute();
430
431
		while($row = $result->fetch()) {
432
			list(, $name) = URLUtil::splitPath($row['principaluri']);
433
			$row['displayname'] = $row['displayname'] . "($name)";
434
			$components = [];
435
			if ($row['components']) {
436
				$components = explode(',',$row['components']);
437
			}
438
			$calendar = [
439
				'id' => $row['id'],
440
				'uri' => $row['publicuri'],
441
				'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
442
				'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
443
				'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
444
				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
445
				'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
446
				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], $this->legacyEndpoint),
447
				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
448
				'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
449
			];
450
451
			foreach($this->propertyMap as $xmlName=>$dbName) {
452
				$calendar[$xmlName] = $row[$dbName];
453
			}
454
455
			$this->addOwnerPrincipal($calendar);
456
457
			if (!isset($calendars[$calendar['id']])) {
458
				$calendars[$calendar['id']] = $calendar;
459
			}
460
		}
461
		$result->closeCursor();
462
463
		return array_values($calendars);
464
	}
465
466
	/**
467
	 * @param string $uri
468
	 * @return array
469
	 * @throws NotFound
470
	 */
471
	public function getPublicCalendar($uri) {
472
		$fields = array_values($this->propertyMap);
473
		$fields[] = 'a.id';
474
		$fields[] = 'a.uri';
475
		$fields[] = 'a.synctoken';
476
		$fields[] = 'a.components';
477
		$fields[] = 'a.principaluri';
478
		$fields[] = 'a.transparent';
479
		$fields[] = 's.access';
480
		$fields[] = 's.publicuri';
481
		$query = $this->db->getQueryBuilder();
482
		$result = $query->select($fields)
483
			->from('dav_shares', 's')
484
			->join('s', 'calendars', 'a', $query->expr()->eq('s.resourceid', 'a.id'))
485
			->where($query->expr()->in('s.access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
486
			->andWhere($query->expr()->eq('s.type', $query->createNamedParameter('calendar')))
487
			->andWhere($query->expr()->eq('s.publicuri', $query->createNamedParameter($uri)))
488
			->execute();
489
490
		$row = $result->fetch(\PDO::FETCH_ASSOC);
491
492
		$result->closeCursor();
493
494
		if ($row === false) {
495
			throw new NotFound('Node with name \'' . $uri . '\' could not be found');
496
		}
497
498
		list(, $name) = URLUtil::splitPath($row['principaluri']);
499
		$row['displayname'] = $row['displayname'] . ' ' . "($name)";
500
		$components = [];
501
		if ($row['components']) {
502
			$components = explode(',',$row['components']);
503
		}
504
		$calendar = [
505
			'id' => $row['id'],
506
			'uri' => $row['publicuri'],
507
			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
508
			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
509
			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
510
			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
511
			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
512
			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
513
			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}read-only' => (int)$row['access'] === Backend::ACCESS_READ,
514
			'{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}public' => (int)$row['access'] === self::ACCESS_PUBLIC,
515
		];
516
517
		foreach($this->propertyMap as $xmlName=>$dbName) {
518
			$calendar[$xmlName] = $row[$dbName];
519
		}
520
521
		$this->addOwnerPrincipal($calendar);
522
523
		return $calendar;
524
525
	}
526
527
	/**
528
	 * @param string $principal
529
	 * @param string $uri
530
	 * @return array|null
531
	 */
532
	public function getCalendarByUri($principal, $uri) {
533
		$fields = array_values($this->propertyMap);
534
		$fields[] = 'id';
535
		$fields[] = 'uri';
536
		$fields[] = 'synctoken';
537
		$fields[] = 'components';
538
		$fields[] = 'principaluri';
539
		$fields[] = 'transparent';
540
541
		// Making fields a comma-delimited list
542
		$query = $this->db->getQueryBuilder();
543
		$query->select($fields)->from('calendars')
544
			->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
545
			->andWhere($query->expr()->eq('principaluri', $query->createNamedParameter($principal)))
546
			->setMaxResults(1);
547
		$stmt = $query->execute();
548
549
		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
550
		$stmt->closeCursor();
551
		if ($row === false) {
552
			return null;
553
		}
554
555
		$components = [];
556
		if ($row['components']) {
557
			$components = explode(',',$row['components']);
558
		}
559
560
		$calendar = [
561
			'id' => $row['id'],
562
			'uri' => $row['uri'],
563
			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
564
			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
565
			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
566
			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
567
			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
568
		];
569
570
		foreach($this->propertyMap as $xmlName=>$dbName) {
571
			$calendar[$xmlName] = $row[$dbName];
572
		}
573
574
		$this->addOwnerPrincipal($calendar);
575
576
		return $calendar;
577
	}
578
579
	public function getCalendarById($calendarId) {
580
		$fields = array_values($this->propertyMap);
581
		$fields[] = 'id';
582
		$fields[] = 'uri';
583
		$fields[] = 'synctoken';
584
		$fields[] = 'components';
585
		$fields[] = 'principaluri';
586
		$fields[] = 'transparent';
587
588
		// Making fields a comma-delimited list
589
		$query = $this->db->getQueryBuilder();
590
		$query->select($fields)->from('calendars')
591
			->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)))
592
			->setMaxResults(1);
593
		$stmt = $query->execute();
594
595
		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
596
		$stmt->closeCursor();
597
		if ($row === false) {
598
			return null;
599
		}
600
601
		$components = [];
602
		if ($row['components']) {
603
			$components = explode(',',$row['components']);
604
		}
605
606
		$calendar = [
607
			'id' => $row['id'],
608
			'uri' => $row['uri'],
609
			'principaluri' => $this->convertPrincipal($row['principaluri'], !$this->legacyEndpoint),
610
			'{' . Plugin::NS_CALENDARSERVER . '}getctag' => 'http://sabre.io/ns/sync/' . ($row['synctoken']?$row['synctoken']:'0'),
611
			'{http://sabredav.org/ns}sync-token' => $row['synctoken']?$row['synctoken']:'0',
612
			'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet($components),
613
			'{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' => new ScheduleCalendarTransp($row['transparent']?'transparent':'opaque'),
614
		];
615
616
		foreach($this->propertyMap as $xmlName=>$dbName) {
617
			$calendar[$xmlName] = $row[$dbName];
618
		}
619
620
		$this->addOwnerPrincipal($calendar);
621
622
		return $calendar;
623
	}
624
625
	/**
626
	 * Creates a new calendar for a principal.
627
	 *
628
	 * If the creation was a success, an id must be returned that can be used to reference
629
	 * this calendar in other methods, such as updateCalendar.
630
	 *
631
	 * @param string $principalUri
632
	 * @param string $calendarUri
633
	 * @param array $properties
634
	 * @return int
635
	 */
636
	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...
637
		$values = [
638
			'principaluri' => $this->convertPrincipal($principalUri, true),
639
			'uri'          => $calendarUri,
640
			'synctoken'    => 1,
641
			'transparent'  => 0,
642
			'components'   => 'VEVENT,VTODO',
643
			'displayname'  => $calendarUri
644
		];
645
646
		// Default value
647
		$sccs = '{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set';
648
		if (isset($properties[$sccs])) {
649
			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...
650
				throw new DAV\Exception('The ' . $sccs . ' property must be of type: \Sabre\CalDAV\Property\SupportedCalendarComponentSet');
651
			}
652
			$values['components'] = implode(',',$properties[$sccs]->getValue());
653
		}
654
		$transp = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
655
		if (isset($properties[$transp])) {
656
			$values['transparent'] = (int) ($properties[$transp]->getValue() === 'transparent');
657
		}
658
659
		foreach($this->propertyMap as $xmlName=>$dbName) {
660
			if (isset($properties[$xmlName])) {
661
				$values[$dbName] = $properties[$xmlName];
662
			}
663
		}
664
665
		$query = $this->db->getQueryBuilder();
666
		$query->insert('calendars');
667
		foreach($values as $column => $value) {
668
			$query->setValue($column, $query->createNamedParameter($value));
669
		}
670
		$query->execute();
671
		$calendarId = $query->getLastInsertId();
672
673
		$this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendar', new GenericEvent(
674
			'\OCA\DAV\CalDAV\CalDavBackend::createCalendar',
675
			[
676
				'calendarId' => $calendarId,
677
				'calendarData' => $this->getCalendarById($calendarId),
678
		]));
679
680
		return $calendarId;
681
	}
682
683
	/**
684
	 * Updates properties for a calendar.
685
	 *
686
	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
687
	 * To do the actual updates, you must tell this object which properties
688
	 * you're going to process with the handle() method.
689
	 *
690
	 * Calling the handle method is like telling the PropPatch object "I
691
	 * promise I can handle updating this property".
692
	 *
693
	 * Read the PropPatch documentation for more info and examples.
694
	 *
695
	 * @param PropPatch $propPatch
696
	 * @return void
697
	 */
698
	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...
699
		$supportedProperties = array_keys($this->propertyMap);
700
		$supportedProperties[] = '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp';
701
702
		$propPatch->handle($supportedProperties, function($mutations) use ($calendarId) {
703
			$newValues = [];
704
			foreach ($mutations as $propertyName => $propertyValue) {
705
706
				switch ($propertyName) {
707
					case '{' . Plugin::NS_CALDAV . '}schedule-calendar-transp' :
708
						$fieldName = 'transparent';
709
						$newValues[$fieldName] = (int) ($propertyValue->getValue() === 'transparent');
710
						break;
711
					default :
712
						$fieldName = $this->propertyMap[$propertyName];
713
						$newValues[$fieldName] = $propertyValue;
714
						break;
715
				}
716
717
			}
718
			$query = $this->db->getQueryBuilder();
719
			$query->update('calendars');
720
			foreach ($newValues as $fieldName => $value) {
721
				$query->set($fieldName, $query->createNamedParameter($value));
722
			}
723
			$query->where($query->expr()->eq('id', $query->createNamedParameter($calendarId)));
724
			$query->execute();
725
726
			$this->addChange($calendarId, "", 2);
727
728
			$this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendar', new GenericEvent(
729
				'\OCA\DAV\CalDAV\CalDavBackend::updateCalendar',
730
				[
731
					'calendarId' => $calendarId,
732
					'calendarData' => $this->getCalendarById($calendarId),
733
					'shares' => $this->getShares($calendarId),
734
					'propertyMutations' => $mutations,
735
			]));
736
737
			return true;
738
		});
739
	}
740
741
	/**
742
	 * Delete a calendar and all it's objects
743
	 *
744
	 * @param mixed $calendarId
745
	 * @return void
746
	 */
747
	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...
748
		$this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar', new GenericEvent(
749
			'\OCA\DAV\CalDAV\CalDavBackend::deleteCalendar',
750
			[
751
				'calendarId' => $calendarId,
752
				'calendarData' => $this->getCalendarById($calendarId),
753
				'shares' => $this->getShares($calendarId),
754
		]));
755
756
		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ?');
757
		$stmt->execute([$calendarId]);
758
759
		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendars` WHERE `id` = ?');
760
		$stmt->execute([$calendarId]);
761
762
		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarchanges` WHERE `calendarid` = ?');
763
		$stmt->execute([$calendarId]);
764
765
		$this->sharingBackend->deleteAllShares($calendarId);
766
767
		$query = $this->db->getQueryBuilder();
768
		$query->delete($this->dbObjectPropertiesTable)
769
			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
770
			->execute();
771
	}
772
773
	/**
774
	 * Delete all of an user's shares
775
	 *
776
	 * @param string $principaluri
777
	 * @return void
778
	 */
779
	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...
780
		$this->sharingBackend->deleteAllSharesByUser($principaluri);
781
	}
782
783
	/**
784
	 * Returns all calendar objects within a calendar.
785
	 *
786
	 * Every item contains an array with the following keys:
787
	 *   * calendardata - The iCalendar-compatible calendar data
788
	 *   * uri - a unique key which will be used to construct the uri. This can
789
	 *     be any arbitrary string, but making sure it ends with '.ics' is a
790
	 *     good idea. This is only the basename, or filename, not the full
791
	 *     path.
792
	 *   * lastmodified - a timestamp of the last modification time
793
	 *   * etag - An arbitrary string, surrounded by double-quotes. (e.g.:
794
	 *   '"abcdef"')
795
	 *   * size - The size of the calendar objects, in bytes.
796
	 *   * component - optional, a string containing the type of object, such
797
	 *     as 'vevent' or 'vtodo'. If specified, this will be used to populate
798
	 *     the Content-Type header.
799
	 *
800
	 * Note that the etag is optional, but it's highly encouraged to return for
801
	 * speed reasons.
802
	 *
803
	 * The calendardata is also optional. If it's not returned
804
	 * 'getCalendarObject' will be called later, which *is* expected to return
805
	 * calendardata.
806
	 *
807
	 * If neither etag or size are specified, the calendardata will be
808
	 * used/fetched to determine these numbers. If both are specified the
809
	 * amount of times this is needed is reduced by a great degree.
810
	 *
811
	 * @param mixed $calendarId
812
	 * @return array
813
	 */
814
	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...
815
		$query = $this->db->getQueryBuilder();
816
		$query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'componenttype', 'classification'])
817
			->from('calendarobjects')
818
			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)));
819
		$stmt = $query->execute();
820
821
		$result = [];
822
		foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
823
			$result[] = [
824
					'id'           => $row['id'],
825
					'uri'          => $row['uri'],
826
					'lastmodified' => $row['lastmodified'],
827
					'etag'         => '"' . $row['etag'] . '"',
828
					'calendarid'   => $row['calendarid'],
829
					'size'         => (int)$row['size'],
830
					'component'    => strtolower($row['componenttype']),
831
					'classification'=> (int)$row['classification']
832
			];
833
		}
834
835
		return $result;
836
	}
837
838
	/**
839
	 * Returns information from a single calendar object, based on it's object
840
	 * uri.
841
	 *
842
	 * The object uri is only the basename, or filename and not a full path.
843
	 *
844
	 * The returned array must have the same keys as getCalendarObjects. The
845
	 * 'calendardata' object is required here though, while it's not required
846
	 * for getCalendarObjects.
847
	 *
848
	 * This method must return null if the object did not exist.
849
	 *
850
	 * @param mixed $calendarId
851
	 * @param string $objectUri
852
	 * @return array|null
853
	 */
854
	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...
855
856
		$query = $this->db->getQueryBuilder();
857
		$query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification'])
858
				->from('calendarobjects')
859
				->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
860
				->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)));
861
		$stmt = $query->execute();
862
		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
863
864
		if(!$row) return null;
865
866
		return [
867
				'id'            => $row['id'],
868
				'uri'           => $row['uri'],
869
				'lastmodified'  => $row['lastmodified'],
870
				'etag'          => '"' . $row['etag'] . '"',
871
				'calendarid'    => $row['calendarid'],
872
				'size'          => (int)$row['size'],
873
				'calendardata'  => $this->readBlob($row['calendardata']),
874
				'component'     => strtolower($row['componenttype']),
875
				'classification'=> (int)$row['classification']
876
		];
877
	}
878
879
	/**
880
	 * Returns a list of calendar objects.
881
	 *
882
	 * This method should work identical to getCalendarObject, but instead
883
	 * return all the calendar objects in the list as an array.
884
	 *
885
	 * If the backend supports this, it may allow for some speed-ups.
886
	 *
887
	 * @param mixed $calendarId
888
	 * @param string[] $uris
889
	 * @return array
890
	 */
891
	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...
892
		if (empty($uris)) {
893
			return [];
894
		}
895
896
		$chunks = array_chunk($uris, 100);
897
		$objects = [];
898
899
		$query = $this->db->getQueryBuilder();
900
		$query->select(['id', 'uri', 'lastmodified', 'etag', 'calendarid', 'size', 'calendardata', 'componenttype', 'classification'])
901
			->from('calendarobjects')
902
			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
903
			->andWhere($query->expr()->in('uri', $query->createParameter('uri')));
904
905
		foreach ($chunks as $uris) {
906
			$query->setParameter('uri', $uris, IQueryBuilder::PARAM_STR_ARRAY);
907
			$result = $query->execute();
908
909
			while ($row = $result->fetch()) {
910
				$objects[] = [
911
					'id'           => $row['id'],
912
					'uri'          => $row['uri'],
913
					'lastmodified' => $row['lastmodified'],
914
					'etag'         => '"' . $row['etag'] . '"',
915
					'calendarid'   => $row['calendarid'],
916
					'size'         => (int)$row['size'],
917
					'calendardata' => $this->readBlob($row['calendardata']),
918
					'component'    => strtolower($row['componenttype']),
919
					'classification' => (int)$row['classification']
920
				];
921
			}
922
			$result->closeCursor();
923
		}
924
		return $objects;
925
	}
926
927
	/**
928
	 * Creates a new calendar object.
929
	 *
930
	 * The object uri is only the basename, or filename and not a full path.
931
	 *
932
	 * It is possible return an etag from this function, which will be used in
933
	 * the response to this PUT request. Note that the ETag must be surrounded
934
	 * by double-quotes.
935
	 *
936
	 * However, you should only really return this ETag if you don't mangle the
937
	 * calendar-data. If the result of a subsequent GET to this object is not
938
	 * the exact same as this request body, you should omit the ETag.
939
	 *
940
	 * @param mixed $calendarId
941
	 * @param string $objectUri
942
	 * @param string $calendarData
943
	 * @return string
944
	 */
945
	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...
946
		$extraData = $this->getDenormalizedData($calendarData);
947
948
		$query = $this->db->getQueryBuilder();
949
		$query->insert('calendarobjects')
950
			->values([
951
				'calendarid' => $query->createNamedParameter($calendarId),
952
				'uri' => $query->createNamedParameter($objectUri),
953
				'calendardata' => $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB),
954
				'lastmodified' => $query->createNamedParameter(time()),
955
				'etag' => $query->createNamedParameter($extraData['etag']),
956
				'size' => $query->createNamedParameter($extraData['size']),
957
				'componenttype' => $query->createNamedParameter($extraData['componentType']),
958
				'firstoccurence' => $query->createNamedParameter($extraData['firstOccurence']),
959
				'lastoccurence' => $query->createNamedParameter($extraData['lastOccurence']),
960
				'classification' => $query->createNamedParameter($extraData['classification']),
961
				'uid' => $query->createNamedParameter($extraData['uid']),
962
			])
963
			->execute();
964
965
		$this->updateProperties($calendarId, $objectUri, $calendarData);
966
967
		$this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject', new GenericEvent(
968
			'\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject',
969
			[
970
				'calendarId' => $calendarId,
971
				'calendarData' => $this->getCalendarById($calendarId),
972
				'shares' => $this->getShares($calendarId),
973
				'objectData' => $this->getCalendarObject($calendarId, $objectUri),
974
			]
975
		));
976
		$this->addChange($calendarId, $objectUri, 1);
977
978
		return '"' . $extraData['etag'] . '"';
979
	}
980
981
	/**
982
	 * Updates an existing calendarobject, based on it's uri.
983
	 *
984
	 * The object uri is only the basename, or filename and not a full path.
985
	 *
986
	 * It is possible return an etag from this function, which will be used in
987
	 * the response to this PUT request. Note that the ETag must be surrounded
988
	 * by double-quotes.
989
	 *
990
	 * However, you should only really return this ETag if you don't mangle the
991
	 * calendar-data. If the result of a subsequent GET to this object is not
992
	 * the exact same as this request body, you should omit the ETag.
993
	 *
994
	 * @param mixed $calendarId
995
	 * @param string $objectUri
996
	 * @param string $calendarData
997
	 * @return string
998
	 */
999
	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...
1000
		$extraData = $this->getDenormalizedData($calendarData);
1001
1002
		$query = $this->db->getQueryBuilder();
1003
		$query->update('calendarobjects')
1004
				->set('calendardata', $query->createNamedParameter($calendarData, IQueryBuilder::PARAM_LOB))
1005
				->set('lastmodified', $query->createNamedParameter(time()))
1006
				->set('etag', $query->createNamedParameter($extraData['etag']))
1007
				->set('size', $query->createNamedParameter($extraData['size']))
1008
				->set('componenttype', $query->createNamedParameter($extraData['componentType']))
1009
				->set('firstoccurence', $query->createNamedParameter($extraData['firstOccurence']))
1010
				->set('lastoccurence', $query->createNamedParameter($extraData['lastOccurence']))
1011
				->set('classification', $query->createNamedParameter($extraData['classification']))
1012
				->set('uid', $query->createNamedParameter($extraData['uid']))
1013
			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)))
1014
			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
1015
			->execute();
1016
1017
		$this->updateProperties($calendarId, $objectUri, $calendarData);
1018
1019
		$data = $this->getCalendarObject($calendarId, $objectUri);
1020 View Code Duplication
		if (is_array($data)) {
1021
			$this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject', new GenericEvent(
1022
				'\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject',
1023
				[
1024
					'calendarId' => $calendarId,
1025
					'calendarData' => $this->getCalendarById($calendarId),
1026
					'shares' => $this->getShares($calendarId),
1027
					'objectData' => $data,
1028
				]
1029
			));
1030
		}
1031
		$this->addChange($calendarId, $objectUri, 2);
1032
1033
		return '"' . $extraData['etag'] . '"';
1034
	}
1035
1036
	/**
1037
	 * @param int $calendarObjectId
1038
	 * @param int $classification
1039
	 */
1040
	public function setClassification($calendarObjectId, $classification) {
1041
		if (!in_array($classification, [
1042
			self::CLASSIFICATION_PUBLIC, self::CLASSIFICATION_PRIVATE, self::CLASSIFICATION_CONFIDENTIAL
1043
		])) {
1044
			throw new \InvalidArgumentException();
1045
		}
1046
		$query = $this->db->getQueryBuilder();
1047
		$query->update('calendarobjects')
1048
			->set('classification', $query->createNamedParameter($classification))
1049
			->where($query->expr()->eq('id', $query->createNamedParameter($calendarObjectId)))
1050
			->execute();
1051
	}
1052
1053
	/**
1054
	 * Deletes an existing calendar object.
1055
	 *
1056
	 * The object uri is only the basename, or filename and not a full path.
1057
	 *
1058
	 * @param mixed $calendarId
1059
	 * @param string $objectUri
1060
	 * @return void
1061
	 */
1062
	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...
1063
		$data = $this->getCalendarObject($calendarId, $objectUri);
1064 View Code Duplication
		if (is_array($data)) {
1065
			$this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject', new GenericEvent(
1066
				'\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject',
1067
				[
1068
					'calendarId' => $calendarId,
1069
					'calendarData' => $this->getCalendarById($calendarId),
1070
					'shares' => $this->getShares($calendarId),
1071
					'objectData' => $data,
1072
				]
1073
			));
1074
		}
1075
1076
		$stmt = $this->db->prepare('DELETE FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ? AND `uri` = ?');
1077
		$stmt->execute([$calendarId, $objectUri]);
1078
1079
		$this->purgeProperties($calendarId, $data['id']);
1080
1081
		$this->addChange($calendarId, $objectUri, 3);
1082
	}
1083
1084
	/**
1085
	 * Performs a calendar-query on the contents of this calendar.
1086
	 *
1087
	 * The calendar-query is defined in RFC4791 : CalDAV. Using the
1088
	 * calendar-query it is possible for a client to request a specific set of
1089
	 * object, based on contents of iCalendar properties, date-ranges and
1090
	 * iCalendar component types (VTODO, VEVENT).
1091
	 *
1092
	 * This method should just return a list of (relative) urls that match this
1093
	 * query.
1094
	 *
1095
	 * The list of filters are specified as an array. The exact array is
1096
	 * documented by Sabre\CalDAV\CalendarQueryParser.
1097
	 *
1098
	 * Note that it is extremely likely that getCalendarObject for every path
1099
	 * returned from this method will be called almost immediately after. You
1100
	 * may want to anticipate this to speed up these requests.
1101
	 *
1102
	 * This method provides a default implementation, which parses *all* the
1103
	 * iCalendar objects in the specified calendar.
1104
	 *
1105
	 * This default may well be good enough for personal use, and calendars
1106
	 * that aren't very large. But if you anticipate high usage, big calendars
1107
	 * or high loads, you are strongly advised to optimize certain paths.
1108
	 *
1109
	 * The best way to do so is override this method and to optimize
1110
	 * specifically for 'common filters'.
1111
	 *
1112
	 * Requests that are extremely common are:
1113
	 *   * requests for just VEVENTS
1114
	 *   * requests for just VTODO
1115
	 *   * requests with a time-range-filter on either VEVENT or VTODO.
1116
	 *
1117
	 * ..and combinations of these requests. It may not be worth it to try to
1118
	 * handle every possible situation and just rely on the (relatively
1119
	 * easy to use) CalendarQueryValidator to handle the rest.
1120
	 *
1121
	 * Note that especially time-range-filters may be difficult to parse. A
1122
	 * time-range filter specified on a VEVENT must for instance also handle
1123
	 * recurrence rules correctly.
1124
	 * A good example of how to interprete all these filters can also simply
1125
	 * be found in Sabre\CalDAV\CalendarQueryFilter. This class is as correct
1126
	 * as possible, so it gives you a good idea on what type of stuff you need
1127
	 * to think of.
1128
	 *
1129
	 * @param mixed $calendarId
1130
	 * @param array $filters
1131
	 * @return array
1132
	 */
1133
	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...
1134
		$componentType = null;
1135
		$requirePostFilter = true;
1136
		$timeRange = null;
1137
1138
		// if no filters were specified, we don't need to filter after a query
1139
		if (!$filters['prop-filters'] && !$filters['comp-filters']) {
1140
			$requirePostFilter = false;
1141
		}
1142
1143
		// Figuring out if there's a component filter
1144
		if (count($filters['comp-filters']) > 0 && !$filters['comp-filters'][0]['is-not-defined']) {
1145
			$componentType = $filters['comp-filters'][0]['name'];
1146
1147
			// Checking if we need post-filters
1148 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']) {
1149
				$requirePostFilter = false;
1150
			}
1151
			// There was a time-range filter
1152
			if ($componentType == 'VEVENT' && isset($filters['comp-filters'][0]['time-range'])) {
1153
				$timeRange = $filters['comp-filters'][0]['time-range'];
1154
1155
				// If start time OR the end time is not specified, we can do a
1156
				// 100% accurate mysql query.
1157 View Code Duplication
				if (!$filters['prop-filters'] && !$filters['comp-filters'][0]['comp-filters'] && !$filters['comp-filters'][0]['prop-filters'] && (!$timeRange['start'] || !$timeRange['end'])) {
1158
					$requirePostFilter = false;
1159
				}
1160
			}
1161
1162
		}
1163
		$columns = ['uri'];
1164
		if ($requirePostFilter) {
1165
			$columns = ['uri', 'calendardata'];
1166
		}
1167
		$query = $this->db->getQueryBuilder();
1168
		$query->select($columns)
1169
			->from('calendarobjects')
1170
			->where($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)));
1171
1172
		if ($componentType) {
1173
			$query->andWhere($query->expr()->eq('componenttype', $query->createNamedParameter($componentType)));
1174
		}
1175
1176
		if ($timeRange && $timeRange['start']) {
1177
			$query->andWhere($query->expr()->gt('lastoccurence', $query->createNamedParameter($timeRange['start']->getTimeStamp())));
1178
		}
1179
		if ($timeRange && $timeRange['end']) {
1180
			$query->andWhere($query->expr()->lt('firstoccurence', $query->createNamedParameter($timeRange['end']->getTimeStamp())));
1181
		}
1182
1183
		$stmt = $query->execute();
1184
1185
		$result = [];
1186
		while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1187
			if ($requirePostFilter) {
1188
				if (!$this->validateFilterForObject($row, $filters)) {
1189
					continue;
1190
				}
1191
			}
1192
			$result[] = $row['uri'];
1193
		}
1194
1195
		return $result;
1196
	}
1197
1198
	/**
1199
	 * custom Nextcloud search extension for CalDAV
1200
	 *
1201
	 * @param string $principalUri
1202
	 * @param array $filters
1203
	 * @param integer|null $limit
1204
	 * @param integer|null $offset
1205
	 * @return array
1206
	 */
1207
	public function calendarSearch($principalUri, array $filters, $limit=null, $offset=null) {
1208
		$calendars = $this->getCalendarsForUser($principalUri);
1209
		$ownCalendars = [];
1210
		$sharedCalendars = [];
1211
1212
		$uriMapper = [];
1213
1214
		foreach($calendars as $calendar) {
1215
			if ($calendar['{http://owncloud.org/ns}owner-principal'] === $principalUri) {
1216
				$ownCalendars[] = $calendar['id'];
1217
			} else {
1218
				$sharedCalendars[] = $calendar['id'];
1219
			}
1220
			$uriMapper[$calendar['id']] = $calendar['uri'];
1221
		}
1222
		if (count($ownCalendars) === 0 && count($sharedCalendars) === 0) {
1223
			return [];
1224
		}
1225
1226
		$query = $this->db->getQueryBuilder();
1227
		// Calendar id expressions
1228
		$calendarExpressions = [];
1229
		foreach($ownCalendars as $id) {
1230
			$calendarExpressions[] = $query->expr()
1231
				->eq('c.calendarid', $query->createNamedParameter($id));
1232
		}
1233
		foreach($sharedCalendars as $id) {
1234
			$calendarExpressions[] = $query->expr()->andX(
1235
				$query->expr()->eq('c.calendarid',
1236
					$query->createNamedParameter($id)),
1237
				$query->expr()->eq('c.classification',
1238
					$query->createNamedParameter(self::CLASSIFICATION_PUBLIC))
1239
			);
1240
		}
1241
1242
		if (count($calendarExpressions) === 1) {
1243
			$calExpr = $calendarExpressions[0];
1244
		} else {
1245
			$calExpr = call_user_func_array([$query->expr(), 'orX'], $calendarExpressions);
1246
		}
1247
1248
		// Component expressions
1249
		$compExpressions = [];
1250
		foreach($filters['comps'] as $comp) {
1251
			$compExpressions[] = $query->expr()
1252
				->eq('c.componenttype', $query->createNamedParameter($comp));
1253
		}
1254
1255
		if (count($compExpressions) === 1) {
1256
			$compExpr = $compExpressions[0];
1257
		} else {
1258
			$compExpr = call_user_func_array([$query->expr(), 'orX'], $compExpressions);
1259
		}
1260
1261
		if (!isset($filters['props'])) {
1262
			$filters['props'] = [];
1263
		}
1264
		if (!isset($filters['params'])) {
1265
			$filters['params'] = [];
1266
		}
1267
1268
		$propParamExpressions = [];
1269
		foreach($filters['props'] as $prop) {
1270
			$propParamExpressions[] = $query->expr()->andX(
1271
				$query->expr()->eq('i.name', $query->createNamedParameter($prop)),
1272
				$query->expr()->isNull('i.parameter')
1273
			);
1274
		}
1275
		foreach($filters['params'] as $param) {
1276
			$propParamExpressions[] = $query->expr()->andX(
1277
				$query->expr()->eq('i.name', $query->createNamedParameter($param['property'])),
1278
				$query->expr()->eq('i.parameter', $query->createNamedParameter($param['parameter']))
1279
			);
1280
		}
1281
1282
		if (count($propParamExpressions) === 1) {
1283
			$propParamExpr = $propParamExpressions[0];
1284
		} else {
1285
			$propParamExpr = call_user_func_array([$query->expr(), 'orX'], $propParamExpressions);
1286
		}
1287
1288
		$query->select(['c.calendarid', 'c.uri'])
1289
			->from($this->dbObjectPropertiesTable, 'i')
1290
			->join('i', 'calendarobjects', 'c', $query->expr()->eq('i.objectid', 'c.id'))
1291
			->where($calExpr)
1292
			->andWhere($compExpr)
1293
			->andWhere($propParamExpr)
1294
			->andWhere($query->expr()->iLike('i.value',
1295
				$query->createNamedParameter('%'.$this->db->escapeLikeParameter($filters['search-term']).'%')));
1296
1297
		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...
1298
			$query->setFirstResult($offset);
1299
		}
1300
		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...
1301
			$query->setMaxResults($limit);
1302
		}
1303
1304
		$stmt = $query->execute();
1305
1306
		$result = [];
1307
		while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1308
			$path = $uriMapper[$row['calendarid']] . '/' . $row['uri'];
1309
			if (!in_array($path, $result)) {
1310
				$result[] = $path;
1311
			}
1312
		}
1313
1314
		return $result;
1315
	}
1316
1317
	/**
1318
	 * Searches through all of a users calendars and calendar objects to find
1319
	 * an object with a specific UID.
1320
	 *
1321
	 * This method should return the path to this object, relative to the
1322
	 * calendar home, so this path usually only contains two parts:
1323
	 *
1324
	 * calendarpath/objectpath.ics
1325
	 *
1326
	 * If the uid is not found, return null.
1327
	 *
1328
	 * This method should only consider * objects that the principal owns, so
1329
	 * any calendars owned by other principals that also appear in this
1330
	 * collection should be ignored.
1331
	 *
1332
	 * @param string $principalUri
1333
	 * @param string $uid
1334
	 * @return string|null
1335
	 */
1336
	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...
1337
1338
		$query = $this->db->getQueryBuilder();
1339
		$query->selectAlias('c.uri', 'calendaruri')->selectAlias('co.uri', 'objecturi')
1340
			->from('calendarobjects', 'co')
1341
			->leftJoin('co', 'calendars', 'c', $query->expr()->eq('co.calendarid', 'c.id'))
1342
			->where($query->expr()->eq('c.principaluri', $query->createNamedParameter($principalUri)))
1343
			->andWhere($query->expr()->eq('co.uid', $query->createNamedParameter($uid)));
1344
1345
		$stmt = $query->execute();
1346
1347
		if ($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1348
			return $row['calendaruri'] . '/' . $row['objecturi'];
1349
		}
1350
1351
		return null;
1352
	}
1353
1354
	/**
1355
	 * The getChanges method returns all the changes that have happened, since
1356
	 * the specified syncToken in the specified calendar.
1357
	 *
1358
	 * This function should return an array, such as the following:
1359
	 *
1360
	 * [
1361
	 *   'syncToken' => 'The current synctoken',
1362
	 *   'added'   => [
1363
	 *      'new.txt',
1364
	 *   ],
1365
	 *   'modified'   => [
1366
	 *      'modified.txt',
1367
	 *   ],
1368
	 *   'deleted' => [
1369
	 *      'foo.php.bak',
1370
	 *      'old.txt'
1371
	 *   ]
1372
	 * );
1373
	 *
1374
	 * The returned syncToken property should reflect the *current* syncToken
1375
	 * of the calendar, as reported in the {http://sabredav.org/ns}sync-token
1376
	 * property This is * needed here too, to ensure the operation is atomic.
1377
	 *
1378
	 * If the $syncToken argument is specified as null, this is an initial
1379
	 * sync, and all members should be reported.
1380
	 *
1381
	 * The modified property is an array of nodenames that have changed since
1382
	 * the last token.
1383
	 *
1384
	 * The deleted property is an array with nodenames, that have been deleted
1385
	 * from collection.
1386
	 *
1387
	 * The $syncLevel argument is basically the 'depth' of the report. If it's
1388
	 * 1, you only have to report changes that happened only directly in
1389
	 * immediate descendants. If it's 2, it should also include changes from
1390
	 * the nodes below the child collections. (grandchildren)
1391
	 *
1392
	 * The $limit argument allows a client to specify how many results should
1393
	 * be returned at most. If the limit is not specified, it should be treated
1394
	 * as infinite.
1395
	 *
1396
	 * If the limit (infinite or not) is higher than you're willing to return,
1397
	 * you should throw a Sabre\DAV\Exception\TooMuchMatches() exception.
1398
	 *
1399
	 * If the syncToken is expired (due to data cleanup) or unknown, you must
1400
	 * return null.
1401
	 *
1402
	 * The limit is 'suggestive'. You are free to ignore it.
1403
	 *
1404
	 * @param string $calendarId
1405
	 * @param string $syncToken
1406
	 * @param int $syncLevel
1407
	 * @param int $limit
1408
	 * @return array
1409
	 */
1410 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...
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1411
		// Current synctoken
1412
		$stmt = $this->db->prepare('SELECT `synctoken` FROM `*PREFIX*calendars` WHERE `id` = ?');
1413
		$stmt->execute([ $calendarId ]);
1414
		$currentToken = $stmt->fetchColumn(0);
1415
1416
		if (is_null($currentToken)) {
1417
			return null;
1418
		}
1419
1420
		$result = [
1421
			'syncToken' => $currentToken,
1422
			'added'     => [],
1423
			'modified'  => [],
1424
			'deleted'   => [],
1425
		];
1426
1427
		if ($syncToken) {
1428
1429
			$query = "SELECT `uri`, `operation` FROM `*PREFIX*calendarchanges` WHERE `synctoken` >= ? AND `synctoken` < ? AND `calendarid` = ? ORDER BY `synctoken`";
1430
			if ($limit>0) {
1431
				$query.= " `LIMIT` " . (int)$limit;
1432
			}
1433
1434
			// Fetching all changes
1435
			$stmt = $this->db->prepare($query);
1436
			$stmt->execute([$syncToken, $currentToken, $calendarId]);
1437
1438
			$changes = [];
1439
1440
			// This loop ensures that any duplicates are overwritten, only the
1441
			// last change on a node is relevant.
1442
			while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1443
1444
				$changes[$row['uri']] = $row['operation'];
1445
1446
			}
1447
1448
			foreach($changes as $uri => $operation) {
1449
1450
				switch($operation) {
1451
					case 1 :
1452
						$result['added'][] = $uri;
1453
						break;
1454
					case 2 :
1455
						$result['modified'][] = $uri;
1456
						break;
1457
					case 3 :
1458
						$result['deleted'][] = $uri;
1459
						break;
1460
				}
1461
1462
			}
1463
		} else {
1464
			// No synctoken supplied, this is the initial sync.
1465
			$query = "SELECT `uri` FROM `*PREFIX*calendarobjects` WHERE `calendarid` = ?";
1466
			$stmt = $this->db->prepare($query);
1467
			$stmt->execute([$calendarId]);
1468
1469
			$result['added'] = $stmt->fetchAll(\PDO::FETCH_COLUMN);
1470
		}
1471
		return $result;
1472
1473
	}
1474
1475
	/**
1476
	 * Returns a list of subscriptions for a principal.
1477
	 *
1478
	 * Every subscription is an array with the following keys:
1479
	 *  * id, a unique id that will be used by other functions to modify the
1480
	 *    subscription. This can be the same as the uri or a database key.
1481
	 *  * uri. This is just the 'base uri' or 'filename' of the subscription.
1482
	 *  * principaluri. The owner of the subscription. Almost always the same as
1483
	 *    principalUri passed to this method.
1484
	 *
1485
	 * Furthermore, all the subscription info must be returned too:
1486
	 *
1487
	 * 1. {DAV:}displayname
1488
	 * 2. {http://apple.com/ns/ical/}refreshrate
1489
	 * 3. {http://calendarserver.org/ns/}subscribed-strip-todos (omit if todos
1490
	 *    should not be stripped).
1491
	 * 4. {http://calendarserver.org/ns/}subscribed-strip-alarms (omit if alarms
1492
	 *    should not be stripped).
1493
	 * 5. {http://calendarserver.org/ns/}subscribed-strip-attachments (omit if
1494
	 *    attachments should not be stripped).
1495
	 * 6. {http://calendarserver.org/ns/}source (Must be a
1496
	 *     Sabre\DAV\Property\Href).
1497
	 * 7. {http://apple.com/ns/ical/}calendar-color
1498
	 * 8. {http://apple.com/ns/ical/}calendar-order
1499
	 * 9. {urn:ietf:params:xml:ns:caldav}supported-calendar-component-set
1500
	 *    (should just be an instance of
1501
	 *    Sabre\CalDAV\Property\SupportedCalendarComponentSet, with a bunch of
1502
	 *    default components).
1503
	 *
1504
	 * @param string $principalUri
1505
	 * @return array
1506
	 */
1507
	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...
1508
		$fields = array_values($this->subscriptionPropertyMap);
1509
		$fields[] = 'id';
1510
		$fields[] = 'uri';
1511
		$fields[] = 'source';
1512
		$fields[] = 'principaluri';
1513
		$fields[] = 'lastmodified';
1514
1515
		$query = $this->db->getQueryBuilder();
1516
		$query->select($fields)
1517
			->from('calendarsubscriptions')
1518
			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
1519
			->orderBy('calendarorder', 'asc');
1520
		$stmt =$query->execute();
1521
1522
		$subscriptions = [];
1523
		while($row = $stmt->fetch(\PDO::FETCH_ASSOC)) {
1524
1525
			$subscription = [
1526
				'id'           => $row['id'],
1527
				'uri'          => $row['uri'],
1528
				'principaluri' => $row['principaluri'],
1529
				'source'       => $row['source'],
1530
				'lastmodified' => $row['lastmodified'],
1531
1532
				'{' . Plugin::NS_CALDAV . '}supported-calendar-component-set' => new SupportedCalendarComponentSet(['VTODO', 'VEVENT']),
1533
			];
1534
1535
			foreach($this->subscriptionPropertyMap as $xmlName=>$dbName) {
1536
				if (!is_null($row[$dbName])) {
1537
					$subscription[$xmlName] = $row[$dbName];
1538
				}
1539
			}
1540
1541
			$subscriptions[] = $subscription;
1542
1543
		}
1544
1545
		return $subscriptions;
1546
	}
1547
1548
	/**
1549
	 * Creates a new subscription for a principal.
1550
	 *
1551
	 * If the creation was a success, an id must be returned that can be used to reference
1552
	 * this subscription in other methods, such as updateSubscription.
1553
	 *
1554
	 * @param string $principalUri
1555
	 * @param string $uri
1556
	 * @param array $properties
1557
	 * @return mixed
1558
	 */
1559
	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...
1560
1561
		if (!isset($properties['{http://calendarserver.org/ns/}source'])) {
1562
			throw new Forbidden('The {http://calendarserver.org/ns/}source property is required when creating subscriptions');
1563
		}
1564
1565
		$values = [
1566
			'principaluri' => $principalUri,
1567
			'uri'          => $uri,
1568
			'source'       => $properties['{http://calendarserver.org/ns/}source']->getHref(),
1569
			'lastmodified' => time(),
1570
		];
1571
1572
		$propertiesBoolean = ['striptodos', 'stripalarms', 'stripattachments'];
1573
1574
		foreach($this->subscriptionPropertyMap as $xmlName=>$dbName) {
1575
			if (array_key_exists($xmlName, $properties)) {
1576
					$values[$dbName] = $properties[$xmlName];
1577
					if (in_array($dbName, $propertiesBoolean)) {
1578
						$values[$dbName] = true;
1579
				}
1580
			}
1581
		}
1582
1583
		$valuesToInsert = array();
1584
1585
		$query = $this->db->getQueryBuilder();
1586
1587
		foreach (array_keys($values) as $name) {
1588
			$valuesToInsert[$name] = $query->createNamedParameter($values[$name]);
1589
		}
1590
1591
		$query->insert('calendarsubscriptions')
1592
			->values($valuesToInsert)
1593
			->execute();
1594
1595
		return $this->db->lastInsertId('*PREFIX*calendarsubscriptions');
1596
	}
1597
1598
	/**
1599
	 * Updates a subscription
1600
	 *
1601
	 * The list of mutations is stored in a Sabre\DAV\PropPatch object.
1602
	 * To do the actual updates, you must tell this object which properties
1603
	 * you're going to process with the handle() method.
1604
	 *
1605
	 * Calling the handle method is like telling the PropPatch object "I
1606
	 * promise I can handle updating this property".
1607
	 *
1608
	 * Read the PropPatch documentation for more info and examples.
1609
	 *
1610
	 * @param mixed $subscriptionId
1611
	 * @param PropPatch $propPatch
1612
	 * @return void
1613
	 */
1614
	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...
1615
		$supportedProperties = array_keys($this->subscriptionPropertyMap);
1616
		$supportedProperties[] = '{http://calendarserver.org/ns/}source';
1617
1618
		$propPatch->handle($supportedProperties, function($mutations) use ($subscriptionId) {
1619
1620
			$newValues = [];
1621
1622
			foreach($mutations as $propertyName=>$propertyValue) {
1623
				if ($propertyName === '{http://calendarserver.org/ns/}source') {
1624
					$newValues['source'] = $propertyValue->getHref();
1625
				} else {
1626
					$fieldName = $this->subscriptionPropertyMap[$propertyName];
1627
					$newValues[$fieldName] = $propertyValue;
1628
				}
1629
			}
1630
1631
			$query = $this->db->getQueryBuilder();
1632
			$query->update('calendarsubscriptions')
1633
				->set('lastmodified', $query->createNamedParameter(time()));
1634
			foreach($newValues as $fieldName=>$value) {
1635
				$query->set($fieldName, $query->createNamedParameter($value));
1636
			}
1637
			$query->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
1638
				->execute();
1639
1640
			return true;
1641
1642
		});
1643
	}
1644
1645
	/**
1646
	 * Deletes a subscription.
1647
	 *
1648
	 * @param mixed $subscriptionId
1649
	 * @return void
1650
	 */
1651
	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...
1652
		$query = $this->db->getQueryBuilder();
1653
		$query->delete('calendarsubscriptions')
1654
			->where($query->expr()->eq('id', $query->createNamedParameter($subscriptionId)))
1655
			->execute();
1656
	}
1657
1658
	/**
1659
	 * Returns a single scheduling object for the inbox collection.
1660
	 *
1661
	 * The returned array should contain the following elements:
1662
	 *   * uri - A unique basename for the object. This will be used to
1663
	 *           construct a full uri.
1664
	 *   * calendardata - The iCalendar object
1665
	 *   * lastmodified - The last modification date. Can be an int for a unix
1666
	 *                    timestamp, or a PHP DateTime object.
1667
	 *   * etag - A unique token that must change if the object changed.
1668
	 *   * size - The size of the object, in bytes.
1669
	 *
1670
	 * @param string $principalUri
1671
	 * @param string $objectUri
1672
	 * @return array
1673
	 */
1674
	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...
1675
		$query = $this->db->getQueryBuilder();
1676
		$stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
1677
			->from('schedulingobjects')
1678
			->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
1679
			->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
1680
			->execute();
1681
1682
		$row = $stmt->fetch(\PDO::FETCH_ASSOC);
1683
1684
		if(!$row) {
1685
			return null;
1686
		}
1687
1688
		return [
1689
				'uri'          => $row['uri'],
1690
				'calendardata' => $row['calendardata'],
1691
				'lastmodified' => $row['lastmodified'],
1692
				'etag'         => '"' . $row['etag'] . '"',
1693
				'size'         => (int)$row['size'],
1694
		];
1695
	}
1696
1697
	/**
1698
	 * Returns all scheduling objects for the inbox collection.
1699
	 *
1700
	 * These objects should be returned as an array. Every item in the array
1701
	 * should follow the same structure as returned from getSchedulingObject.
1702
	 *
1703
	 * The main difference is that 'calendardata' is optional.
1704
	 *
1705
	 * @param string $principalUri
1706
	 * @return array
1707
	 */
1708
	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...
1709
		$query = $this->db->getQueryBuilder();
1710
		$stmt = $query->select(['uri', 'calendardata', 'lastmodified', 'etag', 'size'])
1711
				->from('schedulingobjects')
1712
				->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
1713
				->execute();
1714
1715
		$result = [];
1716
		foreach($stmt->fetchAll(\PDO::FETCH_ASSOC) as $row) {
1717
			$result[] = [
1718
					'calendardata' => $row['calendardata'],
1719
					'uri'          => $row['uri'],
1720
					'lastmodified' => $row['lastmodified'],
1721
					'etag'         => '"' . $row['etag'] . '"',
1722
					'size'         => (int)$row['size'],
1723
			];
1724
		}
1725
1726
		return $result;
1727
	}
1728
1729
	/**
1730
	 * Deletes a scheduling object from the inbox collection.
1731
	 *
1732
	 * @param string $principalUri
1733
	 * @param string $objectUri
1734
	 * @return void
1735
	 */
1736
	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...
1737
		$query = $this->db->getQueryBuilder();
1738
		$query->delete('schedulingobjects')
1739
				->where($query->expr()->eq('principaluri', $query->createNamedParameter($principalUri)))
1740
				->andWhere($query->expr()->eq('uri', $query->createNamedParameter($objectUri)))
1741
				->execute();
1742
	}
1743
1744
	/**
1745
	 * Creates a new scheduling object. This should land in a users' inbox.
1746
	 *
1747
	 * @param string $principalUri
1748
	 * @param string $objectUri
1749
	 * @param string $objectData
1750
	 * @return void
1751
	 */
1752
	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...
1753
		$query = $this->db->getQueryBuilder();
1754
		$query->insert('schedulingobjects')
1755
			->values([
1756
				'principaluri' => $query->createNamedParameter($principalUri),
1757
				'calendardata' => $query->createNamedParameter($objectData),
1758
				'uri' => $query->createNamedParameter($objectUri),
1759
				'lastmodified' => $query->createNamedParameter(time()),
1760
				'etag' => $query->createNamedParameter(md5($objectData)),
1761
				'size' => $query->createNamedParameter(strlen($objectData))
1762
			])
1763
			->execute();
1764
	}
1765
1766
	/**
1767
	 * Adds a change record to the calendarchanges table.
1768
	 *
1769
	 * @param mixed $calendarId
1770
	 * @param string $objectUri
1771
	 * @param int $operation 1 = add, 2 = modify, 3 = delete.
1772
	 * @return void
1773
	 */
1774 View Code Duplication
	protected function addChange($calendarId, $objectUri, $operation) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1775
1776
		$stmt = $this->db->prepare('INSERT INTO `*PREFIX*calendarchanges` (`uri`, `synctoken`, `calendarid`, `operation`) SELECT ?, `synctoken`, ?, ? FROM `*PREFIX*calendars` WHERE `id` = ?');
1777
		$stmt->execute([
1778
			$objectUri,
1779
			$calendarId,
1780
			$operation,
1781
			$calendarId
1782
		]);
1783
		$stmt = $this->db->prepare('UPDATE `*PREFIX*calendars` SET `synctoken` = `synctoken` + 1 WHERE `id` = ?');
1784
		$stmt->execute([
1785
			$calendarId
1786
		]);
1787
1788
	}
1789
1790
	/**
1791
	 * Parses some information from calendar objects, used for optimized
1792
	 * calendar-queries.
1793
	 *
1794
	 * Returns an array with the following keys:
1795
	 *   * etag - An md5 checksum of the object without the quotes.
1796
	 *   * size - Size of the object in bytes
1797
	 *   * componentType - VEVENT, VTODO or VJOURNAL
1798
	 *   * firstOccurence
1799
	 *   * lastOccurence
1800
	 *   * uid - value of the UID property
1801
	 *
1802
	 * @param string $calendarData
1803
	 * @return array
1804
	 */
1805
	public function getDenormalizedData($calendarData) {
1806
1807
		$vObject = Reader::read($calendarData);
1808
		$componentType = null;
1809
		$component = null;
1810
		$firstOccurrence = null;
1811
		$lastOccurrence = null;
1812
		$uid = null;
1813
		$classification = self::CLASSIFICATION_PUBLIC;
1814
		foreach($vObject->getComponents() as $component) {
1815
			if ($component->name!=='VTIMEZONE') {
1816
				$componentType = $component->name;
1817
				$uid = (string)$component->UID;
1818
				break;
1819
			}
1820
		}
1821
		if (!$componentType) {
1822
			throw new \Sabre\DAV\Exception\BadRequest('Calendar objects must have a VJOURNAL, VEVENT or VTODO component');
1823
		}
1824
		if ($componentType === 'VEVENT' && $component->DTSTART) {
1825
			$firstOccurrence = $component->DTSTART->getDateTime()->getTimeStamp();
1826
			// Finding the last occurrence is a bit harder
1827
			if (!isset($component->RRULE)) {
1828
				if (isset($component->DTEND)) {
1829
					$lastOccurrence = $component->DTEND->getDateTime()->getTimeStamp();
1830
				} elseif (isset($component->DURATION)) {
1831
					$endDate = clone $component->DTSTART->getDateTime();
1832
					$endDate->add(DateTimeParser::parse($component->DURATION->getValue()));
1833
					$lastOccurrence = $endDate->getTimeStamp();
1834
				} elseif (!$component->DTSTART->hasTime()) {
1835
					$endDate = clone $component->DTSTART->getDateTime();
1836
					$endDate->modify('+1 day');
1837
					$lastOccurrence = $endDate->getTimeStamp();
1838
				} else {
1839
					$lastOccurrence = $firstOccurrence;
1840
				}
1841
			} else {
1842
				$it = new EventIterator($vObject, (string)$component->UID);
1843
				$maxDate = new \DateTime(self::MAX_DATE);
1844
				if ($it->isInfinite()) {
1845
					$lastOccurrence = $maxDate->getTimestamp();
1846
				} else {
1847
					$end = $it->getDtEnd();
1848
					while($it->valid() && $end < $maxDate) {
1849
						$end = $it->getDtEnd();
1850
						$it->next();
1851
1852
					}
1853
					$lastOccurrence = $end->getTimestamp();
1854
				}
1855
1856
			}
1857
		}
1858
1859
		if ($component->CLASS) {
1860
			$classification = CalDavBackend::CLASSIFICATION_PRIVATE;
1861
			switch ($component->CLASS->getValue()) {
1862
				case 'PUBLIC':
1863
					$classification = CalDavBackend::CLASSIFICATION_PUBLIC;
1864
					break;
1865
				case 'CONFIDENTIAL':
1866
					$classification = CalDavBackend::CLASSIFICATION_CONFIDENTIAL;
1867
					break;
1868
			}
1869
		}
1870
		return [
1871
			'etag' => md5($calendarData),
1872
			'size' => strlen($calendarData),
1873
			'componentType' => $componentType,
1874
			'firstOccurence' => is_null($firstOccurrence) ? null : max(0, $firstOccurrence),
1875
			'lastOccurence'  => $lastOccurrence,
1876
			'uid' => $uid,
1877
			'classification' => $classification
1878
		];
1879
1880
	}
1881
1882
	private function readBlob($cardData) {
1883
		if (is_resource($cardData)) {
1884
			return stream_get_contents($cardData);
1885
		}
1886
1887
		return $cardData;
1888
	}
1889
1890
	/**
1891
	 * @param IShareable $shareable
1892
	 * @param array $add
1893
	 * @param array $remove
1894
	 */
1895
	public function updateShares($shareable, $add, $remove) {
1896
		$calendarId = $shareable->getResourceId();
1897
		$this->dispatcher->dispatch('\OCA\DAV\CalDAV\CalDavBackend::updateShares', new GenericEvent(
1898
			'\OCA\DAV\CalDAV\CalDavBackend::updateShares',
1899
			[
1900
				'calendarId' => $calendarId,
1901
				'calendarData' => $this->getCalendarById($calendarId),
1902
				'shares' => $this->getShares($calendarId),
1903
				'add' => $add,
1904
				'remove' => $remove,
1905
			]));
1906
		$this->sharingBackend->updateShares($shareable, $add, $remove);
1907
	}
1908
1909
	/**
1910
	 * @param int $resourceId
1911
	 * @return array
1912
	 */
1913
	public function getShares($resourceId) {
1914
		return $this->sharingBackend->getShares($resourceId);
1915
	}
1916
1917
	/**
1918
	 * @param boolean $value
1919
	 * @param \OCA\DAV\CalDAV\Calendar $calendar
1920
	 * @return string|null
1921
	 */
1922
	public function setPublishStatus($value, $calendar) {
1923
		$query = $this->db->getQueryBuilder();
1924
		if ($value) {
1925
			$publicUri = $this->random->generate(16, ISecureRandom::CHAR_UPPER.ISecureRandom::CHAR_DIGITS);
1926
			$query->insert('dav_shares')
1927
				->values([
1928
					'principaluri' => $query->createNamedParameter($calendar->getPrincipalURI()),
1929
					'type' => $query->createNamedParameter('calendar'),
1930
					'access' => $query->createNamedParameter(self::ACCESS_PUBLIC),
1931
					'resourceid' => $query->createNamedParameter($calendar->getResourceId()),
1932
					'publicuri' => $query->createNamedParameter($publicUri)
1933
				]);
1934
			$query->execute();
1935
			return $publicUri;
1936
		}
1937
		$query->delete('dav_shares')
1938
			->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
1939
			->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)));
1940
		$query->execute();
1941
		return null;
1942
	}
1943
1944
	/**
1945
	 * @param \OCA\DAV\CalDAV\Calendar $calendar
1946
	 * @return mixed
1947
	 */
1948 View Code Duplication
	public function getPublishStatus($calendar) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
1949
		$query = $this->db->getQueryBuilder();
1950
		$result = $query->select('publicuri')
1951
			->from('dav_shares')
1952
			->where($query->expr()->eq('resourceid', $query->createNamedParameter($calendar->getResourceId())))
1953
			->andWhere($query->expr()->eq('access', $query->createNamedParameter(self::ACCESS_PUBLIC)))
1954
			->execute();
1955
1956
		$row = $result->fetch();
1957
		$result->closeCursor();
1958
		return $row ? reset($row) : false;
1959
	}
1960
1961
	/**
1962
	 * @param int $resourceId
1963
	 * @param array $acl
1964
	 * @return array
1965
	 */
1966
	public function applyShareAcl($resourceId, $acl) {
1967
		return $this->sharingBackend->applyShareAcl($resourceId, $acl);
1968
	}
1969
1970
1971
1972
	/**
1973
	 * update properties table
1974
	 *
1975
	 * @param int $calendarId
1976
	 * @param string $objectUri
1977
	 * @param string $calendarData
1978
	 */
1979
	public function updateProperties($calendarId, $objectUri, $calendarData) {
1980
		$objectId = $this->getCalendarObjectId($calendarId, $objectUri);
1981
1982
		try {
1983
			$vCalendar = $this->readCalendarData($calendarData);
1984
		} catch (\Exception $ex) {
1985
			return;
1986
		}
1987
1988
		$this->purgeProperties($calendarId, $objectId);
1989
1990
		$query = $this->db->getQueryBuilder();
1991
		$query->insert($this->dbObjectPropertiesTable)
1992
			->values(
1993
				[
1994
					'calendarid' => $query->createNamedParameter($calendarId),
1995
					'objectid' => $query->createNamedParameter($objectId),
1996
					'name' => $query->createParameter('name'),
1997
					'parameter' => $query->createParameter('parameter'),
1998
					'value' => $query->createParameter('value'),
1999
				]
2000
			);
2001
2002
		$indexComponents = ['VEVENT', 'VJOURNAL', 'VTODO'];
2003
		foreach ($vCalendar->getComponents() as $component) {
2004
			if (!in_array($component->name, $indexComponents)) {
2005
				continue;
2006
			}
2007
2008
			foreach ($component->children() as $property) {
2009
				if (in_array($property->name, self::$indexProperties)) {
2010
					$value = $property->getValue();
2011
					// is this a shitty db?
2012
					if ($this->db->supports4ByteText()) {
2013
						$value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value);
2014
					}
2015
					$value = substr($value, 0, 254);
2016
2017
					$query->setParameter('name', $property->name);
2018
					$query->setParameter('parameter', null);
2019
					$query->setParameter('value', $value);
2020
					$query->execute();
2021
				}
2022
2023
				if (in_array($property->name, array_keys(self::$indexParameters))) {
2024
					$parameters = $property->parameters();
2025
					$indexedParametersForProperty = self::$indexParameters[$property->name];
2026
2027
					foreach ($parameters as $key => $value) {
2028
						if (in_array($key, $indexedParametersForProperty)) {
2029
							// is this a shitty db?
2030
							if ($this->db->supports4ByteText()) {
2031
								$value = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $value);
2032
							}
2033
							$value = substr($value, 0, 254);
2034
2035
							$query->setParameter('name', $property->name);
2036
							$query->setParameter('parameter', substr($key, 0, 254));
2037
							$query->setParameter('value', substr($value, 0, 254));
2038
							$query->execute();
2039
						}
2040
					}
2041
				}
2042
			}
2043
		}
2044
	}
2045
2046
	/**
2047
	 * read VCalendar data into a VCalendar object
2048
	 *
2049
	 * @param string $objectData
2050
	 * @return VCalendar
2051
	 */
2052
	protected function readCalendarData($objectData) {
2053
		return Reader::read($objectData);
2054
	}
2055
2056
	/**
2057
	 * delete all properties from a given calendar object
2058
	 *
2059
	 * @param int $calendarId
2060
	 * @param int $objectId
2061
	 */
2062 View Code Duplication
	protected function purgeProperties($calendarId, $objectId) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2063
		$query = $this->db->getQueryBuilder();
2064
		$query->delete($this->dbObjectPropertiesTable)
2065
			->where($query->expr()->eq('objectid', $query->createNamedParameter($objectId)))
2066
			->andWhere($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)));
2067
		$query->execute();
2068
	}
2069
2070
	/**
2071
	 * get ID from a given calendar object
2072
	 *
2073
	 * @param int $calendarId
2074
	 * @param string $uri
2075
	 * @return int
2076
	 */
2077 View Code Duplication
	protected function getCalendarObjectId($calendarId, $uri) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2078
		$query = $this->db->getQueryBuilder();
2079
		$query->select('id')->from('calendarobjects')
2080
			->where($query->expr()->eq('uri', $query->createNamedParameter($uri)))
2081
			->andWhere($query->expr()->eq('calendarid', $query->createNamedParameter($calendarId)));
2082
2083
		$result = $query->execute();
2084
		$objectIds = $result->fetch();
2085
		$result->closeCursor();
2086
2087
		if (!isset($objectIds['id'])) {
2088
			throw new \InvalidArgumentException('Calendarobject does not exists: ' . $uri);
2089
		}
2090
2091
		return (int)$objectIds['id'];
2092
	}
2093
2094 View Code Duplication
	private function convertPrincipal($principalUri, $toV2) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2095
		if ($this->principalBackend->getPrincipalPrefix() === 'principals') {
2096
			list(, $name) = URLUtil::splitPath($principalUri);
2097
			if ($toV2 === true) {
2098
				return "principals/users/$name";
2099
			}
2100
			return "principals/$name";
2101
		}
2102
		return $principalUri;
2103
	}
2104
2105 View Code Duplication
	private function addOwnerPrincipal(&$calendarInfo) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2106
		$ownerPrincipalKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_OWNCLOUD . '}owner-principal';
2107
		$displaynameKey = '{' . \OCA\DAV\DAV\Sharing\Plugin::NS_NEXTCLOUD . '}owner-displayname';
2108
		if (isset($calendarInfo[$ownerPrincipalKey])) {
2109
			$uri = $calendarInfo[$ownerPrincipalKey];
2110
		} else {
2111
			$uri = $calendarInfo['principaluri'];
2112
		}
2113
2114
		$principalInformation = $this->principalBackend->getPrincipalByPath($uri);
2115
		if (isset($principalInformation['{DAV:}displayname'])) {
2116
			$calendarInfo[$displaynameKey] = $principalInformation['{DAV:}displayname'];
2117
		}
2118
	}
2119
}
2120