Completed
Push — master ( d842b2...8a1d3c )
by Lukas
17:12
created

CalDavBackend::addOwnerPrincipal()   A

Complexity

Conditions 3
Paths 4

Size

Total Lines 14
Code Lines 10

Duplication

Lines 14
Ratio 100 %

Importance

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