Completed
Push — master ( 6ca8ce...06e969 )
by Lukas
47:33 queued 38:57
created

CalDavBackend::getPublicCalendars()   C

Complexity

Conditions 8
Paths 33

Size

Total Lines 51
Code Lines 41

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
eloc 41
nc 33
nop 0
dl 0
loc 51
rs 6.5978
c 0
b 0
f 0

How to fix   Long Method   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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