Passed
Push — master ( f0dd71...c56a27 )
by Christoph
11:49 queued 12s
created
apps/dav/lib/CalDAV/Reminder/ReminderService.php 1 patch
Indentation   +734 added lines, -734 removed lines patch added patch discarded remove patch
@@ -45,738 +45,738 @@
 block discarded – undo
45 45
 
46 46
 class ReminderService {
47 47
 
48
-	/** @var Backend */
49
-	private $backend;
50
-
51
-	/** @var NotificationProviderManager */
52
-	private $notificationProviderManager;
53
-
54
-	/** @var IUserManager */
55
-	private $userManager;
56
-
57
-	/** @var IGroupManager */
58
-	private $groupManager;
59
-
60
-	/** @var CalDavBackend */
61
-	private $caldavBackend;
62
-
63
-	/** @var ITimeFactory */
64
-	private $timeFactory;
65
-
66
-	public const REMINDER_TYPE_EMAIL = 'EMAIL';
67
-	public const REMINDER_TYPE_DISPLAY = 'DISPLAY';
68
-	public const REMINDER_TYPE_AUDIO = 'AUDIO';
69
-
70
-	/**
71
-	 * @var String[]
72
-	 *
73
-	 * Official RFC5545 reminder types
74
-	 */
75
-	public const REMINDER_TYPES = [
76
-		self::REMINDER_TYPE_EMAIL,
77
-		self::REMINDER_TYPE_DISPLAY,
78
-		self::REMINDER_TYPE_AUDIO
79
-	];
80
-
81
-	/**
82
-	 * ReminderService constructor.
83
-	 *
84
-	 * @param Backend $backend
85
-	 * @param NotificationProviderManager $notificationProviderManager
86
-	 * @param IUserManager $userManager
87
-	 * @param IGroupManager $groupManager
88
-	 * @param CalDavBackend $caldavBackend
89
-	 * @param ITimeFactory $timeFactory
90
-	 */
91
-	public function __construct(Backend $backend,
92
-								NotificationProviderManager $notificationProviderManager,
93
-								IUserManager $userManager,
94
-								IGroupManager $groupManager,
95
-								CalDavBackend $caldavBackend,
96
-								ITimeFactory $timeFactory) {
97
-		$this->backend = $backend;
98
-		$this->notificationProviderManager = $notificationProviderManager;
99
-		$this->userManager = $userManager;
100
-		$this->groupManager = $groupManager;
101
-		$this->caldavBackend = $caldavBackend;
102
-		$this->timeFactory = $timeFactory;
103
-	}
104
-
105
-	/**
106
-	 * Process reminders to activate
107
-	 *
108
-	 * @throws NotificationProvider\ProviderNotAvailableException
109
-	 * @throws NotificationTypeDoesNotExistException
110
-	 */
111
-	public function processReminders():void {
112
-		$reminders = $this->backend->getRemindersToProcess();
113
-
114
-		foreach($reminders as $reminder) {
115
-			$calendarData = is_resource($reminder['calendardata'])
116
-				? stream_get_contents($reminder['calendardata'])
117
-				: $reminder['calendardata'];
118
-
119
-			$vcalendar = $this->parseCalendarData($calendarData);
120
-			if (!$vcalendar) {
121
-				$this->backend->removeReminder($reminder['id']);
122
-				continue;
123
-			}
124
-
125
-			$vevent = $this->getVEventByRecurrenceId($vcalendar, $reminder['recurrence_id'], $reminder['is_recurrence_exception']);
126
-			if (!$vevent) {
127
-				$this->backend->removeReminder($reminder['id']);
128
-				continue;
129
-			}
130
-
131
-			if ($this->wasEventCancelled($vevent)) {
132
-				$this->deleteOrProcessNext($reminder, $vevent);
133
-				continue;
134
-			}
135
-
136
-			if (!$this->notificationProviderManager->hasProvider($reminder['type'])) {
137
-				$this->deleteOrProcessNext($reminder, $vevent);
138
-				continue;
139
-			}
140
-
141
-			$users = $this->getAllUsersWithWriteAccessToCalendar($reminder['calendar_id']);
142
-			$user = $this->getUserFromPrincipalURI($reminder['principaluri']);
143
-			if ($user) {
144
-				$users[] = $user;
145
-			}
146
-
147
-			$notificationProvider = $this->notificationProviderManager->getProvider($reminder['type']);
148
-			$notificationProvider->send($vevent, $reminder['displayname'], $users);
149
-
150
-			$this->deleteOrProcessNext($reminder, $vevent);
151
-		}
152
-	}
153
-
154
-	/**
155
-	 * @param string $action
156
-	 * @param array $objectData
157
-	 * @throws VObject\InvalidDataException
158
-	 */
159
-	public function onTouchCalendarObject(string $action,
160
-										  array $objectData):void {
161
-		// We only support VEvents for now
162
-		if (strcasecmp($objectData['component'], 'vevent') !== 0) {
163
-			return;
164
-		}
165
-
166
-		switch($action) {
167
-			case '\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject':
168
-				$this->onCalendarObjectCreate($objectData);
169
-				break;
170
-
171
-			case '\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject':
172
-				$this->onCalendarObjectEdit($objectData);
173
-				break;
174
-
175
-			case '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject':
176
-				$this->onCalendarObjectDelete($objectData);
177
-				break;
178
-
179
-			default:
180
-				break;
181
-		}
182
-	}
183
-
184
-	/**
185
-	 * @param array $objectData
186
-	 */
187
-	private function onCalendarObjectCreate(array $objectData):void {
188
-		$calendarData = is_resource($objectData['calendardata'])
189
-			? stream_get_contents($objectData['calendardata'])
190
-			: $objectData['calendardata'];
191
-
192
-		/** @var VObject\Component\VCalendar $vcalendar */
193
-		$vcalendar = $this->parseCalendarData($calendarData);
194
-		if (!$vcalendar) {
195
-			return;
196
-		}
197
-
198
-		$vevents = $this->getAllVEventsFromVCalendar($vcalendar);
199
-		if (count($vevents) === 0) {
200
-			return;
201
-		}
202
-
203
-		$uid = (string) $vevents[0]->UID;
204
-		$recurrenceExceptions = $this->getRecurrenceExceptionFromListOfVEvents($vevents);
205
-		$masterItem = $this->getMasterItemFromListOfVEvents($vevents);
206
-		$now = $this->timeFactory->getDateTime();
207
-		$isRecurring = $masterItem ? $this->isRecurring($masterItem) : false;
208
-
209
-		foreach($recurrenceExceptions as $recurrenceException) {
210
-			$eventHash = $this->getEventHash($recurrenceException);
211
-
212
-			if (!isset($recurrenceException->VALARM)) {
213
-				continue;
214
-			}
215
-
216
-			foreach($recurrenceException->VALARM as $valarm) {
217
-				/** @var VAlarm $valarm */
218
-				$alarmHash = $this->getAlarmHash($valarm);
219
-				$triggerTime = $valarm->getEffectiveTriggerTime();
220
-				$diff = $now->diff($triggerTime);
221
-				if ($diff->invert === 1) {
222
-					continue;
223
-				}
224
-
225
-				$alarms = $this->getRemindersForVAlarm($valarm, $objectData,
226
-					$eventHash, $alarmHash, true, true);
227
-				$this->writeRemindersToDatabase($alarms);
228
-			}
229
-		}
230
-
231
-		if ($masterItem) {
232
-			$processedAlarms = [];
233
-			$masterAlarms = [];
234
-			$masterHash = $this->getEventHash($masterItem);
235
-
236
-			if (!isset($masterItem->VALARM)) {
237
-				return;
238
-			}
239
-
240
-			foreach($masterItem->VALARM as $valarm) {
241
-				$masterAlarms[] = $this->getAlarmHash($valarm);
242
-			}
243
-
244
-			try {
245
-				$iterator = new EventIterator($vevents, $uid);
246
-			} catch (NoInstancesException $e) {
247
-				// This event is recurring, but it doesn't have a single
248
-				// instance. We are skipping this event from the output
249
-				// entirely.
250
-				return;
251
-			}
252
-
253
-			while($iterator->valid() && count($processedAlarms) < count($masterAlarms)) {
254
-				$event = $iterator->getEventObject();
255
-
256
-				// Recurrence-exceptions are handled separately, so just ignore them here
257
-				if (\in_array($event, $recurrenceExceptions, true)) {
258
-					$iterator->next();
259
-					continue;
260
-				}
261
-
262
-				foreach($event->VALARM as $valarm) {
263
-					/** @var VAlarm $valarm */
264
-					$alarmHash = $this->getAlarmHash($valarm);
265
-					if (\in_array($alarmHash, $processedAlarms, true)) {
266
-						continue;
267
-					}
268
-
269
-					if (!\in_array((string) $valarm->ACTION, self::REMINDER_TYPES, true)) {
270
-						// Action allows x-name, we don't insert reminders
271
-						// into the database if they are not standard
272
-						$processedAlarms[] = $alarmHash;
273
-						continue;
274
-					}
275
-
276
-					$triggerTime = $valarm->getEffectiveTriggerTime();
277
-
278
-					// If effective trigger time is in the past
279
-					// just skip and generate for next event
280
-					$diff = $now->diff($triggerTime);
281
-					if ($diff->invert === 1) {
282
-						// If an absolute alarm is in the past,
283
-						// just add it to processedAlarms, so
284
-						// we don't extend till eternity
285
-						if (!$this->isAlarmRelative($valarm)) {
286
-							$processedAlarms[] = $alarmHash;
287
-						}
288
-
289
-						continue;
290
-					}
291
-
292
-					$alarms = $this->getRemindersForVAlarm($valarm, $objectData, $masterHash, $alarmHash, $isRecurring, false);
293
-					$this->writeRemindersToDatabase($alarms);
294
-					$processedAlarms[] = $alarmHash;
295
-				}
296
-
297
-				$iterator->next();
298
-			}
299
-		}
300
-	}
301
-
302
-	/**
303
-	 * @param array $objectData
304
-	 */
305
-	private function onCalendarObjectEdit(array $objectData):void {
306
-		// TODO - this can be vastly improved
307
-		//  - get cached reminders
308
-		//  - ...
309
-
310
-		$this->onCalendarObjectDelete($objectData);
311
-		$this->onCalendarObjectCreate($objectData);
312
-	}
313
-
314
-	/**
315
-	 * @param array $objectData
316
-	 */
317
-	private function onCalendarObjectDelete(array $objectData):void {
318
-		$this->backend->cleanRemindersForEvent((int) $objectData['id']);
319
-	}
320
-
321
-	/**
322
-	 * @param VAlarm $valarm
323
-	 * @param array $objectData
324
-	 * @param string|null $eventHash
325
-	 * @param string|null $alarmHash
326
-	 * @param bool $isRecurring
327
-	 * @param bool $isRecurrenceException
328
-	 * @return array
329
-	 */
330
-	private function getRemindersForVAlarm(VAlarm $valarm,
331
-										   array $objectData,
332
-										   string $eventHash=null,
333
-										   string $alarmHash=null,
334
-										   bool $isRecurring=false,
335
-										   bool $isRecurrenceException=false):array {
336
-		if ($eventHash === null) {
337
-			$eventHash = $this->getEventHash($valarm->parent);
338
-		}
339
-		if ($alarmHash === null) {
340
-			$alarmHash = $this->getAlarmHash($valarm);
341
-		}
342
-
343
-		$recurrenceId = $this->getEffectiveRecurrenceIdOfVEvent($valarm->parent);
344
-		$isRelative = $this->isAlarmRelative($valarm);
345
-		/** @var DateTimeImmutable $notificationDate */
346
-		$notificationDate = $valarm->getEffectiveTriggerTime();
347
-		$clonedNotificationDate = new \DateTime('now', $notificationDate->getTimezone());
348
-		$clonedNotificationDate->setTimestamp($notificationDate->getTimestamp());
349
-
350
-		$alarms = [];
351
-
352
-		$alarms[] = [
353
-			'calendar_id' => $objectData['calendarid'],
354
-			'object_id' => $objectData['id'],
355
-			'uid' => (string) $valarm->parent->UID,
356
-			'is_recurring' => $isRecurring,
357
-			'recurrence_id' => $recurrenceId,
358
-			'is_recurrence_exception' => $isRecurrenceException,
359
-			'event_hash' => $eventHash,
360
-			'alarm_hash' => $alarmHash,
361
-			'type' => (string) $valarm->ACTION,
362
-			'is_relative' => $isRelative,
363
-			'notification_date' => $notificationDate->getTimestamp(),
364
-			'is_repeat_based' => false,
365
-		];
366
-
367
-		$repeat = isset($valarm->REPEAT) ? (int) $valarm->REPEAT->getValue() : 0;
368
-		for($i = 0; $i < $repeat; $i++) {
369
-			if ($valarm->DURATION === null) {
370
-				continue;
371
-			}
372
-
373
-			$clonedNotificationDate->add($valarm->DURATION->getDateInterval());
374
-			$alarms[] = [
375
-				'calendar_id' => $objectData['calendarid'],
376
-				'object_id' => $objectData['id'],
377
-				'uid' => (string) $valarm->parent->UID,
378
-				'is_recurring' => $isRecurring,
379
-				'recurrence_id' => $recurrenceId,
380
-				'is_recurrence_exception' => $isRecurrenceException,
381
-				'event_hash' => $eventHash,
382
-				'alarm_hash' => $alarmHash,
383
-				'type' => (string) $valarm->ACTION,
384
-				'is_relative' => $isRelative,
385
-				'notification_date' => $clonedNotificationDate->getTimestamp(),
386
-				'is_repeat_based' => true,
387
-			];
388
-		}
389
-
390
-		return $alarms;
391
-	}
392
-
393
-	/**
394
-	 * @param array $reminders
395
-	 */
396
-	private function writeRemindersToDatabase(array $reminders): void {
397
-		foreach($reminders as $reminder) {
398
-			$this->backend->insertReminder(
399
-				(int) $reminder['calendar_id'],
400
-				(int) $reminder['object_id'],
401
-				$reminder['uid'],
402
-				$reminder['is_recurring'],
403
-				(int) $reminder['recurrence_id'],
404
-				$reminder['is_recurrence_exception'],
405
-				$reminder['event_hash'],
406
-				$reminder['alarm_hash'],
407
-				$reminder['type'],
408
-				$reminder['is_relative'],
409
-				(int) $reminder['notification_date'],
410
-				$reminder['is_repeat_based']
411
-			);
412
-		}
413
-	}
414
-
415
-	/**
416
-	 * @param array $reminder
417
-	 * @param VEvent $vevent
418
-	 */
419
-	private function deleteOrProcessNext(array $reminder,
420
-										 VObject\Component\VEvent $vevent):void {
421
-		if ($reminder['is_repeat_based'] ||
422
-			!$reminder['is_recurring'] ||
423
-			!$reminder['is_relative'] ||
424
-			$reminder['is_recurrence_exception']) {
425
-
426
-			$this->backend->removeReminder($reminder['id']);
427
-			return;
428
-		}
429
-
430
-		$vevents = $this->getAllVEventsFromVCalendar($vevent->parent);
431
-		$recurrenceExceptions = $this->getRecurrenceExceptionFromListOfVEvents($vevents);
432
-		$now = $this->timeFactory->getDateTime();
433
-
434
-		try {
435
-			$iterator = new EventIterator($vevents, $reminder['uid']);
436
-		} catch (NoInstancesException $e) {
437
-			// This event is recurring, but it doesn't have a single
438
-			// instance. We are skipping this event from the output
439
-			// entirely.
440
-			return;
441
-		}
442
-
443
-		while($iterator->valid()) {
444
-			$event = $iterator->getEventObject();
445
-
446
-			// Recurrence-exceptions are handled separately, so just ignore them here
447
-			if (\in_array($event, $recurrenceExceptions, true)) {
448
-				$iterator->next();
449
-				continue;
450
-			}
451
-
452
-			$recurrenceId = $this->getEffectiveRecurrenceIdOfVEvent($event);
453
-			if ($reminder['recurrence_id'] >= $recurrenceId) {
454
-				$iterator->next();
455
-				continue;
456
-			}
457
-
458
-			foreach($event->VALARM as $valarm) {
459
-				/** @var VAlarm $valarm */
460
-				$alarmHash = $this->getAlarmHash($valarm);
461
-				if ($alarmHash !== $reminder['alarm_hash']) {
462
-					continue;
463
-				}
464
-
465
-				$triggerTime = $valarm->getEffectiveTriggerTime();
466
-
467
-				// If effective trigger time is in the past
468
-				// just skip and generate for next event
469
-				$diff = $now->diff($triggerTime);
470
-				if ($diff->invert === 1) {
471
-					continue;
472
-				}
473
-
474
-				$this->backend->removeReminder($reminder['id']);
475
-				$alarms = $this->getRemindersForVAlarm($valarm, [
476
-					'calendarid' => $reminder['calendar_id'],
477
-					'id' => $reminder['object_id'],
478
-				], $reminder['event_hash'], $alarmHash, true, false);
479
-				$this->writeRemindersToDatabase($alarms);
480
-
481
-				// Abort generating reminders after creating one successfully
482
-				return;
483
-			}
484
-
485
-			$iterator->next();
486
-		}
487
-
488
-		$this->backend->removeReminder($reminder['id']);
489
-	}
490
-
491
-	/**
492
-	 * @param int $calendarId
493
-	 * @return IUser[]
494
-	 */
495
-	private function getAllUsersWithWriteAccessToCalendar(int $calendarId):array {
496
-		$shares = $this->caldavBackend->getShares($calendarId);
497
-
498
-		$users = [];
499
-		$userIds = [];
500
-		$groups = [];
501
-		foreach ($shares as $share) {
502
-			// Only consider writable shares
503
-			if ($share['readOnly']) {
504
-				continue;
505
-			}
506
-
507
-			$principal = explode('/', $share['{http://owncloud.org/ns}principal']);
508
-			if ($principal[1] === 'users') {
509
-				$user = $this->userManager->get($principal[2]);
510
-				if ($user) {
511
-					$users[] = $user;
512
-					$userIds[] = $principal[2];
513
-				}
514
-			} else if ($principal[1] === 'groups') {
515
-				$groups[] = $principal[2];
516
-			}
517
-		}
518
-
519
-		foreach ($groups as $gid) {
520
-			$group = $this->groupManager->get($gid);
521
-			if ($group instanceof IGroup) {
522
-				foreach ($group->getUsers() as $user) {
523
-					if (!\in_array($user->getUID(), $userIds, true)) {
524
-						$users[] = $user;
525
-						$userIds[] = $user->getUID();
526
-					}
527
-				}
528
-			}
529
-		}
530
-
531
-		return $users;
532
-	}
533
-
534
-	/**
535
-	 * Gets a hash of the event.
536
-	 * If the hash changes, we have to update all relative alarms.
537
-	 *
538
-	 * @param VEvent $vevent
539
-	 * @return string
540
-	 */
541
-	private function getEventHash(VEvent $vevent):string {
542
-		$properties = [
543
-			(string) $vevent->DTSTART->serialize(),
544
-		];
545
-
546
-		if ($vevent->DTEND) {
547
-			$properties[] = (string) $vevent->DTEND->serialize();
548
-		}
549
-		if ($vevent->DURATION) {
550
-			$properties[] = (string) $vevent->DURATION->serialize();
551
-		}
552
-		if ($vevent->{'RECURRENCE-ID'}) {
553
-			$properties[] = (string) $vevent->{'RECURRENCE-ID'}->serialize();
554
-		}
555
-		if ($vevent->RRULE) {
556
-			$properties[] = (string) $vevent->RRULE->serialize();
557
-		}
558
-		if ($vevent->EXDATE) {
559
-			$properties[] = (string) $vevent->EXDATE->serialize();
560
-		}
561
-		if ($vevent->RDATE) {
562
-			$properties[] = (string) $vevent->RDATE->serialize();
563
-		}
564
-
565
-		return md5(implode('::', $properties));
566
-	}
567
-
568
-	/**
569
-	 * Gets a hash of the alarm.
570
-	 * If the hash changes, we have to update oc_dav_reminders.
571
-	 *
572
-	 * @param VAlarm $valarm
573
-	 * @return string
574
-	 */
575
-	private function getAlarmHash(VAlarm $valarm):string {
576
-		$properties = [
577
-			(string) $valarm->ACTION->serialize(),
578
-			(string) $valarm->TRIGGER->serialize(),
579
-		];
580
-
581
-		if ($valarm->DURATION) {
582
-			$properties[] = (string) $valarm->DURATION->serialize();
583
-		}
584
-		if ($valarm->REPEAT) {
585
-			$properties[] = (string) $valarm->REPEAT->serialize();
586
-		}
587
-
588
-		return md5(implode('::', $properties));
589
-	}
590
-
591
-	/**
592
-	 * @param VObject\Component\VCalendar $vcalendar
593
-	 * @param int $recurrenceId
594
-	 * @param bool $isRecurrenceException
595
-	 * @return VEvent|null
596
-	 */
597
-	private function getVEventByRecurrenceId(VObject\Component\VCalendar $vcalendar,
598
-											 int $recurrenceId,
599
-											 bool $isRecurrenceException):?VEvent {
600
-		$vevents = $this->getAllVEventsFromVCalendar($vcalendar);
601
-		if (count($vevents) === 0) {
602
-			return null;
603
-		}
604
-
605
-		$uid = (string) $vevents[0]->UID;
606
-		$recurrenceExceptions = $this->getRecurrenceExceptionFromListOfVEvents($vevents);
607
-		$masterItem = $this->getMasterItemFromListOfVEvents($vevents);
608
-
609
-		// Handle recurrence-exceptions first, because recurrence-expansion is expensive
610
-		if ($isRecurrenceException) {
611
-			foreach($recurrenceExceptions as $recurrenceException) {
612
-				if ($this->getEffectiveRecurrenceIdOfVEvent($recurrenceException) === $recurrenceId) {
613
-					return $recurrenceException;
614
-				}
615
-			}
616
-
617
-			return null;
618
-		}
619
-
620
-		if ($masterItem) {
621
-			try {
622
-				$iterator = new EventIterator($vevents, $uid);
623
-			} catch (NoInstancesException $e) {
624
-				// This event is recurring, but it doesn't have a single
625
-				// instance. We are skipping this event from the output
626
-				// entirely.
627
-				return null;
628
-			}
629
-
630
-			while ($iterator->valid()) {
631
-				$event = $iterator->getEventObject();
632
-
633
-				// Recurrence-exceptions are handled separately, so just ignore them here
634
-				if (\in_array($event, $recurrenceExceptions, true)) {
635
-					$iterator->next();
636
-					continue;
637
-				}
638
-
639
-				if ($this->getEffectiveRecurrenceIdOfVEvent($event) === $recurrenceId) {
640
-					return $event;
641
-				}
642
-
643
-				$iterator->next();
644
-			}
645
-		}
646
-
647
-		return null;
648
-	}
649
-
650
-	/**
651
-	 * @param VEvent $vevent
652
-	 * @return string
653
-	 */
654
-	private function getStatusOfEvent(VEvent $vevent):string {
655
-		if ($vevent->STATUS) {
656
-			return (string) $vevent->STATUS;
657
-		}
658
-
659
-		// Doesn't say so in the standard,
660
-		// but we consider events without a status
661
-		// to be confirmed
662
-		return 'CONFIRMED';
663
-	}
664
-
665
-	/**
666
-	 * @param VObject\Component\VEvent $vevent
667
-	 * @return bool
668
-	 */
669
-	private function wasEventCancelled(VObject\Component\VEvent $vevent):bool {
670
-		return $this->getStatusOfEvent($vevent) === 'CANCELLED';
671
-	}
672
-
673
-	/**
674
-	 * @param string $calendarData
675
-	 * @return VObject\Component\VCalendar|null
676
-	 */
677
-	private function parseCalendarData(string $calendarData):?VObject\Component\VCalendar {
678
-		try {
679
-			return VObject\Reader::read($calendarData,
680
-				VObject\Reader::OPTION_FORGIVING);
681
-		} catch(ParseException $ex) {
682
-			return null;
683
-		}
684
-	}
685
-
686
-	/**
687
-	 * @param string $principalUri
688
-	 * @return IUser|null
689
-	 */
690
-	private function getUserFromPrincipalURI(string $principalUri):?IUser {
691
-		if (!$principalUri) {
692
-			return null;
693
-		}
694
-
695
-		if (stripos($principalUri, 'principals/users/') !== 0) {
696
-			return null;
697
-		}
698
-
699
-		$userId = substr($principalUri, 17);
700
-		return $this->userManager->get($userId);
701
-	}
702
-
703
-	/**
704
-	 * @param VObject\Component\VCalendar $vcalendar
705
-	 * @return VObject\Component\VEvent[]
706
-	 */
707
-	private function getAllVEventsFromVCalendar(VObject\Component\VCalendar $vcalendar):array {
708
-		$vevents = [];
709
-
710
-		foreach($vcalendar->children() as $child) {
711
-			if (!($child instanceof VObject\Component)) {
712
-				continue;
713
-			}
714
-
715
-			if ($child->name !== 'VEVENT') {
716
-				continue;
717
-			}
718
-
719
-			$vevents[] = $child;
720
-		}
721
-
722
-		return $vevents;
723
-	}
724
-
725
-	/**
726
-	 * @param array $vevents
727
-	 * @return VObject\Component\VEvent[]
728
-	 */
729
-	private function getRecurrenceExceptionFromListOfVEvents(array $vevents):array {
730
-		return array_values(array_filter($vevents, function (VEvent $vevent) {
731
-			return $vevent->{'RECURRENCE-ID'} !== null;
732
-		}));
733
-	}
734
-
735
-	/**
736
-	 * @param array $vevents
737
-	 * @return VEvent|null
738
-	 */
739
-	private function getMasterItemFromListOfVEvents(array $vevents):?VEvent {
740
-		$elements = array_values(array_filter($vevents, function (VEvent $vevent) {
741
-			return $vevent->{'RECURRENCE-ID'} === null;
742
-		}));
743
-
744
-		if (count($elements) === 0) {
745
-			return null;
746
-		}
747
-		if (count($elements) > 1) {
748
-			throw new \TypeError('Multiple master objects');
749
-		}
750
-
751
-		return $elements[0];
752
-	}
753
-
754
-	/**
755
-	 * @param VAlarm $valarm
756
-	 * @return bool
757
-	 */
758
-	private function isAlarmRelative(VAlarm $valarm):bool {
759
-		$trigger = $valarm->TRIGGER;
760
-		return $trigger instanceof VObject\Property\ICalendar\Duration;
761
-	}
762
-
763
-	/**
764
-	 * @param VEvent $vevent
765
-	 * @return int
766
-	 */
767
-	private function getEffectiveRecurrenceIdOfVEvent(VEvent $vevent):int {
768
-		if (isset($vevent->{'RECURRENCE-ID'})) {
769
-			return $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimestamp();
770
-		}
771
-
772
-		return $vevent->DTSTART->getDateTime()->getTimestamp();
773
-	}
774
-
775
-	/**
776
-	 * @param VEvent $vevent
777
-	 * @return bool
778
-	 */
779
-	private function isRecurring(VEvent $vevent):bool {
780
-		return isset($vevent->RRULE) || isset($vevent->RDATE);
781
-	}
48
+    /** @var Backend */
49
+    private $backend;
50
+
51
+    /** @var NotificationProviderManager */
52
+    private $notificationProviderManager;
53
+
54
+    /** @var IUserManager */
55
+    private $userManager;
56
+
57
+    /** @var IGroupManager */
58
+    private $groupManager;
59
+
60
+    /** @var CalDavBackend */
61
+    private $caldavBackend;
62
+
63
+    /** @var ITimeFactory */
64
+    private $timeFactory;
65
+
66
+    public const REMINDER_TYPE_EMAIL = 'EMAIL';
67
+    public const REMINDER_TYPE_DISPLAY = 'DISPLAY';
68
+    public const REMINDER_TYPE_AUDIO = 'AUDIO';
69
+
70
+    /**
71
+     * @var String[]
72
+     *
73
+     * Official RFC5545 reminder types
74
+     */
75
+    public const REMINDER_TYPES = [
76
+        self::REMINDER_TYPE_EMAIL,
77
+        self::REMINDER_TYPE_DISPLAY,
78
+        self::REMINDER_TYPE_AUDIO
79
+    ];
80
+
81
+    /**
82
+     * ReminderService constructor.
83
+     *
84
+     * @param Backend $backend
85
+     * @param NotificationProviderManager $notificationProviderManager
86
+     * @param IUserManager $userManager
87
+     * @param IGroupManager $groupManager
88
+     * @param CalDavBackend $caldavBackend
89
+     * @param ITimeFactory $timeFactory
90
+     */
91
+    public function __construct(Backend $backend,
92
+                                NotificationProviderManager $notificationProviderManager,
93
+                                IUserManager $userManager,
94
+                                IGroupManager $groupManager,
95
+                                CalDavBackend $caldavBackend,
96
+                                ITimeFactory $timeFactory) {
97
+        $this->backend = $backend;
98
+        $this->notificationProviderManager = $notificationProviderManager;
99
+        $this->userManager = $userManager;
100
+        $this->groupManager = $groupManager;
101
+        $this->caldavBackend = $caldavBackend;
102
+        $this->timeFactory = $timeFactory;
103
+    }
104
+
105
+    /**
106
+     * Process reminders to activate
107
+     *
108
+     * @throws NotificationProvider\ProviderNotAvailableException
109
+     * @throws NotificationTypeDoesNotExistException
110
+     */
111
+    public function processReminders():void {
112
+        $reminders = $this->backend->getRemindersToProcess();
113
+
114
+        foreach($reminders as $reminder) {
115
+            $calendarData = is_resource($reminder['calendardata'])
116
+                ? stream_get_contents($reminder['calendardata'])
117
+                : $reminder['calendardata'];
118
+
119
+            $vcalendar = $this->parseCalendarData($calendarData);
120
+            if (!$vcalendar) {
121
+                $this->backend->removeReminder($reminder['id']);
122
+                continue;
123
+            }
124
+
125
+            $vevent = $this->getVEventByRecurrenceId($vcalendar, $reminder['recurrence_id'], $reminder['is_recurrence_exception']);
126
+            if (!$vevent) {
127
+                $this->backend->removeReminder($reminder['id']);
128
+                continue;
129
+            }
130
+
131
+            if ($this->wasEventCancelled($vevent)) {
132
+                $this->deleteOrProcessNext($reminder, $vevent);
133
+                continue;
134
+            }
135
+
136
+            if (!$this->notificationProviderManager->hasProvider($reminder['type'])) {
137
+                $this->deleteOrProcessNext($reminder, $vevent);
138
+                continue;
139
+            }
140
+
141
+            $users = $this->getAllUsersWithWriteAccessToCalendar($reminder['calendar_id']);
142
+            $user = $this->getUserFromPrincipalURI($reminder['principaluri']);
143
+            if ($user) {
144
+                $users[] = $user;
145
+            }
146
+
147
+            $notificationProvider = $this->notificationProviderManager->getProvider($reminder['type']);
148
+            $notificationProvider->send($vevent, $reminder['displayname'], $users);
149
+
150
+            $this->deleteOrProcessNext($reminder, $vevent);
151
+        }
152
+    }
153
+
154
+    /**
155
+     * @param string $action
156
+     * @param array $objectData
157
+     * @throws VObject\InvalidDataException
158
+     */
159
+    public function onTouchCalendarObject(string $action,
160
+                                            array $objectData):void {
161
+        // We only support VEvents for now
162
+        if (strcasecmp($objectData['component'], 'vevent') !== 0) {
163
+            return;
164
+        }
165
+
166
+        switch($action) {
167
+            case '\OCA\DAV\CalDAV\CalDavBackend::createCalendarObject':
168
+                $this->onCalendarObjectCreate($objectData);
169
+                break;
170
+
171
+            case '\OCA\DAV\CalDAV\CalDavBackend::updateCalendarObject':
172
+                $this->onCalendarObjectEdit($objectData);
173
+                break;
174
+
175
+            case '\OCA\DAV\CalDAV\CalDavBackend::deleteCalendarObject':
176
+                $this->onCalendarObjectDelete($objectData);
177
+                break;
178
+
179
+            default:
180
+                break;
181
+        }
182
+    }
183
+
184
+    /**
185
+     * @param array $objectData
186
+     */
187
+    private function onCalendarObjectCreate(array $objectData):void {
188
+        $calendarData = is_resource($objectData['calendardata'])
189
+            ? stream_get_contents($objectData['calendardata'])
190
+            : $objectData['calendardata'];
191
+
192
+        /** @var VObject\Component\VCalendar $vcalendar */
193
+        $vcalendar = $this->parseCalendarData($calendarData);
194
+        if (!$vcalendar) {
195
+            return;
196
+        }
197
+
198
+        $vevents = $this->getAllVEventsFromVCalendar($vcalendar);
199
+        if (count($vevents) === 0) {
200
+            return;
201
+        }
202
+
203
+        $uid = (string) $vevents[0]->UID;
204
+        $recurrenceExceptions = $this->getRecurrenceExceptionFromListOfVEvents($vevents);
205
+        $masterItem = $this->getMasterItemFromListOfVEvents($vevents);
206
+        $now = $this->timeFactory->getDateTime();
207
+        $isRecurring = $masterItem ? $this->isRecurring($masterItem) : false;
208
+
209
+        foreach($recurrenceExceptions as $recurrenceException) {
210
+            $eventHash = $this->getEventHash($recurrenceException);
211
+
212
+            if (!isset($recurrenceException->VALARM)) {
213
+                continue;
214
+            }
215
+
216
+            foreach($recurrenceException->VALARM as $valarm) {
217
+                /** @var VAlarm $valarm */
218
+                $alarmHash = $this->getAlarmHash($valarm);
219
+                $triggerTime = $valarm->getEffectiveTriggerTime();
220
+                $diff = $now->diff($triggerTime);
221
+                if ($diff->invert === 1) {
222
+                    continue;
223
+                }
224
+
225
+                $alarms = $this->getRemindersForVAlarm($valarm, $objectData,
226
+                    $eventHash, $alarmHash, true, true);
227
+                $this->writeRemindersToDatabase($alarms);
228
+            }
229
+        }
230
+
231
+        if ($masterItem) {
232
+            $processedAlarms = [];
233
+            $masterAlarms = [];
234
+            $masterHash = $this->getEventHash($masterItem);
235
+
236
+            if (!isset($masterItem->VALARM)) {
237
+                return;
238
+            }
239
+
240
+            foreach($masterItem->VALARM as $valarm) {
241
+                $masterAlarms[] = $this->getAlarmHash($valarm);
242
+            }
243
+
244
+            try {
245
+                $iterator = new EventIterator($vevents, $uid);
246
+            } catch (NoInstancesException $e) {
247
+                // This event is recurring, but it doesn't have a single
248
+                // instance. We are skipping this event from the output
249
+                // entirely.
250
+                return;
251
+            }
252
+
253
+            while($iterator->valid() && count($processedAlarms) < count($masterAlarms)) {
254
+                $event = $iterator->getEventObject();
255
+
256
+                // Recurrence-exceptions are handled separately, so just ignore them here
257
+                if (\in_array($event, $recurrenceExceptions, true)) {
258
+                    $iterator->next();
259
+                    continue;
260
+                }
261
+
262
+                foreach($event->VALARM as $valarm) {
263
+                    /** @var VAlarm $valarm */
264
+                    $alarmHash = $this->getAlarmHash($valarm);
265
+                    if (\in_array($alarmHash, $processedAlarms, true)) {
266
+                        continue;
267
+                    }
268
+
269
+                    if (!\in_array((string) $valarm->ACTION, self::REMINDER_TYPES, true)) {
270
+                        // Action allows x-name, we don't insert reminders
271
+                        // into the database if they are not standard
272
+                        $processedAlarms[] = $alarmHash;
273
+                        continue;
274
+                    }
275
+
276
+                    $triggerTime = $valarm->getEffectiveTriggerTime();
277
+
278
+                    // If effective trigger time is in the past
279
+                    // just skip and generate for next event
280
+                    $diff = $now->diff($triggerTime);
281
+                    if ($diff->invert === 1) {
282
+                        // If an absolute alarm is in the past,
283
+                        // just add it to processedAlarms, so
284
+                        // we don't extend till eternity
285
+                        if (!$this->isAlarmRelative($valarm)) {
286
+                            $processedAlarms[] = $alarmHash;
287
+                        }
288
+
289
+                        continue;
290
+                    }
291
+
292
+                    $alarms = $this->getRemindersForVAlarm($valarm, $objectData, $masterHash, $alarmHash, $isRecurring, false);
293
+                    $this->writeRemindersToDatabase($alarms);
294
+                    $processedAlarms[] = $alarmHash;
295
+                }
296
+
297
+                $iterator->next();
298
+            }
299
+        }
300
+    }
301
+
302
+    /**
303
+     * @param array $objectData
304
+     */
305
+    private function onCalendarObjectEdit(array $objectData):void {
306
+        // TODO - this can be vastly improved
307
+        //  - get cached reminders
308
+        //  - ...
309
+
310
+        $this->onCalendarObjectDelete($objectData);
311
+        $this->onCalendarObjectCreate($objectData);
312
+    }
313
+
314
+    /**
315
+     * @param array $objectData
316
+     */
317
+    private function onCalendarObjectDelete(array $objectData):void {
318
+        $this->backend->cleanRemindersForEvent((int) $objectData['id']);
319
+    }
320
+
321
+    /**
322
+     * @param VAlarm $valarm
323
+     * @param array $objectData
324
+     * @param string|null $eventHash
325
+     * @param string|null $alarmHash
326
+     * @param bool $isRecurring
327
+     * @param bool $isRecurrenceException
328
+     * @return array
329
+     */
330
+    private function getRemindersForVAlarm(VAlarm $valarm,
331
+                                            array $objectData,
332
+                                            string $eventHash=null,
333
+                                            string $alarmHash=null,
334
+                                            bool $isRecurring=false,
335
+                                            bool $isRecurrenceException=false):array {
336
+        if ($eventHash === null) {
337
+            $eventHash = $this->getEventHash($valarm->parent);
338
+        }
339
+        if ($alarmHash === null) {
340
+            $alarmHash = $this->getAlarmHash($valarm);
341
+        }
342
+
343
+        $recurrenceId = $this->getEffectiveRecurrenceIdOfVEvent($valarm->parent);
344
+        $isRelative = $this->isAlarmRelative($valarm);
345
+        /** @var DateTimeImmutable $notificationDate */
346
+        $notificationDate = $valarm->getEffectiveTriggerTime();
347
+        $clonedNotificationDate = new \DateTime('now', $notificationDate->getTimezone());
348
+        $clonedNotificationDate->setTimestamp($notificationDate->getTimestamp());
349
+
350
+        $alarms = [];
351
+
352
+        $alarms[] = [
353
+            'calendar_id' => $objectData['calendarid'],
354
+            'object_id' => $objectData['id'],
355
+            'uid' => (string) $valarm->parent->UID,
356
+            'is_recurring' => $isRecurring,
357
+            'recurrence_id' => $recurrenceId,
358
+            'is_recurrence_exception' => $isRecurrenceException,
359
+            'event_hash' => $eventHash,
360
+            'alarm_hash' => $alarmHash,
361
+            'type' => (string) $valarm->ACTION,
362
+            'is_relative' => $isRelative,
363
+            'notification_date' => $notificationDate->getTimestamp(),
364
+            'is_repeat_based' => false,
365
+        ];
366
+
367
+        $repeat = isset($valarm->REPEAT) ? (int) $valarm->REPEAT->getValue() : 0;
368
+        for($i = 0; $i < $repeat; $i++) {
369
+            if ($valarm->DURATION === null) {
370
+                continue;
371
+            }
372
+
373
+            $clonedNotificationDate->add($valarm->DURATION->getDateInterval());
374
+            $alarms[] = [
375
+                'calendar_id' => $objectData['calendarid'],
376
+                'object_id' => $objectData['id'],
377
+                'uid' => (string) $valarm->parent->UID,
378
+                'is_recurring' => $isRecurring,
379
+                'recurrence_id' => $recurrenceId,
380
+                'is_recurrence_exception' => $isRecurrenceException,
381
+                'event_hash' => $eventHash,
382
+                'alarm_hash' => $alarmHash,
383
+                'type' => (string) $valarm->ACTION,
384
+                'is_relative' => $isRelative,
385
+                'notification_date' => $clonedNotificationDate->getTimestamp(),
386
+                'is_repeat_based' => true,
387
+            ];
388
+        }
389
+
390
+        return $alarms;
391
+    }
392
+
393
+    /**
394
+     * @param array $reminders
395
+     */
396
+    private function writeRemindersToDatabase(array $reminders): void {
397
+        foreach($reminders as $reminder) {
398
+            $this->backend->insertReminder(
399
+                (int) $reminder['calendar_id'],
400
+                (int) $reminder['object_id'],
401
+                $reminder['uid'],
402
+                $reminder['is_recurring'],
403
+                (int) $reminder['recurrence_id'],
404
+                $reminder['is_recurrence_exception'],
405
+                $reminder['event_hash'],
406
+                $reminder['alarm_hash'],
407
+                $reminder['type'],
408
+                $reminder['is_relative'],
409
+                (int) $reminder['notification_date'],
410
+                $reminder['is_repeat_based']
411
+            );
412
+        }
413
+    }
414
+
415
+    /**
416
+     * @param array $reminder
417
+     * @param VEvent $vevent
418
+     */
419
+    private function deleteOrProcessNext(array $reminder,
420
+                                            VObject\Component\VEvent $vevent):void {
421
+        if ($reminder['is_repeat_based'] ||
422
+            !$reminder['is_recurring'] ||
423
+            !$reminder['is_relative'] ||
424
+            $reminder['is_recurrence_exception']) {
425
+
426
+            $this->backend->removeReminder($reminder['id']);
427
+            return;
428
+        }
429
+
430
+        $vevents = $this->getAllVEventsFromVCalendar($vevent->parent);
431
+        $recurrenceExceptions = $this->getRecurrenceExceptionFromListOfVEvents($vevents);
432
+        $now = $this->timeFactory->getDateTime();
433
+
434
+        try {
435
+            $iterator = new EventIterator($vevents, $reminder['uid']);
436
+        } catch (NoInstancesException $e) {
437
+            // This event is recurring, but it doesn't have a single
438
+            // instance. We are skipping this event from the output
439
+            // entirely.
440
+            return;
441
+        }
442
+
443
+        while($iterator->valid()) {
444
+            $event = $iterator->getEventObject();
445
+
446
+            // Recurrence-exceptions are handled separately, so just ignore them here
447
+            if (\in_array($event, $recurrenceExceptions, true)) {
448
+                $iterator->next();
449
+                continue;
450
+            }
451
+
452
+            $recurrenceId = $this->getEffectiveRecurrenceIdOfVEvent($event);
453
+            if ($reminder['recurrence_id'] >= $recurrenceId) {
454
+                $iterator->next();
455
+                continue;
456
+            }
457
+
458
+            foreach($event->VALARM as $valarm) {
459
+                /** @var VAlarm $valarm */
460
+                $alarmHash = $this->getAlarmHash($valarm);
461
+                if ($alarmHash !== $reminder['alarm_hash']) {
462
+                    continue;
463
+                }
464
+
465
+                $triggerTime = $valarm->getEffectiveTriggerTime();
466
+
467
+                // If effective trigger time is in the past
468
+                // just skip and generate for next event
469
+                $diff = $now->diff($triggerTime);
470
+                if ($diff->invert === 1) {
471
+                    continue;
472
+                }
473
+
474
+                $this->backend->removeReminder($reminder['id']);
475
+                $alarms = $this->getRemindersForVAlarm($valarm, [
476
+                    'calendarid' => $reminder['calendar_id'],
477
+                    'id' => $reminder['object_id'],
478
+                ], $reminder['event_hash'], $alarmHash, true, false);
479
+                $this->writeRemindersToDatabase($alarms);
480
+
481
+                // Abort generating reminders after creating one successfully
482
+                return;
483
+            }
484
+
485
+            $iterator->next();
486
+        }
487
+
488
+        $this->backend->removeReminder($reminder['id']);
489
+    }
490
+
491
+    /**
492
+     * @param int $calendarId
493
+     * @return IUser[]
494
+     */
495
+    private function getAllUsersWithWriteAccessToCalendar(int $calendarId):array {
496
+        $shares = $this->caldavBackend->getShares($calendarId);
497
+
498
+        $users = [];
499
+        $userIds = [];
500
+        $groups = [];
501
+        foreach ($shares as $share) {
502
+            // Only consider writable shares
503
+            if ($share['readOnly']) {
504
+                continue;
505
+            }
506
+
507
+            $principal = explode('/', $share['{http://owncloud.org/ns}principal']);
508
+            if ($principal[1] === 'users') {
509
+                $user = $this->userManager->get($principal[2]);
510
+                if ($user) {
511
+                    $users[] = $user;
512
+                    $userIds[] = $principal[2];
513
+                }
514
+            } else if ($principal[1] === 'groups') {
515
+                $groups[] = $principal[2];
516
+            }
517
+        }
518
+
519
+        foreach ($groups as $gid) {
520
+            $group = $this->groupManager->get($gid);
521
+            if ($group instanceof IGroup) {
522
+                foreach ($group->getUsers() as $user) {
523
+                    if (!\in_array($user->getUID(), $userIds, true)) {
524
+                        $users[] = $user;
525
+                        $userIds[] = $user->getUID();
526
+                    }
527
+                }
528
+            }
529
+        }
530
+
531
+        return $users;
532
+    }
533
+
534
+    /**
535
+     * Gets a hash of the event.
536
+     * If the hash changes, we have to update all relative alarms.
537
+     *
538
+     * @param VEvent $vevent
539
+     * @return string
540
+     */
541
+    private function getEventHash(VEvent $vevent):string {
542
+        $properties = [
543
+            (string) $vevent->DTSTART->serialize(),
544
+        ];
545
+
546
+        if ($vevent->DTEND) {
547
+            $properties[] = (string) $vevent->DTEND->serialize();
548
+        }
549
+        if ($vevent->DURATION) {
550
+            $properties[] = (string) $vevent->DURATION->serialize();
551
+        }
552
+        if ($vevent->{'RECURRENCE-ID'}) {
553
+            $properties[] = (string) $vevent->{'RECURRENCE-ID'}->serialize();
554
+        }
555
+        if ($vevent->RRULE) {
556
+            $properties[] = (string) $vevent->RRULE->serialize();
557
+        }
558
+        if ($vevent->EXDATE) {
559
+            $properties[] = (string) $vevent->EXDATE->serialize();
560
+        }
561
+        if ($vevent->RDATE) {
562
+            $properties[] = (string) $vevent->RDATE->serialize();
563
+        }
564
+
565
+        return md5(implode('::', $properties));
566
+    }
567
+
568
+    /**
569
+     * Gets a hash of the alarm.
570
+     * If the hash changes, we have to update oc_dav_reminders.
571
+     *
572
+     * @param VAlarm $valarm
573
+     * @return string
574
+     */
575
+    private function getAlarmHash(VAlarm $valarm):string {
576
+        $properties = [
577
+            (string) $valarm->ACTION->serialize(),
578
+            (string) $valarm->TRIGGER->serialize(),
579
+        ];
580
+
581
+        if ($valarm->DURATION) {
582
+            $properties[] = (string) $valarm->DURATION->serialize();
583
+        }
584
+        if ($valarm->REPEAT) {
585
+            $properties[] = (string) $valarm->REPEAT->serialize();
586
+        }
587
+
588
+        return md5(implode('::', $properties));
589
+    }
590
+
591
+    /**
592
+     * @param VObject\Component\VCalendar $vcalendar
593
+     * @param int $recurrenceId
594
+     * @param bool $isRecurrenceException
595
+     * @return VEvent|null
596
+     */
597
+    private function getVEventByRecurrenceId(VObject\Component\VCalendar $vcalendar,
598
+                                                int $recurrenceId,
599
+                                                bool $isRecurrenceException):?VEvent {
600
+        $vevents = $this->getAllVEventsFromVCalendar($vcalendar);
601
+        if (count($vevents) === 0) {
602
+            return null;
603
+        }
604
+
605
+        $uid = (string) $vevents[0]->UID;
606
+        $recurrenceExceptions = $this->getRecurrenceExceptionFromListOfVEvents($vevents);
607
+        $masterItem = $this->getMasterItemFromListOfVEvents($vevents);
608
+
609
+        // Handle recurrence-exceptions first, because recurrence-expansion is expensive
610
+        if ($isRecurrenceException) {
611
+            foreach($recurrenceExceptions as $recurrenceException) {
612
+                if ($this->getEffectiveRecurrenceIdOfVEvent($recurrenceException) === $recurrenceId) {
613
+                    return $recurrenceException;
614
+                }
615
+            }
616
+
617
+            return null;
618
+        }
619
+
620
+        if ($masterItem) {
621
+            try {
622
+                $iterator = new EventIterator($vevents, $uid);
623
+            } catch (NoInstancesException $e) {
624
+                // This event is recurring, but it doesn't have a single
625
+                // instance. We are skipping this event from the output
626
+                // entirely.
627
+                return null;
628
+            }
629
+
630
+            while ($iterator->valid()) {
631
+                $event = $iterator->getEventObject();
632
+
633
+                // Recurrence-exceptions are handled separately, so just ignore them here
634
+                if (\in_array($event, $recurrenceExceptions, true)) {
635
+                    $iterator->next();
636
+                    continue;
637
+                }
638
+
639
+                if ($this->getEffectiveRecurrenceIdOfVEvent($event) === $recurrenceId) {
640
+                    return $event;
641
+                }
642
+
643
+                $iterator->next();
644
+            }
645
+        }
646
+
647
+        return null;
648
+    }
649
+
650
+    /**
651
+     * @param VEvent $vevent
652
+     * @return string
653
+     */
654
+    private function getStatusOfEvent(VEvent $vevent):string {
655
+        if ($vevent->STATUS) {
656
+            return (string) $vevent->STATUS;
657
+        }
658
+
659
+        // Doesn't say so in the standard,
660
+        // but we consider events without a status
661
+        // to be confirmed
662
+        return 'CONFIRMED';
663
+    }
664
+
665
+    /**
666
+     * @param VObject\Component\VEvent $vevent
667
+     * @return bool
668
+     */
669
+    private function wasEventCancelled(VObject\Component\VEvent $vevent):bool {
670
+        return $this->getStatusOfEvent($vevent) === 'CANCELLED';
671
+    }
672
+
673
+    /**
674
+     * @param string $calendarData
675
+     * @return VObject\Component\VCalendar|null
676
+     */
677
+    private function parseCalendarData(string $calendarData):?VObject\Component\VCalendar {
678
+        try {
679
+            return VObject\Reader::read($calendarData,
680
+                VObject\Reader::OPTION_FORGIVING);
681
+        } catch(ParseException $ex) {
682
+            return null;
683
+        }
684
+    }
685
+
686
+    /**
687
+     * @param string $principalUri
688
+     * @return IUser|null
689
+     */
690
+    private function getUserFromPrincipalURI(string $principalUri):?IUser {
691
+        if (!$principalUri) {
692
+            return null;
693
+        }
694
+
695
+        if (stripos($principalUri, 'principals/users/') !== 0) {
696
+            return null;
697
+        }
698
+
699
+        $userId = substr($principalUri, 17);
700
+        return $this->userManager->get($userId);
701
+    }
702
+
703
+    /**
704
+     * @param VObject\Component\VCalendar $vcalendar
705
+     * @return VObject\Component\VEvent[]
706
+     */
707
+    private function getAllVEventsFromVCalendar(VObject\Component\VCalendar $vcalendar):array {
708
+        $vevents = [];
709
+
710
+        foreach($vcalendar->children() as $child) {
711
+            if (!($child instanceof VObject\Component)) {
712
+                continue;
713
+            }
714
+
715
+            if ($child->name !== 'VEVENT') {
716
+                continue;
717
+            }
718
+
719
+            $vevents[] = $child;
720
+        }
721
+
722
+        return $vevents;
723
+    }
724
+
725
+    /**
726
+     * @param array $vevents
727
+     * @return VObject\Component\VEvent[]
728
+     */
729
+    private function getRecurrenceExceptionFromListOfVEvents(array $vevents):array {
730
+        return array_values(array_filter($vevents, function (VEvent $vevent) {
731
+            return $vevent->{'RECURRENCE-ID'} !== null;
732
+        }));
733
+    }
734
+
735
+    /**
736
+     * @param array $vevents
737
+     * @return VEvent|null
738
+     */
739
+    private function getMasterItemFromListOfVEvents(array $vevents):?VEvent {
740
+        $elements = array_values(array_filter($vevents, function (VEvent $vevent) {
741
+            return $vevent->{'RECURRENCE-ID'} === null;
742
+        }));
743
+
744
+        if (count($elements) === 0) {
745
+            return null;
746
+        }
747
+        if (count($elements) > 1) {
748
+            throw new \TypeError('Multiple master objects');
749
+        }
750
+
751
+        return $elements[0];
752
+    }
753
+
754
+    /**
755
+     * @param VAlarm $valarm
756
+     * @return bool
757
+     */
758
+    private function isAlarmRelative(VAlarm $valarm):bool {
759
+        $trigger = $valarm->TRIGGER;
760
+        return $trigger instanceof VObject\Property\ICalendar\Duration;
761
+    }
762
+
763
+    /**
764
+     * @param VEvent $vevent
765
+     * @return int
766
+     */
767
+    private function getEffectiveRecurrenceIdOfVEvent(VEvent $vevent):int {
768
+        if (isset($vevent->{'RECURRENCE-ID'})) {
769
+            return $vevent->{'RECURRENCE-ID'}->getDateTime()->getTimestamp();
770
+        }
771
+
772
+        return $vevent->DTSTART->getDateTime()->getTimestamp();
773
+    }
774
+
775
+    /**
776
+     * @param VEvent $vevent
777
+     * @return bool
778
+     */
779
+    private function isRecurring(VEvent $vevent):bool {
780
+        return isset($vevent->RRULE) || isset($vevent->RDATE);
781
+    }
782 782
 }
Please login to merge, or discard this patch.
apps/dav/lib/CalDAV/Publishing/PublishPlugin.php 1 patch
Indentation   +190 added lines, -190 removed lines patch added patch discarded remove patch
@@ -40,197 +40,197 @@
 block discarded – undo
40 40
 use Sabre\HTTP\ResponseInterface;
41 41
 
42 42
 class PublishPlugin extends ServerPlugin {
43
-	const NS_CALENDARSERVER = 'http://calendarserver.org/ns/';
44
-
45
-	/**
46
-	 * Reference to SabreDAV server object.
47
-	 *
48
-	 * @var \Sabre\DAV\Server
49
-	 */
50
-	protected $server;
51
-
52
-	/**
53
-	 * Config instance to get instance secret.
54
-	 *
55
-	 * @var IConfig
56
-	 */
57
-	protected $config;
58
-
59
-	/**
60
-	 * URL Generator for absolute URLs.
61
-	 *
62
-	 * @var IURLGenerator
63
-	 */
64
-	protected $urlGenerator;
65
-
66
-	/**
67
-	 * PublishPlugin constructor.
68
-	 *
69
-	 * @param IConfig $config
70
-	 * @param IURLGenerator $urlGenerator
71
-	 */
72
-	public function __construct(IConfig $config, IURLGenerator $urlGenerator) {
73
-		$this->config = $config;
74
-		$this->urlGenerator = $urlGenerator;
75
-	}
76
-
77
-	/**
78
-	 * This method should return a list of server-features.
79
-	 *
80
-	 * This is for example 'versioning' and is added to the DAV: header
81
-	 * in an OPTIONS response.
82
-	 *
83
-	 * @return string[]
84
-	 */
85
-	public function getFeatures() {
86
-		// May have to be changed to be detected
87
-		return ['oc-calendar-publishing', 'calendarserver-sharing'];
88
-	}
89
-
90
-	/**
91
-	 * Returns a plugin name.
92
-	 *
93
-	 * Using this name other plugins will be able to access other plugins
94
-	 * using Sabre\DAV\Server::getPlugin
95
-	 *
96
-	 * @return string
97
-	 */
98
-	public function getPluginName() {
99
-		return 'oc-calendar-publishing';
100
-	}
101
-
102
-	/**
103
-	 * This initializes the plugin.
104
-	 *
105
-	 * This function is called by Sabre\DAV\Server, after
106
-	 * addPlugin is called.
107
-	 *
108
-	 * This method should set up the required event subscriptions.
109
-	 *
110
-	 * @param Server $server
111
-	 */
112
-	public function initialize(Server $server) {
113
-		$this->server = $server;
114
-
115
-		$this->server->on('method:POST', [$this, 'httpPost']);
116
-		$this->server->on('propFind',    [$this, 'propFind']);
117
-	}
118
-
119
-	public function propFind(PropFind $propFind, INode $node) {
120
-		if ($node instanceof Calendar) {
121
-			$propFind->handle('{'.self::NS_CALENDARSERVER.'}publish-url', function () use ($node) {
122
-				if ($node->getPublishStatus()) {
123
-					// We return the publish-url only if the calendar is published.
124
-					$token = $node->getPublishStatus();
125
-					$publishUrl = $this->urlGenerator->getAbsoluteURL($this->server->getBaseUri().'public-calendars/').$token;
126
-
127
-					return new Publisher($publishUrl, true);
128
-				}
129
-			});
130
-
131
-			$propFind->handle('{'.self::NS_CALENDARSERVER.'}allowed-sharing-modes', function () use ($node) {
132
-				$canShare = (!$node->isSubscription() && $node->canWrite());
133
-				$canPublish = (!$node->isSubscription() && $node->canWrite());
134
-
135
-				return new AllowedSharingModes($canShare, $canPublish);
136
-			});
137
-		}
138
-	}
139
-
140
-	/**
141
-	 * We intercept this to handle POST requests on calendars.
142
-	 *
143
-	 * @param RequestInterface $request
144
-	 * @param ResponseInterface $response
145
-	 *
146
-	 * @return void|bool
147
-	 */
148
-	public function httpPost(RequestInterface $request, ResponseInterface $response) {
149
-		$path = $request->getPath();
150
-
151
-		// Only handling xml
152
-		$contentType = $request->getHeader('Content-Type');
153
-		if (strpos($contentType, 'application/xml') === false && strpos($contentType, 'text/xml') === false) {
154
-			return;
155
-		}
156
-
157
-		// Making sure the node exists
158
-		try {
159
-			$node = $this->server->tree->getNodeForPath($path);
160
-		} catch (NotFound $e) {
161
-			return;
162
-		}
163
-
164
-		$requestBody = $request->getBodyAsString();
165
-
166
-		// If this request handler could not deal with this POST request, it
167
-		// will return 'null' and other plugins get a chance to handle the
168
-		// request.
169
-		//
170
-		// However, we already requested the full body. This is a problem,
171
-		// because a body can only be read once. This is why we preemptively
172
-		// re-populated the request body with the existing data.
173
-		$request->setBody($requestBody);
174
-
175
-		$this->server->xml->parse($requestBody, $request->getUrl(), $documentType);
176
-
177
-		switch ($documentType) {
178
-
179
-			case '{'.self::NS_CALENDARSERVER.'}publish-calendar' :
180
-
181
-			// We can only deal with IShareableCalendar objects
182
-			if (!$node instanceof Calendar) {
183
-				return;
184
-			}
185
-			$this->server->transactionType = 'post-publish-calendar';
186
-
187
-			// Getting ACL info
188
-			$acl = $this->server->getPlugin('acl');
189
-
190
-			// If there's no ACL support, we allow everything
191
-			if ($acl) {
192
-				$acl->checkPrivileges($path, '{DAV:}write');
193
-			}
194
-
195
-			$node->setPublishStatus(true);
196
-
197
-			// iCloud sends back the 202, so we will too.
198
-			$response->setStatus(202);
199
-
200
-			// Adding this because sending a response body may cause issues,
201
-			// and I wanted some type of indicator the response was handled.
202
-			$response->setHeader('X-Sabre-Status', 'everything-went-well');
203
-
204
-			// Breaking the event chain
205
-			return false;
206
-
207
-			case '{'.self::NS_CALENDARSERVER.'}unpublish-calendar' :
208
-
209
-			// We can only deal with IShareableCalendar objects
210
-			if (!$node instanceof Calendar) {
211
-				return;
212
-			}
213
-			$this->server->transactionType = 'post-unpublish-calendar';
214
-
215
-			// Getting ACL info
216
-			$acl = $this->server->getPlugin('acl');
217
-
218
-			// If there's no ACL support, we allow everything
219
-			if ($acl) {
220
-				$acl->checkPrivileges($path, '{DAV:}write');
221
-			}
222
-
223
-			$node->setPublishStatus(false);
224
-
225
-			$response->setStatus(200);
43
+    const NS_CALENDARSERVER = 'http://calendarserver.org/ns/';
44
+
45
+    /**
46
+     * Reference to SabreDAV server object.
47
+     *
48
+     * @var \Sabre\DAV\Server
49
+     */
50
+    protected $server;
51
+
52
+    /**
53
+     * Config instance to get instance secret.
54
+     *
55
+     * @var IConfig
56
+     */
57
+    protected $config;
58
+
59
+    /**
60
+     * URL Generator for absolute URLs.
61
+     *
62
+     * @var IURLGenerator
63
+     */
64
+    protected $urlGenerator;
65
+
66
+    /**
67
+     * PublishPlugin constructor.
68
+     *
69
+     * @param IConfig $config
70
+     * @param IURLGenerator $urlGenerator
71
+     */
72
+    public function __construct(IConfig $config, IURLGenerator $urlGenerator) {
73
+        $this->config = $config;
74
+        $this->urlGenerator = $urlGenerator;
75
+    }
76
+
77
+    /**
78
+     * This method should return a list of server-features.
79
+     *
80
+     * This is for example 'versioning' and is added to the DAV: header
81
+     * in an OPTIONS response.
82
+     *
83
+     * @return string[]
84
+     */
85
+    public function getFeatures() {
86
+        // May have to be changed to be detected
87
+        return ['oc-calendar-publishing', 'calendarserver-sharing'];
88
+    }
89
+
90
+    /**
91
+     * Returns a plugin name.
92
+     *
93
+     * Using this name other plugins will be able to access other plugins
94
+     * using Sabre\DAV\Server::getPlugin
95
+     *
96
+     * @return string
97
+     */
98
+    public function getPluginName() {
99
+        return 'oc-calendar-publishing';
100
+    }
101
+
102
+    /**
103
+     * This initializes the plugin.
104
+     *
105
+     * This function is called by Sabre\DAV\Server, after
106
+     * addPlugin is called.
107
+     *
108
+     * This method should set up the required event subscriptions.
109
+     *
110
+     * @param Server $server
111
+     */
112
+    public function initialize(Server $server) {
113
+        $this->server = $server;
114
+
115
+        $this->server->on('method:POST', [$this, 'httpPost']);
116
+        $this->server->on('propFind',    [$this, 'propFind']);
117
+    }
118
+
119
+    public function propFind(PropFind $propFind, INode $node) {
120
+        if ($node instanceof Calendar) {
121
+            $propFind->handle('{'.self::NS_CALENDARSERVER.'}publish-url', function () use ($node) {
122
+                if ($node->getPublishStatus()) {
123
+                    // We return the publish-url only if the calendar is published.
124
+                    $token = $node->getPublishStatus();
125
+                    $publishUrl = $this->urlGenerator->getAbsoluteURL($this->server->getBaseUri().'public-calendars/').$token;
126
+
127
+                    return new Publisher($publishUrl, true);
128
+                }
129
+            });
130
+
131
+            $propFind->handle('{'.self::NS_CALENDARSERVER.'}allowed-sharing-modes', function () use ($node) {
132
+                $canShare = (!$node->isSubscription() && $node->canWrite());
133
+                $canPublish = (!$node->isSubscription() && $node->canWrite());
134
+
135
+                return new AllowedSharingModes($canShare, $canPublish);
136
+            });
137
+        }
138
+    }
139
+
140
+    /**
141
+     * We intercept this to handle POST requests on calendars.
142
+     *
143
+     * @param RequestInterface $request
144
+     * @param ResponseInterface $response
145
+     *
146
+     * @return void|bool
147
+     */
148
+    public function httpPost(RequestInterface $request, ResponseInterface $response) {
149
+        $path = $request->getPath();
150
+
151
+        // Only handling xml
152
+        $contentType = $request->getHeader('Content-Type');
153
+        if (strpos($contentType, 'application/xml') === false && strpos($contentType, 'text/xml') === false) {
154
+            return;
155
+        }
156
+
157
+        // Making sure the node exists
158
+        try {
159
+            $node = $this->server->tree->getNodeForPath($path);
160
+        } catch (NotFound $e) {
161
+            return;
162
+        }
163
+
164
+        $requestBody = $request->getBodyAsString();
165
+
166
+        // If this request handler could not deal with this POST request, it
167
+        // will return 'null' and other plugins get a chance to handle the
168
+        // request.
169
+        //
170
+        // However, we already requested the full body. This is a problem,
171
+        // because a body can only be read once. This is why we preemptively
172
+        // re-populated the request body with the existing data.
173
+        $request->setBody($requestBody);
174
+
175
+        $this->server->xml->parse($requestBody, $request->getUrl(), $documentType);
176
+
177
+        switch ($documentType) {
178
+
179
+            case '{'.self::NS_CALENDARSERVER.'}publish-calendar' :
180
+
181
+            // We can only deal with IShareableCalendar objects
182
+            if (!$node instanceof Calendar) {
183
+                return;
184
+            }
185
+            $this->server->transactionType = 'post-publish-calendar';
186
+
187
+            // Getting ACL info
188
+            $acl = $this->server->getPlugin('acl');
189
+
190
+            // If there's no ACL support, we allow everything
191
+            if ($acl) {
192
+                $acl->checkPrivileges($path, '{DAV:}write');
193
+            }
194
+
195
+            $node->setPublishStatus(true);
196
+
197
+            // iCloud sends back the 202, so we will too.
198
+            $response->setStatus(202);
199
+
200
+            // Adding this because sending a response body may cause issues,
201
+            // and I wanted some type of indicator the response was handled.
202
+            $response->setHeader('X-Sabre-Status', 'everything-went-well');
203
+
204
+            // Breaking the event chain
205
+            return false;
206
+
207
+            case '{'.self::NS_CALENDARSERVER.'}unpublish-calendar' :
208
+
209
+            // We can only deal with IShareableCalendar objects
210
+            if (!$node instanceof Calendar) {
211
+                return;
212
+            }
213
+            $this->server->transactionType = 'post-unpublish-calendar';
214
+
215
+            // Getting ACL info
216
+            $acl = $this->server->getPlugin('acl');
217
+
218
+            // If there's no ACL support, we allow everything
219
+            if ($acl) {
220
+                $acl->checkPrivileges($path, '{DAV:}write');
221
+            }
222
+
223
+            $node->setPublishStatus(false);
224
+
225
+            $response->setStatus(200);
226 226
 
227
-			// Adding this because sending a response body may cause issues,
228
-			// and I wanted some type of indicator the response was handled.
229
-			$response->setHeader('X-Sabre-Status', 'everything-went-well');
227
+            // Adding this because sending a response body may cause issues,
228
+            // and I wanted some type of indicator the response was handled.
229
+            $response->setHeader('X-Sabre-Status', 'everything-went-well');
230 230
 
231
-			// Breaking the event chain
232
-			return false;
231
+            // Breaking the event chain
232
+            return false;
233 233
 
234
-		}
235
-	}
234
+        }
235
+    }
236 236
 }
Please login to merge, or discard this patch.
apps/dav/lib/Upload/UploadHome.php 1 patch
Indentation   +66 added lines, -66 removed lines patch added patch discarded remove patch
@@ -33,70 +33,70 @@
 block discarded – undo
33 33
 
34 34
 class UploadHome implements ICollection {
35 35
 
36
-	/** @var array */
37
-	private $principalInfo;
38
-	/** @var CleanupService */
39
-	private $cleanupService;
40
-
41
-	public function __construct(array $principalInfo, CleanupService $cleanupService) {
42
-		$this->principalInfo = $principalInfo;
43
-		$this->cleanupService = $cleanupService;
44
-	}
45
-
46
-	public function createFile($name, $data = null) {
47
-		throw new Forbidden('Permission denied to create file (filename ' . $name . ')');
48
-	}
49
-
50
-	public function createDirectory($name) {
51
-		$this->impl()->createDirectory($name);
52
-
53
-		// Add a cleanup job
54
-		$this->cleanupService->addJob($name);
55
-	}
56
-
57
-	public function getChild($name): UploadFolder {
58
-		return new UploadFolder($this->impl()->getChild($name), $this->cleanupService);
59
-	}
60
-
61
-	public function getChildren(): array {
62
-		return array_map(function ($node) {
63
-			return new UploadFolder($node, $this->cleanupService);
64
-		}, $this->impl()->getChildren());
65
-	}
66
-
67
-	public function childExists($name): bool {
68
-		return !is_null($this->getChild($name));
69
-	}
70
-
71
-	public function delete() {
72
-		$this->impl()->delete();
73
-	}
74
-
75
-	public function getName() {
76
-		list(,$name) = \Sabre\Uri\split($this->principalInfo['uri']);
77
-		return $name;
78
-	}
79
-
80
-	public function setName($name) {
81
-		throw new Forbidden('Permission denied to rename this folder');
82
-	}
83
-
84
-	public function getLastModified() {
85
-		return $this->impl()->getLastModified();
86
-	}
87
-
88
-	/**
89
-	 * @return Directory
90
-	 */
91
-	private function impl() {
92
-		$rootView = new View();
93
-		$user = \OC::$server->getUserSession()->getUser();
94
-		Filesystem::initMountPoints($user->getUID());
95
-		if (!$rootView->file_exists('/' . $user->getUID() . '/uploads')) {
96
-			$rootView->mkdir('/' . $user->getUID() . '/uploads');
97
-		}
98
-		$view = new View('/' . $user->getUID() . '/uploads');
99
-		$rootInfo = $view->getFileInfo('');
100
-		return new Directory($view, $rootInfo);
101
-	}
36
+    /** @var array */
37
+    private $principalInfo;
38
+    /** @var CleanupService */
39
+    private $cleanupService;
40
+
41
+    public function __construct(array $principalInfo, CleanupService $cleanupService) {
42
+        $this->principalInfo = $principalInfo;
43
+        $this->cleanupService = $cleanupService;
44
+    }
45
+
46
+    public function createFile($name, $data = null) {
47
+        throw new Forbidden('Permission denied to create file (filename ' . $name . ')');
48
+    }
49
+
50
+    public function createDirectory($name) {
51
+        $this->impl()->createDirectory($name);
52
+
53
+        // Add a cleanup job
54
+        $this->cleanupService->addJob($name);
55
+    }
56
+
57
+    public function getChild($name): UploadFolder {
58
+        return new UploadFolder($this->impl()->getChild($name), $this->cleanupService);
59
+    }
60
+
61
+    public function getChildren(): array {
62
+        return array_map(function ($node) {
63
+            return new UploadFolder($node, $this->cleanupService);
64
+        }, $this->impl()->getChildren());
65
+    }
66
+
67
+    public function childExists($name): bool {
68
+        return !is_null($this->getChild($name));
69
+    }
70
+
71
+    public function delete() {
72
+        $this->impl()->delete();
73
+    }
74
+
75
+    public function getName() {
76
+        list(,$name) = \Sabre\Uri\split($this->principalInfo['uri']);
77
+        return $name;
78
+    }
79
+
80
+    public function setName($name) {
81
+        throw new Forbidden('Permission denied to rename this folder');
82
+    }
83
+
84
+    public function getLastModified() {
85
+        return $this->impl()->getLastModified();
86
+    }
87
+
88
+    /**
89
+     * @return Directory
90
+     */
91
+    private function impl() {
92
+        $rootView = new View();
93
+        $user = \OC::$server->getUserSession()->getUser();
94
+        Filesystem::initMountPoints($user->getUID());
95
+        if (!$rootView->file_exists('/' . $user->getUID() . '/uploads')) {
96
+            $rootView->mkdir('/' . $user->getUID() . '/uploads');
97
+        }
98
+        $view = new View('/' . $user->getUID() . '/uploads');
99
+        $rootInfo = $view->getFileInfo('');
100
+        return new Directory($view, $rootInfo);
101
+    }
102 102
 }
Please login to merge, or discard this patch.
apps/dav/lib/Upload/FutureFile.php 1 patch
Indentation   +72 added lines, -72 removed lines patch added patch discarded remove patch
@@ -38,87 +38,87 @@
 block discarded – undo
38 38
  */
39 39
 class FutureFile implements \Sabre\DAV\IFile {
40 40
 
41
-	/** @var Directory */
42
-	private $root;
43
-	/** @var string */
44
-	private $name;
41
+    /** @var Directory */
42
+    private $root;
43
+    /** @var string */
44
+    private $name;
45 45
 
46
-	/**
47
-	 * @param Directory $root
48
-	 * @param string $name
49
-	 */
50
-	function __construct(Directory $root, $name) {
51
-		$this->root = $root;
52
-		$this->name = $name;
53
-	}
46
+    /**
47
+     * @param Directory $root
48
+     * @param string $name
49
+     */
50
+    function __construct(Directory $root, $name) {
51
+        $this->root = $root;
52
+        $this->name = $name;
53
+    }
54 54
 
55
-	/**
56
-	 * @inheritdoc
57
-	 */
58
-	function put($data) {
59
-		throw new Forbidden('Permission denied to put into this file');
60
-	}
55
+    /**
56
+     * @inheritdoc
57
+     */
58
+    function put($data) {
59
+        throw new Forbidden('Permission denied to put into this file');
60
+    }
61 61
 
62
-	/**
63
-	 * @inheritdoc
64
-	 */
65
-	function get() {
66
-		$nodes = $this->root->getChildren();
67
-		return AssemblyStream::wrap($nodes);
68
-	}
62
+    /**
63
+     * @inheritdoc
64
+     */
65
+    function get() {
66
+        $nodes = $this->root->getChildren();
67
+        return AssemblyStream::wrap($nodes);
68
+    }
69 69
 
70
-	/**
71
-	 * @inheritdoc
72
-	 */
73
-	function getContentType() {
74
-		return 'application/octet-stream';
75
-	}
70
+    /**
71
+     * @inheritdoc
72
+     */
73
+    function getContentType() {
74
+        return 'application/octet-stream';
75
+    }
76 76
 
77
-	/**
78
-	 * @inheritdoc
79
-	 */
80
-	function getETag() {
81
-		return $this->root->getETag();
82
-	}
77
+    /**
78
+     * @inheritdoc
79
+     */
80
+    function getETag() {
81
+        return $this->root->getETag();
82
+    }
83 83
 
84
-	/**
85
-	 * @inheritdoc
86
-	 */
87
-	function getSize() {
88
-		$children = $this->root->getChildren();
89
-		$sizes = array_map(function ($node) {
90
-			/** @var IFile $node */
91
-			return $node->getSize();
92
-		}, $children);
84
+    /**
85
+     * @inheritdoc
86
+     */
87
+    function getSize() {
88
+        $children = $this->root->getChildren();
89
+        $sizes = array_map(function ($node) {
90
+            /** @var IFile $node */
91
+            return $node->getSize();
92
+        }, $children);
93 93
 
94
-		return array_sum($sizes);
95
-	}
94
+        return array_sum($sizes);
95
+    }
96 96
 
97
-	/**
98
-	 * @inheritdoc
99
-	 */
100
-	function delete() {
101
-		$this->root->delete();
102
-	}
97
+    /**
98
+     * @inheritdoc
99
+     */
100
+    function delete() {
101
+        $this->root->delete();
102
+    }
103 103
 
104
-	/**
105
-	 * @inheritdoc
106
-	 */
107
-	function getName() {
108
-		return $this->name;
109
-	}
104
+    /**
105
+     * @inheritdoc
106
+     */
107
+    function getName() {
108
+        return $this->name;
109
+    }
110 110
 
111
-	/**
112
-	 * @inheritdoc
113
-	 */
114
-	function setName($name) {
115
-		throw new Forbidden('Permission denied to rename this file');
116
-	}
111
+    /**
112
+     * @inheritdoc
113
+     */
114
+    function setName($name) {
115
+        throw new Forbidden('Permission denied to rename this file');
116
+    }
117 117
 
118
-	/**
119
-	 * @inheritdoc
120
-	 */
121
-	function getLastModified() {
122
-		return $this->root->getLastModified();
123
-	}
118
+    /**
119
+     * @inheritdoc
120
+     */
121
+    function getLastModified() {
122
+        return $this->root->getLastModified();
123
+    }
124 124
 }
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/FakeLockerPlugin.php 1 patch
Indentation   +99 added lines, -99 removed lines patch added patch discarded remove patch
@@ -47,114 +47,114 @@
 block discarded – undo
47 47
  * @package OCA\DAV\Connector\Sabre
48 48
  */
49 49
 class FakeLockerPlugin extends ServerPlugin {
50
-	/** @var \Sabre\DAV\Server */
51
-	private $server;
50
+    /** @var \Sabre\DAV\Server */
51
+    private $server;
52 52
 
53
-	/** {@inheritDoc} */
54
-	public function initialize(\Sabre\DAV\Server $server) {
55
-		$this->server = $server;
56
-		$this->server->on('method:LOCK', [$this, 'fakeLockProvider'], 1);
57
-		$this->server->on('method:UNLOCK', [$this, 'fakeUnlockProvider'], 1);
58
-		$server->on('propFind', [$this, 'propFind']);
59
-		$server->on('validateTokens', [$this, 'validateTokens']);
60
-	}
53
+    /** {@inheritDoc} */
54
+    public function initialize(\Sabre\DAV\Server $server) {
55
+        $this->server = $server;
56
+        $this->server->on('method:LOCK', [$this, 'fakeLockProvider'], 1);
57
+        $this->server->on('method:UNLOCK', [$this, 'fakeUnlockProvider'], 1);
58
+        $server->on('propFind', [$this, 'propFind']);
59
+        $server->on('validateTokens', [$this, 'validateTokens']);
60
+    }
61 61
 
62
-	/**
63
-	 * Indicate that we support LOCK and UNLOCK
64
-	 *
65
-	 * @param string $path
66
-	 * @return string[]
67
-	 */
68
-	public function getHTTPMethods($path) {
69
-		return [
70
-			'LOCK',
71
-			'UNLOCK',
72
-		];
73
-	}
62
+    /**
63
+     * Indicate that we support LOCK and UNLOCK
64
+     *
65
+     * @param string $path
66
+     * @return string[]
67
+     */
68
+    public function getHTTPMethods($path) {
69
+        return [
70
+            'LOCK',
71
+            'UNLOCK',
72
+        ];
73
+    }
74 74
 
75
-	/**
76
-	 * Indicate that we support locking
77
-	 *
78
-	 * @return integer[]
79
-	 */
80
-	function getFeatures() {
81
-		return [2];
82
-	}
75
+    /**
76
+     * Indicate that we support locking
77
+     *
78
+     * @return integer[]
79
+     */
80
+    function getFeatures() {
81
+        return [2];
82
+    }
83 83
 
84
-	/**
85
-	 * Return some dummy response for PROPFIND requests with regard to locking
86
-	 *
87
-	 * @param PropFind $propFind
88
-	 * @param INode $node
89
-	 * @return void
90
-	 */
91
-	function propFind(PropFind $propFind, INode $node) {
92
-		$propFind->handle('{DAV:}supportedlock', function () {
93
-			return new SupportedLock(true);
94
-		});
95
-		$propFind->handle('{DAV:}lockdiscovery', function () use ($propFind) {
96
-			return new LockDiscovery([]);
97
-		});
98
-	}
84
+    /**
85
+     * Return some dummy response for PROPFIND requests with regard to locking
86
+     *
87
+     * @param PropFind $propFind
88
+     * @param INode $node
89
+     * @return void
90
+     */
91
+    function propFind(PropFind $propFind, INode $node) {
92
+        $propFind->handle('{DAV:}supportedlock', function () {
93
+            return new SupportedLock(true);
94
+        });
95
+        $propFind->handle('{DAV:}lockdiscovery', function () use ($propFind) {
96
+            return new LockDiscovery([]);
97
+        });
98
+    }
99 99
 
100
-	/**
101
-	 * Mark a locking token always as valid
102
-	 *
103
-	 * @param RequestInterface $request
104
-	 * @param array $conditions
105
-	 */
106
-	public function validateTokens(RequestInterface $request, &$conditions) {
107
-		foreach($conditions as &$fileCondition) {
108
-			if(isset($fileCondition['tokens'])) {
109
-				foreach($fileCondition['tokens'] as &$token) {
110
-					if(isset($token['token'])) {
111
-						if(substr($token['token'], 0, 16) === 'opaquelocktoken:') {
112
-							$token['validToken'] = true;
113
-						}
114
-					}
115
-				}
116
-			}
117
-		}
118
-	}
100
+    /**
101
+     * Mark a locking token always as valid
102
+     *
103
+     * @param RequestInterface $request
104
+     * @param array $conditions
105
+     */
106
+    public function validateTokens(RequestInterface $request, &$conditions) {
107
+        foreach($conditions as &$fileCondition) {
108
+            if(isset($fileCondition['tokens'])) {
109
+                foreach($fileCondition['tokens'] as &$token) {
110
+                    if(isset($token['token'])) {
111
+                        if(substr($token['token'], 0, 16) === 'opaquelocktoken:') {
112
+                            $token['validToken'] = true;
113
+                        }
114
+                    }
115
+                }
116
+            }
117
+        }
118
+    }
119 119
 
120
-	/**
121
-	 * Fakes a successful LOCK
122
-	 *
123
-	 * @param RequestInterface $request
124
-	 * @param ResponseInterface $response
125
-	 * @return bool
126
-	 */
127
-	public function fakeLockProvider(RequestInterface $request,
128
-									 ResponseInterface $response) {
120
+    /**
121
+     * Fakes a successful LOCK
122
+     *
123
+     * @param RequestInterface $request
124
+     * @param ResponseInterface $response
125
+     * @return bool
126
+     */
127
+    public function fakeLockProvider(RequestInterface $request,
128
+                                        ResponseInterface $response) {
129 129
 
130
-		$lockInfo = new LockInfo();
131
-		$lockInfo->token = md5($request->getPath());
132
-		$lockInfo->uri = $request->getPath();
133
-		$lockInfo->depth = \Sabre\DAV\Server::DEPTH_INFINITY;
134
-		$lockInfo->timeout = 1800;
130
+        $lockInfo = new LockInfo();
131
+        $lockInfo->token = md5($request->getPath());
132
+        $lockInfo->uri = $request->getPath();
133
+        $lockInfo->depth = \Sabre\DAV\Server::DEPTH_INFINITY;
134
+        $lockInfo->timeout = 1800;
135 135
 
136
-		$body = $this->server->xml->write('{DAV:}prop', [
137
-			'{DAV:}lockdiscovery' =>
138
-					new LockDiscovery([$lockInfo])
139
-		]);
136
+        $body = $this->server->xml->write('{DAV:}prop', [
137
+            '{DAV:}lockdiscovery' =>
138
+                    new LockDiscovery([$lockInfo])
139
+        ]);
140 140
 
141
-		$response->setStatus(200);
142
-		$response->setBody($body);
141
+        $response->setStatus(200);
142
+        $response->setBody($body);
143 143
 
144
-		return false;
145
-	}
144
+        return false;
145
+    }
146 146
 
147
-	/**
148
-	 * Fakes a successful LOCK
149
-	 *
150
-	 * @param RequestInterface $request
151
-	 * @param ResponseInterface $response
152
-	 * @return bool
153
-	 */
154
-	public function fakeUnlockProvider(RequestInterface $request,
155
-									 ResponseInterface $response) {
156
-		$response->setStatus(204);
157
-		$response->setHeader('Content-Length', '0');
158
-		return false;
159
-	}
147
+    /**
148
+     * Fakes a successful LOCK
149
+     *
150
+     * @param RequestInterface $request
151
+     * @param ResponseInterface $response
152
+     * @return bool
153
+     */
154
+    public function fakeUnlockProvider(RequestInterface $request,
155
+                                        ResponseInterface $response) {
156
+        $response->setStatus(204);
157
+        $response->setHeader('Content-Length', '0');
158
+        return false;
159
+    }
160 160
 }
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/SharesPlugin.php 1 patch
Indentation   +177 added lines, -177 removed lines patch added patch discarded remove patch
@@ -36,181 +36,181 @@
 block discarded – undo
36 36
  */
37 37
 class SharesPlugin extends \Sabre\DAV\ServerPlugin {
38 38
 
39
-	const NS_OWNCLOUD = 'http://owncloud.org/ns';
40
-	const NS_NEXTCLOUD = 'http://nextcloud.org/ns';
41
-	const SHARETYPES_PROPERTYNAME = '{http://owncloud.org/ns}share-types';
42
-	const SHAREES_PROPERTYNAME = '{http://nextcloud.org/ns}sharees';
43
-
44
-	/**
45
-	 * Reference to main server object
46
-	 *
47
-	 * @var \Sabre\DAV\Server
48
-	 */
49
-	private $server;
50
-
51
-	/**
52
-	 * @var \OCP\Share\IManager
53
-	 */
54
-	private $shareManager;
55
-
56
-	/**
57
-	 * @var \Sabre\DAV\Tree
58
-	 */
59
-	private $tree;
60
-
61
-	/**
62
-	 * @var string
63
-	 */
64
-	private $userId;
65
-
66
-	/**
67
-	 * @var \OCP\Files\Folder
68
-	 */
69
-	private $userFolder;
70
-
71
-	/** @var IShare[] */
72
-	private $cachedShares = [];
73
-
74
-	private $cachedFolders = [];
75
-
76
-	/**
77
-	 * @param \Sabre\DAV\Tree $tree tree
78
-	 * @param IUserSession $userSession user session
79
-	 * @param \OCP\Files\Folder $userFolder user home folder
80
-	 * @param \OCP\Share\IManager $shareManager share manager
81
-	 */
82
-	public function __construct(
83
-		\Sabre\DAV\Tree $tree,
84
-		IUserSession $userSession,
85
-		\OCP\Files\Folder $userFolder,
86
-		\OCP\Share\IManager $shareManager
87
-	) {
88
-		$this->tree = $tree;
89
-		$this->shareManager = $shareManager;
90
-		$this->userFolder = $userFolder;
91
-		$this->userId = $userSession->getUser()->getUID();
92
-	}
93
-
94
-	/**
95
-	 * This initializes the plugin.
96
-	 *
97
-	 * This function is called by \Sabre\DAV\Server, after
98
-	 * addPlugin is called.
99
-	 *
100
-	 * This method should set up the required event subscriptions.
101
-	 *
102
-	 * @param \Sabre\DAV\Server $server
103
-	 */
104
-	public function initialize(\Sabre\DAV\Server $server) {
105
-		$server->xml->namespacesMap[self::NS_OWNCLOUD] = 'oc';
106
-		$server->xml->elementMap[self::SHARETYPES_PROPERTYNAME] = ShareTypeList::class;
107
-		$server->protectedProperties[] = self::SHARETYPES_PROPERTYNAME;
108
-		$server->protectedProperties[] = self::SHAREES_PROPERTYNAME;
109
-
110
-		$this->server = $server;
111
-		$this->server->on('propFind', [$this, 'handleGetProperties']);
112
-	}
113
-
114
-	private function getShare(\OCP\Files\Node $node): array {
115
-		$result = [];
116
-		$requestedShareTypes = [
117
-			\OCP\Share::SHARE_TYPE_USER,
118
-			\OCP\Share::SHARE_TYPE_GROUP,
119
-			\OCP\Share::SHARE_TYPE_LINK,
120
-			\OCP\Share::SHARE_TYPE_REMOTE,
121
-			\OCP\Share::SHARE_TYPE_EMAIL,
122
-			\OCP\Share::SHARE_TYPE_ROOM,
123
-			\OCP\Share::SHARE_TYPE_CIRCLE,
124
-		];
125
-		foreach ($requestedShareTypes as $requestedShareType) {
126
-			$shares = $this->shareManager->getSharesBy(
127
-				$this->userId,
128
-				$requestedShareType,
129
-				$node,
130
-				false,
131
-				-1
132
-			);
133
-			foreach ($shares as $share) {
134
-				$result[] = $share;
135
-			}
136
-		}
137
-		return $result;
138
-	}
139
-
140
-	private function getSharesFolder(\OCP\Files\Folder $node): array {
141
-		return $this->shareManager->getSharesInFolder(
142
-			$this->userId,
143
-			$node,
144
-			true
145
-		);
146
-	}
147
-
148
-	private function getShares(\Sabre\DAV\INode $sabreNode): array {
149
-		if (isset($this->cachedShares[$sabreNode->getId()])) {
150
-			$shares = $this->cachedShares[$sabreNode->getId()];
151
-		} else {
152
-			list($parentPath,) = \Sabre\Uri\split($sabreNode->getPath());
153
-			if ($parentPath === '') {
154
-				$parentPath = '/';
155
-			}
156
-			// if we already cached the folder this file is in we know there are no shares for this file
157
-			if (array_search($parentPath, $this->cachedFolders) === false) {
158
-				$node = $this->userFolder->get($sabreNode->getPath());
159
-				$shares = $this->getShare($node);
160
-				$this->cachedShares[$sabreNode->getId()] = $shares;
161
-			} else {
162
-				return [];
163
-			}
164
-		}
165
-
166
-		return $shares;
167
-	}
168
-
169
-	/**
170
-	 * Adds shares to propfind response
171
-	 *
172
-	 * @param PropFind $propFind propfind object
173
-	 * @param \Sabre\DAV\INode $sabreNode sabre node
174
-	 */
175
-	public function handleGetProperties(
176
-		PropFind $propFind,
177
-		\Sabre\DAV\INode $sabreNode
178
-	) {
179
-		if (!($sabreNode instanceof \OCA\DAV\Connector\Sabre\Node)) {
180
-			return;
181
-		}
182
-
183
-		// need prefetch ?
184
-		if ($sabreNode instanceof \OCA\DAV\Connector\Sabre\Directory
185
-			&& $propFind->getDepth() !== 0
186
-			&& (
187
-				!is_null($propFind->getStatus(self::SHARETYPES_PROPERTYNAME)) ||
188
-				!is_null($propFind->getStatus(self::SHAREES_PROPERTYNAME))
189
-			)
190
-		) {
191
-			$folderNode = $this->userFolder->get($sabreNode->getPath());
192
-
193
-			$this->cachedFolders[] = $sabreNode->getPath();
194
-			$childShares = $this->getSharesFolder($folderNode);
195
-			foreach ($childShares as $id => $shares) {
196
-				$this->cachedShares[$id] = $shares;
197
-			}
198
-		}
199
-
200
-		$propFind->handle(self::SHARETYPES_PROPERTYNAME, function () use ($sabreNode) {
201
-			$shares = $this->getShares($sabreNode);
202
-
203
-			$shareTypes = array_unique(array_map(function (IShare $share) {
204
-				return $share->getShareType();
205
-			}, $shares));
206
-
207
-			return new ShareTypeList($shareTypes);
208
-		});
209
-
210
-		$propFind->handle(self::SHAREES_PROPERTYNAME, function () use ($sabreNode) {
211
-			$shares = $this->getShares($sabreNode);
212
-
213
-			return new ShareeList($shares);
214
-		});
215
-	}
39
+    const NS_OWNCLOUD = 'http://owncloud.org/ns';
40
+    const NS_NEXTCLOUD = 'http://nextcloud.org/ns';
41
+    const SHARETYPES_PROPERTYNAME = '{http://owncloud.org/ns}share-types';
42
+    const SHAREES_PROPERTYNAME = '{http://nextcloud.org/ns}sharees';
43
+
44
+    /**
45
+     * Reference to main server object
46
+     *
47
+     * @var \Sabre\DAV\Server
48
+     */
49
+    private $server;
50
+
51
+    /**
52
+     * @var \OCP\Share\IManager
53
+     */
54
+    private $shareManager;
55
+
56
+    /**
57
+     * @var \Sabre\DAV\Tree
58
+     */
59
+    private $tree;
60
+
61
+    /**
62
+     * @var string
63
+     */
64
+    private $userId;
65
+
66
+    /**
67
+     * @var \OCP\Files\Folder
68
+     */
69
+    private $userFolder;
70
+
71
+    /** @var IShare[] */
72
+    private $cachedShares = [];
73
+
74
+    private $cachedFolders = [];
75
+
76
+    /**
77
+     * @param \Sabre\DAV\Tree $tree tree
78
+     * @param IUserSession $userSession user session
79
+     * @param \OCP\Files\Folder $userFolder user home folder
80
+     * @param \OCP\Share\IManager $shareManager share manager
81
+     */
82
+    public function __construct(
83
+        \Sabre\DAV\Tree $tree,
84
+        IUserSession $userSession,
85
+        \OCP\Files\Folder $userFolder,
86
+        \OCP\Share\IManager $shareManager
87
+    ) {
88
+        $this->tree = $tree;
89
+        $this->shareManager = $shareManager;
90
+        $this->userFolder = $userFolder;
91
+        $this->userId = $userSession->getUser()->getUID();
92
+    }
93
+
94
+    /**
95
+     * This initializes the plugin.
96
+     *
97
+     * This function is called by \Sabre\DAV\Server, after
98
+     * addPlugin is called.
99
+     *
100
+     * This method should set up the required event subscriptions.
101
+     *
102
+     * @param \Sabre\DAV\Server $server
103
+     */
104
+    public function initialize(\Sabre\DAV\Server $server) {
105
+        $server->xml->namespacesMap[self::NS_OWNCLOUD] = 'oc';
106
+        $server->xml->elementMap[self::SHARETYPES_PROPERTYNAME] = ShareTypeList::class;
107
+        $server->protectedProperties[] = self::SHARETYPES_PROPERTYNAME;
108
+        $server->protectedProperties[] = self::SHAREES_PROPERTYNAME;
109
+
110
+        $this->server = $server;
111
+        $this->server->on('propFind', [$this, 'handleGetProperties']);
112
+    }
113
+
114
+    private function getShare(\OCP\Files\Node $node): array {
115
+        $result = [];
116
+        $requestedShareTypes = [
117
+            \OCP\Share::SHARE_TYPE_USER,
118
+            \OCP\Share::SHARE_TYPE_GROUP,
119
+            \OCP\Share::SHARE_TYPE_LINK,
120
+            \OCP\Share::SHARE_TYPE_REMOTE,
121
+            \OCP\Share::SHARE_TYPE_EMAIL,
122
+            \OCP\Share::SHARE_TYPE_ROOM,
123
+            \OCP\Share::SHARE_TYPE_CIRCLE,
124
+        ];
125
+        foreach ($requestedShareTypes as $requestedShareType) {
126
+            $shares = $this->shareManager->getSharesBy(
127
+                $this->userId,
128
+                $requestedShareType,
129
+                $node,
130
+                false,
131
+                -1
132
+            );
133
+            foreach ($shares as $share) {
134
+                $result[] = $share;
135
+            }
136
+        }
137
+        return $result;
138
+    }
139
+
140
+    private function getSharesFolder(\OCP\Files\Folder $node): array {
141
+        return $this->shareManager->getSharesInFolder(
142
+            $this->userId,
143
+            $node,
144
+            true
145
+        );
146
+    }
147
+
148
+    private function getShares(\Sabre\DAV\INode $sabreNode): array {
149
+        if (isset($this->cachedShares[$sabreNode->getId()])) {
150
+            $shares = $this->cachedShares[$sabreNode->getId()];
151
+        } else {
152
+            list($parentPath,) = \Sabre\Uri\split($sabreNode->getPath());
153
+            if ($parentPath === '') {
154
+                $parentPath = '/';
155
+            }
156
+            // if we already cached the folder this file is in we know there are no shares for this file
157
+            if (array_search($parentPath, $this->cachedFolders) === false) {
158
+                $node = $this->userFolder->get($sabreNode->getPath());
159
+                $shares = $this->getShare($node);
160
+                $this->cachedShares[$sabreNode->getId()] = $shares;
161
+            } else {
162
+                return [];
163
+            }
164
+        }
165
+
166
+        return $shares;
167
+    }
168
+
169
+    /**
170
+     * Adds shares to propfind response
171
+     *
172
+     * @param PropFind $propFind propfind object
173
+     * @param \Sabre\DAV\INode $sabreNode sabre node
174
+     */
175
+    public function handleGetProperties(
176
+        PropFind $propFind,
177
+        \Sabre\DAV\INode $sabreNode
178
+    ) {
179
+        if (!($sabreNode instanceof \OCA\DAV\Connector\Sabre\Node)) {
180
+            return;
181
+        }
182
+
183
+        // need prefetch ?
184
+        if ($sabreNode instanceof \OCA\DAV\Connector\Sabre\Directory
185
+            && $propFind->getDepth() !== 0
186
+            && (
187
+                !is_null($propFind->getStatus(self::SHARETYPES_PROPERTYNAME)) ||
188
+                !is_null($propFind->getStatus(self::SHAREES_PROPERTYNAME))
189
+            )
190
+        ) {
191
+            $folderNode = $this->userFolder->get($sabreNode->getPath());
192
+
193
+            $this->cachedFolders[] = $sabreNode->getPath();
194
+            $childShares = $this->getSharesFolder($folderNode);
195
+            foreach ($childShares as $id => $shares) {
196
+                $this->cachedShares[$id] = $shares;
197
+            }
198
+        }
199
+
200
+        $propFind->handle(self::SHARETYPES_PROPERTYNAME, function () use ($sabreNode) {
201
+            $shares = $this->getShares($sabreNode);
202
+
203
+            $shareTypes = array_unique(array_map(function (IShare $share) {
204
+                return $share->getShareType();
205
+            }, $shares));
206
+
207
+            return new ShareTypeList($shareTypes);
208
+        });
209
+
210
+        $propFind->handle(self::SHAREES_PROPERTYNAME, function () use ($sabreNode) {
211
+            $shares = $this->getShares($sabreNode);
212
+
213
+            return new ShareeList($shares);
214
+        });
215
+    }
216 216
 }
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/TagsPlugin.php 1 patch
Indentation   +217 added lines, -217 removed lines patch added patch discarded remove patch
@@ -55,246 +55,246 @@
 block discarded – undo
55 55
 class TagsPlugin extends \Sabre\DAV\ServerPlugin
56 56
 {
57 57
 
58
-	// namespace
59
-	const NS_OWNCLOUD = 'http://owncloud.org/ns';
60
-	const TAGS_PROPERTYNAME = '{http://owncloud.org/ns}tags';
61
-	const FAVORITE_PROPERTYNAME = '{http://owncloud.org/ns}favorite';
62
-	const TAG_FAVORITE = '_$!<Favorite>!$_';
58
+    // namespace
59
+    const NS_OWNCLOUD = 'http://owncloud.org/ns';
60
+    const TAGS_PROPERTYNAME = '{http://owncloud.org/ns}tags';
61
+    const FAVORITE_PROPERTYNAME = '{http://owncloud.org/ns}favorite';
62
+    const TAG_FAVORITE = '_$!<Favorite>!$_';
63 63
 
64
-	/**
65
-	 * Reference to main server object
66
-	 *
67
-	 * @var \Sabre\DAV\Server
68
-	 */
69
-	private $server;
64
+    /**
65
+     * Reference to main server object
66
+     *
67
+     * @var \Sabre\DAV\Server
68
+     */
69
+    private $server;
70 70
 
71
-	/**
72
-	 * @var \OCP\ITagManager
73
-	 */
74
-	private $tagManager;
71
+    /**
72
+     * @var \OCP\ITagManager
73
+     */
74
+    private $tagManager;
75 75
 
76
-	/**
77
-	 * @var \OCP\ITags
78
-	 */
79
-	private $tagger;
76
+    /**
77
+     * @var \OCP\ITags
78
+     */
79
+    private $tagger;
80 80
 
81
-	/**
82
-	 * Array of file id to tags array
83
-	 * The null value means the cache wasn't initialized.
84
-	 *
85
-	 * @var array
86
-	 */
87
-	private $cachedTags;
81
+    /**
82
+     * Array of file id to tags array
83
+     * The null value means the cache wasn't initialized.
84
+     *
85
+     * @var array
86
+     */
87
+    private $cachedTags;
88 88
 
89
-	/**
90
-	 * @var \Sabre\DAV\Tree
91
-	 */
92
-	private $tree;
89
+    /**
90
+     * @var \Sabre\DAV\Tree
91
+     */
92
+    private $tree;
93 93
 
94
-	/**
95
-	 * @param \Sabre\DAV\Tree $tree tree
96
-	 * @param \OCP\ITagManager $tagManager tag manager
97
-	 */
98
-	public function __construct(\Sabre\DAV\Tree $tree, \OCP\ITagManager $tagManager) {
99
-		$this->tree = $tree;
100
-		$this->tagManager = $tagManager;
101
-		$this->tagger = null;
102
-		$this->cachedTags = [];
103
-	}
94
+    /**
95
+     * @param \Sabre\DAV\Tree $tree tree
96
+     * @param \OCP\ITagManager $tagManager tag manager
97
+     */
98
+    public function __construct(\Sabre\DAV\Tree $tree, \OCP\ITagManager $tagManager) {
99
+        $this->tree = $tree;
100
+        $this->tagManager = $tagManager;
101
+        $this->tagger = null;
102
+        $this->cachedTags = [];
103
+    }
104 104
 
105
-	/**
106
-	 * This initializes the plugin.
107
-	 *
108
-	 * This function is called by \Sabre\DAV\Server, after
109
-	 * addPlugin is called.
110
-	 *
111
-	 * This method should set up the required event subscriptions.
112
-	 *
113
-	 * @param \Sabre\DAV\Server $server
114
-	 * @return void
115
-	 */
116
-	public function initialize(\Sabre\DAV\Server $server) {
105
+    /**
106
+     * This initializes the plugin.
107
+     *
108
+     * This function is called by \Sabre\DAV\Server, after
109
+     * addPlugin is called.
110
+     *
111
+     * This method should set up the required event subscriptions.
112
+     *
113
+     * @param \Sabre\DAV\Server $server
114
+     * @return void
115
+     */
116
+    public function initialize(\Sabre\DAV\Server $server) {
117 117
 
118
-		$server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
119
-		$server->xml->elementMap[self::TAGS_PROPERTYNAME] = TagList::class;
118
+        $server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
119
+        $server->xml->elementMap[self::TAGS_PROPERTYNAME] = TagList::class;
120 120
 
121
-		$this->server = $server;
122
-		$this->server->on('propFind', [$this, 'handleGetProperties']);
123
-		$this->server->on('propPatch', [$this, 'handleUpdateProperties']);
124
-	}
121
+        $this->server = $server;
122
+        $this->server->on('propFind', [$this, 'handleGetProperties']);
123
+        $this->server->on('propPatch', [$this, 'handleUpdateProperties']);
124
+    }
125 125
 
126
-	/**
127
-	 * Returns the tagger
128
-	 *
129
-	 * @return \OCP\ITags tagger
130
-	 */
131
-	private function getTagger() {
132
-		if (!$this->tagger) {
133
-			$this->tagger = $this->tagManager->load('files');
134
-		}
135
-		return $this->tagger;
136
-	}
126
+    /**
127
+     * Returns the tagger
128
+     *
129
+     * @return \OCP\ITags tagger
130
+     */
131
+    private function getTagger() {
132
+        if (!$this->tagger) {
133
+            $this->tagger = $this->tagManager->load('files');
134
+        }
135
+        return $this->tagger;
136
+    }
137 137
 
138
-	/**
139
-	 * Returns tags and favorites.
140
-	 *
141
-	 * @param integer $fileId file id
142
-	 * @return array list($tags, $favorite) with $tags as tag array
143
-	 * and $favorite is a boolean whether the file was favorited
144
-	 */
145
-	private function getTagsAndFav($fileId) {
146
-		$isFav = false;
147
-		$tags = $this->getTags($fileId);
148
-		if ($tags) {
149
-			$favPos = array_search(self::TAG_FAVORITE, $tags);
150
-			if ($favPos !== false) {
151
-				$isFav = true;
152
-				unset($tags[$favPos]);
153
-			}
154
-		}
155
-		return [$tags, $isFav];
156
-	}
138
+    /**
139
+     * Returns tags and favorites.
140
+     *
141
+     * @param integer $fileId file id
142
+     * @return array list($tags, $favorite) with $tags as tag array
143
+     * and $favorite is a boolean whether the file was favorited
144
+     */
145
+    private function getTagsAndFav($fileId) {
146
+        $isFav = false;
147
+        $tags = $this->getTags($fileId);
148
+        if ($tags) {
149
+            $favPos = array_search(self::TAG_FAVORITE, $tags);
150
+            if ($favPos !== false) {
151
+                $isFav = true;
152
+                unset($tags[$favPos]);
153
+            }
154
+        }
155
+        return [$tags, $isFav];
156
+    }
157 157
 
158
-	/**
159
-	 * Returns tags for the given file id
160
-	 *
161
-	 * @param integer $fileId file id
162
-	 * @return array list of tags for that file
163
-	 */
164
-	private function getTags($fileId) {
165
-		if (isset($this->cachedTags[$fileId])) {
166
-			return $this->cachedTags[$fileId];
167
-		} else {
168
-			$tags = $this->getTagger()->getTagsForObjects([$fileId]);
169
-			if ($tags !== false) {
170
-				if (empty($tags)) {
171
-					return [];
172
-				}
173
-				return current($tags);
174
-			}
175
-		}
176
-		return null;
177
-	}
158
+    /**
159
+     * Returns tags for the given file id
160
+     *
161
+     * @param integer $fileId file id
162
+     * @return array list of tags for that file
163
+     */
164
+    private function getTags($fileId) {
165
+        if (isset($this->cachedTags[$fileId])) {
166
+            return $this->cachedTags[$fileId];
167
+        } else {
168
+            $tags = $this->getTagger()->getTagsForObjects([$fileId]);
169
+            if ($tags !== false) {
170
+                if (empty($tags)) {
171
+                    return [];
172
+                }
173
+                return current($tags);
174
+            }
175
+        }
176
+        return null;
177
+    }
178 178
 
179
-	/**
180
-	 * Updates the tags of the given file id
181
-	 *
182
-	 * @param int $fileId
183
-	 * @param array $tags array of tag strings
184
-	 */
185
-	private function updateTags($fileId, $tags) {
186
-		$tagger = $this->getTagger();
187
-		$currentTags = $this->getTags($fileId);
179
+    /**
180
+     * Updates the tags of the given file id
181
+     *
182
+     * @param int $fileId
183
+     * @param array $tags array of tag strings
184
+     */
185
+    private function updateTags($fileId, $tags) {
186
+        $tagger = $this->getTagger();
187
+        $currentTags = $this->getTags($fileId);
188 188
 
189
-		$newTags = array_diff($tags, $currentTags);
190
-		foreach ($newTags as $tag) {
191
-			if ($tag === self::TAG_FAVORITE) {
192
-				continue;
193
-			}
194
-			$tagger->tagAs($fileId, $tag);
195
-		}
196
-		$deletedTags = array_diff($currentTags, $tags);
197
-		foreach ($deletedTags as $tag) {
198
-			if ($tag === self::TAG_FAVORITE) {
199
-				continue;
200
-			}
201
-			$tagger->unTag($fileId, $tag);
202
-		}
203
-	}
189
+        $newTags = array_diff($tags, $currentTags);
190
+        foreach ($newTags as $tag) {
191
+            if ($tag === self::TAG_FAVORITE) {
192
+                continue;
193
+            }
194
+            $tagger->tagAs($fileId, $tag);
195
+        }
196
+        $deletedTags = array_diff($currentTags, $tags);
197
+        foreach ($deletedTags as $tag) {
198
+            if ($tag === self::TAG_FAVORITE) {
199
+                continue;
200
+            }
201
+            $tagger->unTag($fileId, $tag);
202
+        }
203
+    }
204 204
 
205
-	/**
206
-	 * Adds tags and favorites properties to the response,
207
-	 * if requested.
208
-	 *
209
-	 * @param PropFind $propFind
210
-	 * @param \Sabre\DAV\INode $node
211
-	 * @return void
212
-	 */
213
-	public function handleGetProperties(
214
-		PropFind $propFind,
215
-		\Sabre\DAV\INode $node
216
-	) {
217
-		if (!($node instanceof \OCA\DAV\Connector\Sabre\Node)) {
218
-			return;
219
-		}
205
+    /**
206
+     * Adds tags and favorites properties to the response,
207
+     * if requested.
208
+     *
209
+     * @param PropFind $propFind
210
+     * @param \Sabre\DAV\INode $node
211
+     * @return void
212
+     */
213
+    public function handleGetProperties(
214
+        PropFind $propFind,
215
+        \Sabre\DAV\INode $node
216
+    ) {
217
+        if (!($node instanceof \OCA\DAV\Connector\Sabre\Node)) {
218
+            return;
219
+        }
220 220
 
221
-		// need prefetch ?
222
-		if ($node instanceof \OCA\DAV\Connector\Sabre\Directory
223
-			&& $propFind->getDepth() !== 0
224
-			&& (!is_null($propFind->getStatus(self::TAGS_PROPERTYNAME))
225
-			|| !is_null($propFind->getStatus(self::FAVORITE_PROPERTYNAME))
226
-		)) {
227
-			// note: pre-fetching only supported for depth <= 1
228
-			$folderContent = $node->getChildren();
229
-			$fileIds[] = (int)$node->getId();
230
-			foreach ($folderContent as $info) {
231
-				$fileIds[] = (int)$info->getId();
232
-			}
233
-			$tags = $this->getTagger()->getTagsForObjects($fileIds);
234
-			if ($tags === false) {
235
-				// the tags API returns false on error...
236
-				$tags = [];
237
-			}
221
+        // need prefetch ?
222
+        if ($node instanceof \OCA\DAV\Connector\Sabre\Directory
223
+            && $propFind->getDepth() !== 0
224
+            && (!is_null($propFind->getStatus(self::TAGS_PROPERTYNAME))
225
+            || !is_null($propFind->getStatus(self::FAVORITE_PROPERTYNAME))
226
+        )) {
227
+            // note: pre-fetching only supported for depth <= 1
228
+            $folderContent = $node->getChildren();
229
+            $fileIds[] = (int)$node->getId();
230
+            foreach ($folderContent as $info) {
231
+                $fileIds[] = (int)$info->getId();
232
+            }
233
+            $tags = $this->getTagger()->getTagsForObjects($fileIds);
234
+            if ($tags === false) {
235
+                // the tags API returns false on error...
236
+                $tags = [];
237
+            }
238 238
 
239
-			$this->cachedTags = $this->cachedTags + $tags;
240
-			$emptyFileIds = array_diff($fileIds, array_keys($tags));
241
-			// also cache the ones that were not found
242
-			foreach ($emptyFileIds as $fileId) {
243
-				$this->cachedTags[$fileId] = [];
244
-			}
245
-		}
239
+            $this->cachedTags = $this->cachedTags + $tags;
240
+            $emptyFileIds = array_diff($fileIds, array_keys($tags));
241
+            // also cache the ones that were not found
242
+            foreach ($emptyFileIds as $fileId) {
243
+                $this->cachedTags[$fileId] = [];
244
+            }
245
+        }
246 246
 
247
-		$isFav = null;
247
+        $isFav = null;
248 248
 
249
-		$propFind->handle(self::TAGS_PROPERTYNAME, function () use (&$isFav, $node) {
250
-			list($tags, $isFav) = $this->getTagsAndFav($node->getId());
251
-			return new TagList($tags);
252
-		});
249
+        $propFind->handle(self::TAGS_PROPERTYNAME, function () use (&$isFav, $node) {
250
+            list($tags, $isFav) = $this->getTagsAndFav($node->getId());
251
+            return new TagList($tags);
252
+        });
253 253
 
254
-		$propFind->handle(self::FAVORITE_PROPERTYNAME, function () use ($isFav, $node) {
255
-			if (is_null($isFav)) {
256
-				list(, $isFav) = $this->getTagsAndFav($node->getId());
257
-			}
258
-			if ($isFav) {
259
-				return 1;
260
-			} else {
261
-				return 0;
262
-			}
263
-		});
264
-	}
254
+        $propFind->handle(self::FAVORITE_PROPERTYNAME, function () use ($isFav, $node) {
255
+            if (is_null($isFav)) {
256
+                list(, $isFav) = $this->getTagsAndFav($node->getId());
257
+            }
258
+            if ($isFav) {
259
+                return 1;
260
+            } else {
261
+                return 0;
262
+            }
263
+        });
264
+    }
265 265
 
266
-	/**
267
-	 * Updates tags and favorites properties, if applicable.
268
-	 *
269
-	 * @param string $path
270
-	 * @param PropPatch $propPatch
271
-	 *
272
-	 * @return void
273
-	 */
274
-	public function handleUpdateProperties($path, PropPatch $propPatch) {
275
-		$node = $this->tree->getNodeForPath($path);
276
-		if (!($node instanceof \OCA\DAV\Connector\Sabre\Node)) {
277
-			return;
278
-		}
266
+    /**
267
+     * Updates tags and favorites properties, if applicable.
268
+     *
269
+     * @param string $path
270
+     * @param PropPatch $propPatch
271
+     *
272
+     * @return void
273
+     */
274
+    public function handleUpdateProperties($path, PropPatch $propPatch) {
275
+        $node = $this->tree->getNodeForPath($path);
276
+        if (!($node instanceof \OCA\DAV\Connector\Sabre\Node)) {
277
+            return;
278
+        }
279 279
 
280
-		$propPatch->handle(self::TAGS_PROPERTYNAME, function ($tagList) use ($node) {
281
-			$this->updateTags($node->getId(), $tagList->getTags());
282
-			return true;
283
-		});
280
+        $propPatch->handle(self::TAGS_PROPERTYNAME, function ($tagList) use ($node) {
281
+            $this->updateTags($node->getId(), $tagList->getTags());
282
+            return true;
283
+        });
284 284
 
285
-		$propPatch->handle(self::FAVORITE_PROPERTYNAME, function ($favState) use ($node) {
286
-			if ((int)$favState === 1 || $favState === 'true') {
287
-				$this->getTagger()->tagAs($node->getId(), self::TAG_FAVORITE);
288
-			} else {
289
-				$this->getTagger()->unTag($node->getId(), self::TAG_FAVORITE);
290
-			}
285
+        $propPatch->handle(self::FAVORITE_PROPERTYNAME, function ($favState) use ($node) {
286
+            if ((int)$favState === 1 || $favState === 'true') {
287
+                $this->getTagger()->tagAs($node->getId(), self::TAG_FAVORITE);
288
+            } else {
289
+                $this->getTagger()->unTag($node->getId(), self::TAG_FAVORITE);
290
+            }
291 291
 
292
-			if (is_null($favState)) {
293
-				// confirm deletion
294
-				return 204;
295
-			}
292
+            if (is_null($favState)) {
293
+                // confirm deletion
294
+                return 204;
295
+            }
296 296
 
297
-			return 200;
298
-		});
299
-	}
297
+            return 200;
298
+        });
299
+    }
300 300
 }
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/FilesPlugin.php 1 patch
Indentation   +466 added lines, -466 removed lines patch added patch discarded remove patch
@@ -53,470 +53,470 @@
 block discarded – undo
53 53
 
54 54
 class FilesPlugin extends ServerPlugin {
55 55
 
56
-	// namespace
57
-	const NS_OWNCLOUD = 'http://owncloud.org/ns';
58
-	const NS_NEXTCLOUD = 'http://nextcloud.org/ns';
59
-	const FILEID_PROPERTYNAME = '{http://owncloud.org/ns}id';
60
-	const INTERNAL_FILEID_PROPERTYNAME = '{http://owncloud.org/ns}fileid';
61
-	const PERMISSIONS_PROPERTYNAME = '{http://owncloud.org/ns}permissions';
62
-	const SHARE_PERMISSIONS_PROPERTYNAME = '{http://open-collaboration-services.org/ns}share-permissions';
63
-	const OCM_SHARE_PERMISSIONS_PROPERTYNAME = '{http://open-cloud-mesh.org/ns}share-permissions';
64
-	const DOWNLOADURL_PROPERTYNAME = '{http://owncloud.org/ns}downloadURL';
65
-	const SIZE_PROPERTYNAME = '{http://owncloud.org/ns}size';
66
-	const GETETAG_PROPERTYNAME = '{DAV:}getetag';
67
-	const LASTMODIFIED_PROPERTYNAME = '{DAV:}lastmodified';
68
-	const OWNER_ID_PROPERTYNAME = '{http://owncloud.org/ns}owner-id';
69
-	const OWNER_DISPLAY_NAME_PROPERTYNAME = '{http://owncloud.org/ns}owner-display-name';
70
-	const CHECKSUMS_PROPERTYNAME = '{http://owncloud.org/ns}checksums';
71
-	const DATA_FINGERPRINT_PROPERTYNAME = '{http://owncloud.org/ns}data-fingerprint';
72
-	const HAS_PREVIEW_PROPERTYNAME = '{http://nextcloud.org/ns}has-preview';
73
-	const MOUNT_TYPE_PROPERTYNAME = '{http://nextcloud.org/ns}mount-type';
74
-	const IS_ENCRYPTED_PROPERTYNAME = '{http://nextcloud.org/ns}is-encrypted';
75
-	const METADATA_ETAG_PROPERTYNAME = '{http://nextcloud.org/ns}metadata_etag';
76
-	const UPLOAD_TIME_PROPERTYNAME = '{http://nextcloud.org/ns}upload_time';
77
-	const CREATION_TIME_PROPERTYNAME = '{http://nextcloud.org/ns}creation_time';
78
-	const SHARE_NOTE = '{http://nextcloud.org/ns}note';
79
-
80
-	/**
81
-	 * Reference to main server object
82
-	 *
83
-	 * @var \Sabre\DAV\Server
84
-	 */
85
-	private $server;
86
-
87
-	/**
88
-	 * @var Tree
89
-	 */
90
-	private $tree;
91
-
92
-	/**
93
-	 * Whether this is public webdav.
94
-	 * If true, some returned information will be stripped off.
95
-	 *
96
-	 * @var bool
97
-	 */
98
-	private $isPublic;
99
-
100
-	/**
101
-	 * @var bool
102
-	 */
103
-	private $downloadAttachment;
104
-
105
-	/**
106
-	 * @var IConfig
107
-	 */
108
-	private $config;
109
-
110
-	/**
111
-	 * @var IRequest
112
-	 */
113
-	private $request;
114
-
115
-	/**
116
-	 * @var IPreview
117
-	 */
118
-	private $previewManager;
119
-
120
-	/**
121
-	 * @param Tree $tree
122
-	 * @param IConfig $config
123
-	 * @param IRequest $request
124
-	 * @param IPreview $previewManager
125
-	 * @param bool $isPublic
126
-	 * @param bool $downloadAttachment
127
-	 */
128
-	public function __construct(Tree $tree,
129
-								IConfig $config,
130
-								IRequest $request,
131
-								IPreview $previewManager,
132
-								$isPublic = false,
133
-								$downloadAttachment = true) {
134
-		$this->tree = $tree;
135
-		$this->config = $config;
136
-		$this->request = $request;
137
-		$this->isPublic = $isPublic;
138
-		$this->downloadAttachment = $downloadAttachment;
139
-		$this->previewManager = $previewManager;
140
-	}
141
-
142
-	/**
143
-	 * This initializes the plugin.
144
-	 *
145
-	 * This function is called by \Sabre\DAV\Server, after
146
-	 * addPlugin is called.
147
-	 *
148
-	 * This method should set up the required event subscriptions.
149
-	 *
150
-	 * @param \Sabre\DAV\Server $server
151
-	 * @return void
152
-	 */
153
-	public function initialize(\Sabre\DAV\Server $server) {
154
-		$server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
155
-		$server->xml->namespaceMap[self::NS_NEXTCLOUD] = 'nc';
156
-		$server->protectedProperties[] = self::FILEID_PROPERTYNAME;
157
-		$server->protectedProperties[] = self::INTERNAL_FILEID_PROPERTYNAME;
158
-		$server->protectedProperties[] = self::PERMISSIONS_PROPERTYNAME;
159
-		$server->protectedProperties[] = self::SHARE_PERMISSIONS_PROPERTYNAME;
160
-		$server->protectedProperties[] = self::OCM_SHARE_PERMISSIONS_PROPERTYNAME;
161
-		$server->protectedProperties[] = self::SIZE_PROPERTYNAME;
162
-		$server->protectedProperties[] = self::DOWNLOADURL_PROPERTYNAME;
163
-		$server->protectedProperties[] = self::OWNER_ID_PROPERTYNAME;
164
-		$server->protectedProperties[] = self::OWNER_DISPLAY_NAME_PROPERTYNAME;
165
-		$server->protectedProperties[] = self::CHECKSUMS_PROPERTYNAME;
166
-		$server->protectedProperties[] = self::DATA_FINGERPRINT_PROPERTYNAME;
167
-		$server->protectedProperties[] = self::HAS_PREVIEW_PROPERTYNAME;
168
-		$server->protectedProperties[] = self::MOUNT_TYPE_PROPERTYNAME;
169
-		$server->protectedProperties[] = self::IS_ENCRYPTED_PROPERTYNAME;
170
-		$server->protectedProperties[] = self::SHARE_NOTE;
171
-
172
-		// normally these cannot be changed (RFC4918), but we want them modifiable through PROPPATCH
173
-		$allowedProperties = ['{DAV:}getetag'];
174
-		$server->protectedProperties = array_diff($server->protectedProperties, $allowedProperties);
175
-
176
-		$this->server = $server;
177
-		$this->server->on('propFind', [$this, 'handleGetProperties']);
178
-		$this->server->on('propPatch', [$this, 'handleUpdateProperties']);
179
-		$this->server->on('afterBind', [$this, 'sendFileIdHeader']);
180
-		$this->server->on('afterWriteContent', [$this, 'sendFileIdHeader']);
181
-		$this->server->on('afterMethod:GET', [$this,'httpGet']);
182
-		$this->server->on('afterMethod:GET', [$this, 'handleDownloadToken']);
183
-		$this->server->on('afterResponse', function ($request, ResponseInterface $response) {
184
-			$body = $response->getBody();
185
-			if (is_resource($body)) {
186
-				fclose($body);
187
-			}
188
-		});
189
-		$this->server->on('beforeMove', [$this, 'checkMove']);
190
-	}
191
-
192
-	/**
193
-	 * Plugin that checks if a move can actually be performed.
194
-	 *
195
-	 * @param string $source source path
196
-	 * @param string $destination destination path
197
-	 * @throws Forbidden
198
-	 * @throws NotFound
199
-	 */
200
-	function checkMove($source, $destination) {
201
-		$sourceNode = $this->tree->getNodeForPath($source);
202
-		if (!$sourceNode instanceof Node) {
203
-			return;
204
-		}
205
-		list($sourceDir,) = \Sabre\Uri\split($source);
206
-		list($destinationDir,) = \Sabre\Uri\split($destination);
207
-
208
-		if ($sourceDir !== $destinationDir) {
209
-			$sourceNodeFileInfo = $sourceNode->getFileInfo();
210
-			if ($sourceNodeFileInfo === null) {
211
-				throw new NotFound($source . ' does not exist');
212
-			}
213
-
214
-			if (!$sourceNodeFileInfo->isDeletable()) {
215
-				throw new Forbidden($source . " cannot be deleted");
216
-			}
217
-		}
218
-	}
219
-
220
-	/**
221
-	 * This sets a cookie to be able to recognize the start of the download
222
-	 * the content must not be longer than 32 characters and must only contain
223
-	 * alphanumeric characters
224
-	 *
225
-	 * @param RequestInterface $request
226
-	 * @param ResponseInterface $response
227
-	 */
228
-	function handleDownloadToken(RequestInterface $request, ResponseInterface $response) {
229
-		$queryParams = $request->getQueryParameters();
230
-
231
-		/**
232
-		 * this sets a cookie to be able to recognize the start of the download
233
-		 * the content must not be longer than 32 characters and must only contain
234
-		 * alphanumeric characters
235
-		 */
236
-		if (isset($queryParams['downloadStartSecret'])) {
237
-			$token = $queryParams['downloadStartSecret'];
238
-			if (!isset($token[32])
239
-				&& preg_match('!^[a-zA-Z0-9]+$!', $token) === 1) {
240
-				// FIXME: use $response->setHeader() instead
241
-				setcookie('ocDownloadStarted', $token, time() + 20, '/');
242
-			}
243
-		}
244
-	}
245
-
246
-	/**
247
-	 * Add headers to file download
248
-	 *
249
-	 * @param RequestInterface $request
250
-	 * @param ResponseInterface $response
251
-	 */
252
-	function httpGet(RequestInterface $request, ResponseInterface $response) {
253
-		// Only handle valid files
254
-		$node = $this->tree->getNodeForPath($request->getPath());
255
-		if (!($node instanceof IFile)) return;
256
-
257
-		// adds a 'Content-Disposition: attachment' header in case no disposition
258
-		// header has been set before
259
-		if ($this->downloadAttachment &&
260
-			$response->getHeader('Content-Disposition') === null) {
261
-			$filename = $node->getName();
262
-			if ($this->request->isUserAgent(
263
-				[
264
-					Request::USER_AGENT_IE,
265
-					Request::USER_AGENT_ANDROID_MOBILE_CHROME,
266
-					Request::USER_AGENT_FREEBOX,
267
-				])) {
268
-				$response->addHeader('Content-Disposition', 'attachment; filename="' . rawurlencode($filename) . '"');
269
-			} else {
270
-				$response->addHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . rawurlencode($filename)
271
-													 . '; filename="' . rawurlencode($filename) . '"');
272
-			}
273
-		}
274
-
275
-		if ($node instanceof \OCA\DAV\Connector\Sabre\File) {
276
-			//Add OC-Checksum header
277
-			/** @var $node File */
278
-			$checksum = $node->getChecksum();
279
-			if ($checksum !== null && $checksum !== '') {
280
-				$response->addHeader('OC-Checksum', $checksum);
281
-			}
282
-		}
283
-	}
284
-
285
-	/**
286
-	 * Adds all ownCloud-specific properties
287
-	 *
288
-	 * @param PropFind $propFind
289
-	 * @param \Sabre\DAV\INode $node
290
-	 * @return void
291
-	 */
292
-	public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node) {
293
-
294
-		$httpRequest = $this->server->httpRequest;
295
-
296
-		if ($node instanceof \OCA\DAV\Connector\Sabre\Node) {
297
-			/**
298
-			 * This was disabled, because it made dir listing throw an exception,
299
-			 * so users were unable to navigate into folders where one subitem
300
-			 * is blocked by the files_accesscontrol app, see:
301
-			 * https://github.com/nextcloud/files_accesscontrol/issues/65
302
-			 * if (!$node->getFileInfo()->isReadable()) {
303
-			 *     // avoid detecting files through this means
304
-			 *     throw new NotFound();
305
-			 * }
306
-			 */
307
-
308
-			$propFind->handle(self::FILEID_PROPERTYNAME, function () use ($node) {
309
-				return $node->getFileId();
310
-			});
311
-
312
-			$propFind->handle(self::INTERNAL_FILEID_PROPERTYNAME, function () use ($node) {
313
-				return $node->getInternalFileId();
314
-			});
315
-
316
-			$propFind->handle(self::PERMISSIONS_PROPERTYNAME, function () use ($node) {
317
-				$perms = $node->getDavPermissions();
318
-				if ($this->isPublic) {
319
-					// remove mount information
320
-					$perms = str_replace(['S', 'M'], '', $perms);
321
-				}
322
-				return $perms;
323
-			});
324
-
325
-			$propFind->handle(self::SHARE_PERMISSIONS_PROPERTYNAME, function () use ($node, $httpRequest) {
326
-				return $node->getSharePermissions(
327
-					$httpRequest->getRawServerValue('PHP_AUTH_USER')
328
-				);
329
-			});
330
-
331
-			$propFind->handle(self::OCM_SHARE_PERMISSIONS_PROPERTYNAME, function () use ($node, $httpRequest) {
332
-				$ncPermissions = $node->getSharePermissions(
333
-					$httpRequest->getRawServerValue('PHP_AUTH_USER')
334
-				);
335
-				$ocmPermissions = $this->ncPermissions2ocmPermissions($ncPermissions);
336
-				return json_encode($ocmPermissions);
337
-			});
338
-
339
-			$propFind->handle(self::GETETAG_PROPERTYNAME, function () use ($node) {
340
-				return $node->getETag();
341
-			});
342
-
343
-			$propFind->handle(self::OWNER_ID_PROPERTYNAME, function () use ($node) {
344
-				$owner = $node->getOwner();
345
-				if (!$owner) {
346
-					return null;
347
-				} else {
348
-					return $owner->getUID();
349
-				}
350
-			});
351
-			$propFind->handle(self::OWNER_DISPLAY_NAME_PROPERTYNAME, function () use ($node) {
352
-				$owner = $node->getOwner();
353
-				if (!$owner) {
354
-					return null;
355
-				} else {
356
-					return $owner->getDisplayName();
357
-				}
358
-			});
359
-
360
-			$propFind->handle(self::HAS_PREVIEW_PROPERTYNAME, function () use ($node) {
361
-				return json_encode($this->previewManager->isAvailable($node->getFileInfo()));
362
-			});
363
-			$propFind->handle(self::SIZE_PROPERTYNAME, function () use ($node) {
364
-				return $node->getSize();
365
-			});
366
-			$propFind->handle(self::MOUNT_TYPE_PROPERTYNAME, function () use ($node) {
367
-				return $node->getFileInfo()->getMountPoint()->getMountType();
368
-			});
369
-
370
-			$propFind->handle(self::SHARE_NOTE, function () use ($node, $httpRequest) {
371
-				return $node->getNoteFromShare(
372
-					$httpRequest->getRawServerValue('PHP_AUTH_USER')
373
-				);
374
-			});
375
-		}
376
-
377
-		if ($node instanceof \OCA\DAV\Connector\Sabre\Node) {
378
-			$propFind->handle(self::DATA_FINGERPRINT_PROPERTYNAME, function () use ($node) {
379
-				return $this->config->getSystemValue('data-fingerprint', '');
380
-			});
381
-		}
382
-
383
-		if ($node instanceof \OCA\DAV\Connector\Sabre\File) {
384
-			$propFind->handle(self::DOWNLOADURL_PROPERTYNAME, function () use ($node) {
385
-				/** @var $node \OCA\DAV\Connector\Sabre\File */
386
-				try {
387
-					$directDownloadUrl = $node->getDirectDownload();
388
-					if (isset($directDownloadUrl['url'])) {
389
-						return $directDownloadUrl['url'];
390
-					}
391
-				} catch (StorageNotAvailableException $e) {
392
-					return false;
393
-				} catch (ForbiddenException $e) {
394
-					return false;
395
-				}
396
-				return false;
397
-			});
398
-
399
-			$propFind->handle(self::CHECKSUMS_PROPERTYNAME, function () use ($node) {
400
-				$checksum = $node->getChecksum();
401
-				if ($checksum === null || $checksum === '') {
402
-					return null;
403
-				}
404
-
405
-				return new ChecksumList($checksum);
406
-			});
407
-
408
-			$propFind->handle(self::CREATION_TIME_PROPERTYNAME, function () use ($node) {
409
-				return $node->getFileInfo()->getCreationTime();
410
-			});
411
-
412
-			$propFind->handle(self::UPLOAD_TIME_PROPERTYNAME, function () use ($node) {
413
-				return $node->getFileInfo()->getUploadTime();
414
-			});
415
-
416
-		}
417
-
418
-		if ($node instanceof \OCA\DAV\Connector\Sabre\Directory) {
419
-			$propFind->handle(self::SIZE_PROPERTYNAME, function () use ($node) {
420
-				return $node->getSize();
421
-			});
422
-
423
-			$propFind->handle(self::IS_ENCRYPTED_PROPERTYNAME, function () use ($node) {
424
-				return $node->getFileInfo()->isEncrypted() ? '1' : '0';
425
-			});
426
-		}
427
-	}
428
-
429
-	/**
430
-	 * translate Nextcloud permissions to OCM Permissions
431
-	 *
432
-	 * @param $ncPermissions
433
-	 * @return array
434
-	 */
435
-	protected function ncPermissions2ocmPermissions($ncPermissions) {
436
-
437
-		$ocmPermissions = [];
438
-
439
-		if ($ncPermissions & Constants::PERMISSION_SHARE) {
440
-			$ocmPermissions[] = 'share';
441
-		}
442
-
443
-		if ($ncPermissions & Constants::PERMISSION_READ) {
444
-			$ocmPermissions[] = 'read';
445
-		}
446
-
447
-		if (($ncPermissions & Constants::PERMISSION_CREATE) ||
448
-			($ncPermissions & Constants::PERMISSION_UPDATE)) {
449
-			$ocmPermissions[] = 'write';
450
-		}
451
-
452
-		return $ocmPermissions;
453
-
454
-	}
455
-
456
-	/**
457
-	 * Update ownCloud-specific properties
458
-	 *
459
-	 * @param string $path
460
-	 * @param PropPatch $propPatch
461
-	 *
462
-	 * @return void
463
-	 */
464
-	public function handleUpdateProperties($path, PropPatch $propPatch) {
465
-		$node = $this->tree->getNodeForPath($path);
466
-		if (!($node instanceof \OCA\DAV\Connector\Sabre\Node)) {
467
-			return;
468
-		}
469
-
470
-		$propPatch->handle(self::LASTMODIFIED_PROPERTYNAME, function ($time) use ($node) {
471
-			if (empty($time)) {
472
-				return false;
473
-			}
474
-			$node->touch($time);
475
-			return true;
476
-		});
477
-		$propPatch->handle(self::GETETAG_PROPERTYNAME, function ($etag) use ($node) {
478
-			if (empty($etag)) {
479
-				return false;
480
-			}
481
-			if ($node->setEtag($etag) !== -1) {
482
-				return true;
483
-			}
484
-			return false;
485
-		});
486
-		$propPatch->handle(self::CREATION_TIME_PROPERTYNAME, function ($time) use ($node) {
487
-			if (empty($time)) {
488
-				return false;
489
-			}
490
-			$node->setCreationTime((int) $time);
491
-			return true;
492
-		});
493
-	}
494
-
495
-	/**
496
-	 * @param string $filePath
497
-	 * @param \Sabre\DAV\INode $node
498
-	 * @throws \Sabre\DAV\Exception\BadRequest
499
-	 */
500
-	public function sendFileIdHeader($filePath, \Sabre\DAV\INode $node = null) {
501
-		// chunked upload handling
502
-		if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
503
-			list($path, $name) = \Sabre\Uri\split($filePath);
504
-			$info = \OC_FileChunking::decodeName($name);
505
-			if (!empty($info)) {
506
-				$filePath = $path . '/' . $info['name'];
507
-			}
508
-		}
509
-
510
-		// we get the node for the given $filePath here because in case of afterCreateFile $node is the parent folder
511
-		if (!$this->server->tree->nodeExists($filePath)) {
512
-			return;
513
-		}
514
-		$node = $this->server->tree->getNodeForPath($filePath);
515
-		if ($node instanceof \OCA\DAV\Connector\Sabre\Node) {
516
-			$fileId = $node->getFileId();
517
-			if (!is_null($fileId)) {
518
-				$this->server->httpResponse->setHeader('OC-FileId', $fileId);
519
-			}
520
-		}
521
-	}
56
+    // namespace
57
+    const NS_OWNCLOUD = 'http://owncloud.org/ns';
58
+    const NS_NEXTCLOUD = 'http://nextcloud.org/ns';
59
+    const FILEID_PROPERTYNAME = '{http://owncloud.org/ns}id';
60
+    const INTERNAL_FILEID_PROPERTYNAME = '{http://owncloud.org/ns}fileid';
61
+    const PERMISSIONS_PROPERTYNAME = '{http://owncloud.org/ns}permissions';
62
+    const SHARE_PERMISSIONS_PROPERTYNAME = '{http://open-collaboration-services.org/ns}share-permissions';
63
+    const OCM_SHARE_PERMISSIONS_PROPERTYNAME = '{http://open-cloud-mesh.org/ns}share-permissions';
64
+    const DOWNLOADURL_PROPERTYNAME = '{http://owncloud.org/ns}downloadURL';
65
+    const SIZE_PROPERTYNAME = '{http://owncloud.org/ns}size';
66
+    const GETETAG_PROPERTYNAME = '{DAV:}getetag';
67
+    const LASTMODIFIED_PROPERTYNAME = '{DAV:}lastmodified';
68
+    const OWNER_ID_PROPERTYNAME = '{http://owncloud.org/ns}owner-id';
69
+    const OWNER_DISPLAY_NAME_PROPERTYNAME = '{http://owncloud.org/ns}owner-display-name';
70
+    const CHECKSUMS_PROPERTYNAME = '{http://owncloud.org/ns}checksums';
71
+    const DATA_FINGERPRINT_PROPERTYNAME = '{http://owncloud.org/ns}data-fingerprint';
72
+    const HAS_PREVIEW_PROPERTYNAME = '{http://nextcloud.org/ns}has-preview';
73
+    const MOUNT_TYPE_PROPERTYNAME = '{http://nextcloud.org/ns}mount-type';
74
+    const IS_ENCRYPTED_PROPERTYNAME = '{http://nextcloud.org/ns}is-encrypted';
75
+    const METADATA_ETAG_PROPERTYNAME = '{http://nextcloud.org/ns}metadata_etag';
76
+    const UPLOAD_TIME_PROPERTYNAME = '{http://nextcloud.org/ns}upload_time';
77
+    const CREATION_TIME_PROPERTYNAME = '{http://nextcloud.org/ns}creation_time';
78
+    const SHARE_NOTE = '{http://nextcloud.org/ns}note';
79
+
80
+    /**
81
+     * Reference to main server object
82
+     *
83
+     * @var \Sabre\DAV\Server
84
+     */
85
+    private $server;
86
+
87
+    /**
88
+     * @var Tree
89
+     */
90
+    private $tree;
91
+
92
+    /**
93
+     * Whether this is public webdav.
94
+     * If true, some returned information will be stripped off.
95
+     *
96
+     * @var bool
97
+     */
98
+    private $isPublic;
99
+
100
+    /**
101
+     * @var bool
102
+     */
103
+    private $downloadAttachment;
104
+
105
+    /**
106
+     * @var IConfig
107
+     */
108
+    private $config;
109
+
110
+    /**
111
+     * @var IRequest
112
+     */
113
+    private $request;
114
+
115
+    /**
116
+     * @var IPreview
117
+     */
118
+    private $previewManager;
119
+
120
+    /**
121
+     * @param Tree $tree
122
+     * @param IConfig $config
123
+     * @param IRequest $request
124
+     * @param IPreview $previewManager
125
+     * @param bool $isPublic
126
+     * @param bool $downloadAttachment
127
+     */
128
+    public function __construct(Tree $tree,
129
+                                IConfig $config,
130
+                                IRequest $request,
131
+                                IPreview $previewManager,
132
+                                $isPublic = false,
133
+                                $downloadAttachment = true) {
134
+        $this->tree = $tree;
135
+        $this->config = $config;
136
+        $this->request = $request;
137
+        $this->isPublic = $isPublic;
138
+        $this->downloadAttachment = $downloadAttachment;
139
+        $this->previewManager = $previewManager;
140
+    }
141
+
142
+    /**
143
+     * This initializes the plugin.
144
+     *
145
+     * This function is called by \Sabre\DAV\Server, after
146
+     * addPlugin is called.
147
+     *
148
+     * This method should set up the required event subscriptions.
149
+     *
150
+     * @param \Sabre\DAV\Server $server
151
+     * @return void
152
+     */
153
+    public function initialize(\Sabre\DAV\Server $server) {
154
+        $server->xml->namespaceMap[self::NS_OWNCLOUD] = 'oc';
155
+        $server->xml->namespaceMap[self::NS_NEXTCLOUD] = 'nc';
156
+        $server->protectedProperties[] = self::FILEID_PROPERTYNAME;
157
+        $server->protectedProperties[] = self::INTERNAL_FILEID_PROPERTYNAME;
158
+        $server->protectedProperties[] = self::PERMISSIONS_PROPERTYNAME;
159
+        $server->protectedProperties[] = self::SHARE_PERMISSIONS_PROPERTYNAME;
160
+        $server->protectedProperties[] = self::OCM_SHARE_PERMISSIONS_PROPERTYNAME;
161
+        $server->protectedProperties[] = self::SIZE_PROPERTYNAME;
162
+        $server->protectedProperties[] = self::DOWNLOADURL_PROPERTYNAME;
163
+        $server->protectedProperties[] = self::OWNER_ID_PROPERTYNAME;
164
+        $server->protectedProperties[] = self::OWNER_DISPLAY_NAME_PROPERTYNAME;
165
+        $server->protectedProperties[] = self::CHECKSUMS_PROPERTYNAME;
166
+        $server->protectedProperties[] = self::DATA_FINGERPRINT_PROPERTYNAME;
167
+        $server->protectedProperties[] = self::HAS_PREVIEW_PROPERTYNAME;
168
+        $server->protectedProperties[] = self::MOUNT_TYPE_PROPERTYNAME;
169
+        $server->protectedProperties[] = self::IS_ENCRYPTED_PROPERTYNAME;
170
+        $server->protectedProperties[] = self::SHARE_NOTE;
171
+
172
+        // normally these cannot be changed (RFC4918), but we want them modifiable through PROPPATCH
173
+        $allowedProperties = ['{DAV:}getetag'];
174
+        $server->protectedProperties = array_diff($server->protectedProperties, $allowedProperties);
175
+
176
+        $this->server = $server;
177
+        $this->server->on('propFind', [$this, 'handleGetProperties']);
178
+        $this->server->on('propPatch', [$this, 'handleUpdateProperties']);
179
+        $this->server->on('afterBind', [$this, 'sendFileIdHeader']);
180
+        $this->server->on('afterWriteContent', [$this, 'sendFileIdHeader']);
181
+        $this->server->on('afterMethod:GET', [$this,'httpGet']);
182
+        $this->server->on('afterMethod:GET', [$this, 'handleDownloadToken']);
183
+        $this->server->on('afterResponse', function ($request, ResponseInterface $response) {
184
+            $body = $response->getBody();
185
+            if (is_resource($body)) {
186
+                fclose($body);
187
+            }
188
+        });
189
+        $this->server->on('beforeMove', [$this, 'checkMove']);
190
+    }
191
+
192
+    /**
193
+     * Plugin that checks if a move can actually be performed.
194
+     *
195
+     * @param string $source source path
196
+     * @param string $destination destination path
197
+     * @throws Forbidden
198
+     * @throws NotFound
199
+     */
200
+    function checkMove($source, $destination) {
201
+        $sourceNode = $this->tree->getNodeForPath($source);
202
+        if (!$sourceNode instanceof Node) {
203
+            return;
204
+        }
205
+        list($sourceDir,) = \Sabre\Uri\split($source);
206
+        list($destinationDir,) = \Sabre\Uri\split($destination);
207
+
208
+        if ($sourceDir !== $destinationDir) {
209
+            $sourceNodeFileInfo = $sourceNode->getFileInfo();
210
+            if ($sourceNodeFileInfo === null) {
211
+                throw new NotFound($source . ' does not exist');
212
+            }
213
+
214
+            if (!$sourceNodeFileInfo->isDeletable()) {
215
+                throw new Forbidden($source . " cannot be deleted");
216
+            }
217
+        }
218
+    }
219
+
220
+    /**
221
+     * This sets a cookie to be able to recognize the start of the download
222
+     * the content must not be longer than 32 characters and must only contain
223
+     * alphanumeric characters
224
+     *
225
+     * @param RequestInterface $request
226
+     * @param ResponseInterface $response
227
+     */
228
+    function handleDownloadToken(RequestInterface $request, ResponseInterface $response) {
229
+        $queryParams = $request->getQueryParameters();
230
+
231
+        /**
232
+         * this sets a cookie to be able to recognize the start of the download
233
+         * the content must not be longer than 32 characters and must only contain
234
+         * alphanumeric characters
235
+         */
236
+        if (isset($queryParams['downloadStartSecret'])) {
237
+            $token = $queryParams['downloadStartSecret'];
238
+            if (!isset($token[32])
239
+                && preg_match('!^[a-zA-Z0-9]+$!', $token) === 1) {
240
+                // FIXME: use $response->setHeader() instead
241
+                setcookie('ocDownloadStarted', $token, time() + 20, '/');
242
+            }
243
+        }
244
+    }
245
+
246
+    /**
247
+     * Add headers to file download
248
+     *
249
+     * @param RequestInterface $request
250
+     * @param ResponseInterface $response
251
+     */
252
+    function httpGet(RequestInterface $request, ResponseInterface $response) {
253
+        // Only handle valid files
254
+        $node = $this->tree->getNodeForPath($request->getPath());
255
+        if (!($node instanceof IFile)) return;
256
+
257
+        // adds a 'Content-Disposition: attachment' header in case no disposition
258
+        // header has been set before
259
+        if ($this->downloadAttachment &&
260
+            $response->getHeader('Content-Disposition') === null) {
261
+            $filename = $node->getName();
262
+            if ($this->request->isUserAgent(
263
+                [
264
+                    Request::USER_AGENT_IE,
265
+                    Request::USER_AGENT_ANDROID_MOBILE_CHROME,
266
+                    Request::USER_AGENT_FREEBOX,
267
+                ])) {
268
+                $response->addHeader('Content-Disposition', 'attachment; filename="' . rawurlencode($filename) . '"');
269
+            } else {
270
+                $response->addHeader('Content-Disposition', 'attachment; filename*=UTF-8\'\'' . rawurlencode($filename)
271
+                                                        . '; filename="' . rawurlencode($filename) . '"');
272
+            }
273
+        }
274
+
275
+        if ($node instanceof \OCA\DAV\Connector\Sabre\File) {
276
+            //Add OC-Checksum header
277
+            /** @var $node File */
278
+            $checksum = $node->getChecksum();
279
+            if ($checksum !== null && $checksum !== '') {
280
+                $response->addHeader('OC-Checksum', $checksum);
281
+            }
282
+        }
283
+    }
284
+
285
+    /**
286
+     * Adds all ownCloud-specific properties
287
+     *
288
+     * @param PropFind $propFind
289
+     * @param \Sabre\DAV\INode $node
290
+     * @return void
291
+     */
292
+    public function handleGetProperties(PropFind $propFind, \Sabre\DAV\INode $node) {
293
+
294
+        $httpRequest = $this->server->httpRequest;
295
+
296
+        if ($node instanceof \OCA\DAV\Connector\Sabre\Node) {
297
+            /**
298
+             * This was disabled, because it made dir listing throw an exception,
299
+             * so users were unable to navigate into folders where one subitem
300
+             * is blocked by the files_accesscontrol app, see:
301
+             * https://github.com/nextcloud/files_accesscontrol/issues/65
302
+             * if (!$node->getFileInfo()->isReadable()) {
303
+             *     // avoid detecting files through this means
304
+             *     throw new NotFound();
305
+             * }
306
+             */
307
+
308
+            $propFind->handle(self::FILEID_PROPERTYNAME, function () use ($node) {
309
+                return $node->getFileId();
310
+            });
311
+
312
+            $propFind->handle(self::INTERNAL_FILEID_PROPERTYNAME, function () use ($node) {
313
+                return $node->getInternalFileId();
314
+            });
315
+
316
+            $propFind->handle(self::PERMISSIONS_PROPERTYNAME, function () use ($node) {
317
+                $perms = $node->getDavPermissions();
318
+                if ($this->isPublic) {
319
+                    // remove mount information
320
+                    $perms = str_replace(['S', 'M'], '', $perms);
321
+                }
322
+                return $perms;
323
+            });
324
+
325
+            $propFind->handle(self::SHARE_PERMISSIONS_PROPERTYNAME, function () use ($node, $httpRequest) {
326
+                return $node->getSharePermissions(
327
+                    $httpRequest->getRawServerValue('PHP_AUTH_USER')
328
+                );
329
+            });
330
+
331
+            $propFind->handle(self::OCM_SHARE_PERMISSIONS_PROPERTYNAME, function () use ($node, $httpRequest) {
332
+                $ncPermissions = $node->getSharePermissions(
333
+                    $httpRequest->getRawServerValue('PHP_AUTH_USER')
334
+                );
335
+                $ocmPermissions = $this->ncPermissions2ocmPermissions($ncPermissions);
336
+                return json_encode($ocmPermissions);
337
+            });
338
+
339
+            $propFind->handle(self::GETETAG_PROPERTYNAME, function () use ($node) {
340
+                return $node->getETag();
341
+            });
342
+
343
+            $propFind->handle(self::OWNER_ID_PROPERTYNAME, function () use ($node) {
344
+                $owner = $node->getOwner();
345
+                if (!$owner) {
346
+                    return null;
347
+                } else {
348
+                    return $owner->getUID();
349
+                }
350
+            });
351
+            $propFind->handle(self::OWNER_DISPLAY_NAME_PROPERTYNAME, function () use ($node) {
352
+                $owner = $node->getOwner();
353
+                if (!$owner) {
354
+                    return null;
355
+                } else {
356
+                    return $owner->getDisplayName();
357
+                }
358
+            });
359
+
360
+            $propFind->handle(self::HAS_PREVIEW_PROPERTYNAME, function () use ($node) {
361
+                return json_encode($this->previewManager->isAvailable($node->getFileInfo()));
362
+            });
363
+            $propFind->handle(self::SIZE_PROPERTYNAME, function () use ($node) {
364
+                return $node->getSize();
365
+            });
366
+            $propFind->handle(self::MOUNT_TYPE_PROPERTYNAME, function () use ($node) {
367
+                return $node->getFileInfo()->getMountPoint()->getMountType();
368
+            });
369
+
370
+            $propFind->handle(self::SHARE_NOTE, function () use ($node, $httpRequest) {
371
+                return $node->getNoteFromShare(
372
+                    $httpRequest->getRawServerValue('PHP_AUTH_USER')
373
+                );
374
+            });
375
+        }
376
+
377
+        if ($node instanceof \OCA\DAV\Connector\Sabre\Node) {
378
+            $propFind->handle(self::DATA_FINGERPRINT_PROPERTYNAME, function () use ($node) {
379
+                return $this->config->getSystemValue('data-fingerprint', '');
380
+            });
381
+        }
382
+
383
+        if ($node instanceof \OCA\DAV\Connector\Sabre\File) {
384
+            $propFind->handle(self::DOWNLOADURL_PROPERTYNAME, function () use ($node) {
385
+                /** @var $node \OCA\DAV\Connector\Sabre\File */
386
+                try {
387
+                    $directDownloadUrl = $node->getDirectDownload();
388
+                    if (isset($directDownloadUrl['url'])) {
389
+                        return $directDownloadUrl['url'];
390
+                    }
391
+                } catch (StorageNotAvailableException $e) {
392
+                    return false;
393
+                } catch (ForbiddenException $e) {
394
+                    return false;
395
+                }
396
+                return false;
397
+            });
398
+
399
+            $propFind->handle(self::CHECKSUMS_PROPERTYNAME, function () use ($node) {
400
+                $checksum = $node->getChecksum();
401
+                if ($checksum === null || $checksum === '') {
402
+                    return null;
403
+                }
404
+
405
+                return new ChecksumList($checksum);
406
+            });
407
+
408
+            $propFind->handle(self::CREATION_TIME_PROPERTYNAME, function () use ($node) {
409
+                return $node->getFileInfo()->getCreationTime();
410
+            });
411
+
412
+            $propFind->handle(self::UPLOAD_TIME_PROPERTYNAME, function () use ($node) {
413
+                return $node->getFileInfo()->getUploadTime();
414
+            });
415
+
416
+        }
417
+
418
+        if ($node instanceof \OCA\DAV\Connector\Sabre\Directory) {
419
+            $propFind->handle(self::SIZE_PROPERTYNAME, function () use ($node) {
420
+                return $node->getSize();
421
+            });
422
+
423
+            $propFind->handle(self::IS_ENCRYPTED_PROPERTYNAME, function () use ($node) {
424
+                return $node->getFileInfo()->isEncrypted() ? '1' : '0';
425
+            });
426
+        }
427
+    }
428
+
429
+    /**
430
+     * translate Nextcloud permissions to OCM Permissions
431
+     *
432
+     * @param $ncPermissions
433
+     * @return array
434
+     */
435
+    protected function ncPermissions2ocmPermissions($ncPermissions) {
436
+
437
+        $ocmPermissions = [];
438
+
439
+        if ($ncPermissions & Constants::PERMISSION_SHARE) {
440
+            $ocmPermissions[] = 'share';
441
+        }
442
+
443
+        if ($ncPermissions & Constants::PERMISSION_READ) {
444
+            $ocmPermissions[] = 'read';
445
+        }
446
+
447
+        if (($ncPermissions & Constants::PERMISSION_CREATE) ||
448
+            ($ncPermissions & Constants::PERMISSION_UPDATE)) {
449
+            $ocmPermissions[] = 'write';
450
+        }
451
+
452
+        return $ocmPermissions;
453
+
454
+    }
455
+
456
+    /**
457
+     * Update ownCloud-specific properties
458
+     *
459
+     * @param string $path
460
+     * @param PropPatch $propPatch
461
+     *
462
+     * @return void
463
+     */
464
+    public function handleUpdateProperties($path, PropPatch $propPatch) {
465
+        $node = $this->tree->getNodeForPath($path);
466
+        if (!($node instanceof \OCA\DAV\Connector\Sabre\Node)) {
467
+            return;
468
+        }
469
+
470
+        $propPatch->handle(self::LASTMODIFIED_PROPERTYNAME, function ($time) use ($node) {
471
+            if (empty($time)) {
472
+                return false;
473
+            }
474
+            $node->touch($time);
475
+            return true;
476
+        });
477
+        $propPatch->handle(self::GETETAG_PROPERTYNAME, function ($etag) use ($node) {
478
+            if (empty($etag)) {
479
+                return false;
480
+            }
481
+            if ($node->setEtag($etag) !== -1) {
482
+                return true;
483
+            }
484
+            return false;
485
+        });
486
+        $propPatch->handle(self::CREATION_TIME_PROPERTYNAME, function ($time) use ($node) {
487
+            if (empty($time)) {
488
+                return false;
489
+            }
490
+            $node->setCreationTime((int) $time);
491
+            return true;
492
+        });
493
+    }
494
+
495
+    /**
496
+     * @param string $filePath
497
+     * @param \Sabre\DAV\INode $node
498
+     * @throws \Sabre\DAV\Exception\BadRequest
499
+     */
500
+    public function sendFileIdHeader($filePath, \Sabre\DAV\INode $node = null) {
501
+        // chunked upload handling
502
+        if (isset($_SERVER['HTTP_OC_CHUNKED'])) {
503
+            list($path, $name) = \Sabre\Uri\split($filePath);
504
+            $info = \OC_FileChunking::decodeName($name);
505
+            if (!empty($info)) {
506
+                $filePath = $path . '/' . $info['name'];
507
+            }
508
+        }
509
+
510
+        // we get the node for the given $filePath here because in case of afterCreateFile $node is the parent folder
511
+        if (!$this->server->tree->nodeExists($filePath)) {
512
+            return;
513
+        }
514
+        $node = $this->server->tree->getNodeForPath($filePath);
515
+        if ($node instanceof \OCA\DAV\Connector\Sabre\Node) {
516
+            $fileId = $node->getFileId();
517
+            if (!is_null($fileId)) {
518
+                $this->server->httpResponse->setHeader('OC-FileId', $fileId);
519
+            }
520
+        }
521
+    }
522 522
 }
Please login to merge, or discard this patch.
apps/dav/lib/Connector/Sabre/Principal.php 1 patch
Indentation   +478 added lines, -478 removed lines patch added patch discarded remove patch
@@ -50,482 +50,482 @@
 block discarded – undo
50 50
 
51 51
 class Principal implements BackendInterface {
52 52
 
53
-	/** @var IUserManager */
54
-	private $userManager;
55
-
56
-	/** @var IGroupManager */
57
-	private $groupManager;
58
-
59
-	/** @var IShareManager */
60
-	private $shareManager;
61
-
62
-	/** @var IUserSession */
63
-	private $userSession;
64
-
65
-	/** @var IAppManager */
66
-	private $appManager;
67
-
68
-	/** @var string */
69
-	private $principalPrefix;
70
-
71
-	/** @var bool */
72
-	private $hasGroups;
73
-
74
-	/** @var bool */
75
-	private $hasCircles;
76
-
77
-	/** @var ProxyMapper */
78
-	private $proxyMapper;
79
-
80
-	/** @var IConfig */
81
-	private $config;
82
-
83
-	/**
84
-	 * Principal constructor.
85
-	 *
86
-	 * @param IUserManager $userManager
87
-	 * @param IGroupManager $groupManager
88
-	 * @param IShareManager $shareManager
89
-	 * @param IUserSession $userSession
90
-	 * @param IAppManager $appManager
91
-	 * @param ProxyMapper $proxyMapper
92
-	 * @param IConfig $config
93
-	 * @param string $principalPrefix
94
-	 */
95
-	public function __construct(IUserManager $userManager,
96
-								IGroupManager $groupManager,
97
-								IShareManager $shareManager,
98
-								IUserSession $userSession,
99
-								IAppManager $appManager,
100
-								ProxyMapper $proxyMapper,
101
-								IConfig $config,
102
-								string $principalPrefix = 'principals/users/') {
103
-		$this->userManager = $userManager;
104
-		$this->groupManager = $groupManager;
105
-		$this->shareManager = $shareManager;
106
-		$this->userSession = $userSession;
107
-		$this->appManager = $appManager;
108
-		$this->principalPrefix = trim($principalPrefix, '/');
109
-		$this->hasGroups = $this->hasCircles = ($principalPrefix === 'principals/users/');
110
-		$this->proxyMapper = $proxyMapper;
111
-		$this->config = $config;
112
-	}
113
-
114
-	use PrincipalProxyTrait {
115
-		getGroupMembership as protected traitGetGroupMembership;
116
-	}
117
-
118
-	/**
119
-	 * Returns a list of principals based on a prefix.
120
-	 *
121
-	 * This prefix will often contain something like 'principals'. You are only
122
-	 * expected to return principals that are in this base path.
123
-	 *
124
-	 * You are expected to return at least a 'uri' for every user, you can
125
-	 * return any additional properties if you wish so. Common properties are:
126
-	 *   {DAV:}displayname
127
-	 *
128
-	 * @param string $prefixPath
129
-	 * @return string[]
130
-	 */
131
-	public function getPrincipalsByPrefix($prefixPath) {
132
-		$principals = [];
133
-
134
-		if ($prefixPath === $this->principalPrefix) {
135
-			foreach($this->userManager->search('') as $user) {
136
-				$principals[] = $this->userToPrincipal($user);
137
-			}
138
-		}
139
-
140
-		return $principals;
141
-	}
142
-
143
-	/**
144
-	 * Returns a specific principal, specified by it's path.
145
-	 * The returned structure should be the exact same as from
146
-	 * getPrincipalsByPrefix.
147
-	 *
148
-	 * @param string $path
149
-	 * @return array
150
-	 */
151
-	public function getPrincipalByPath($path) {
152
-		list($prefix, $name) = \Sabre\Uri\split($path);
153
-
154
-		if ($name === 'calendar-proxy-write' || $name === 'calendar-proxy-read') {
155
-			list($prefix2, $name2) = \Sabre\Uri\split($prefix);
156
-
157
-			if ($prefix2 === $this->principalPrefix) {
158
-				$user = $this->userManager->get($name2);
159
-
160
-				if ($user !== null) {
161
-					return [
162
-						'uri' => 'principals/users/' . $user->getUID() . '/' . $name,
163
-					];
164
-				}
165
-				return null;
166
-			}
167
-		}
168
-
169
-		if ($prefix === $this->principalPrefix) {
170
-			$user = $this->userManager->get($name);
171
-
172
-			if ($user !== null) {
173
-				return $this->userToPrincipal($user);
174
-			}
175
-		} else if ($prefix === 'principals/circles') {
176
-			try {
177
-				return $this->circleToPrincipal($name);
178
-			} catch (QueryException $e) {
179
-				return null;
180
-			}
181
-		}
182
-		return null;
183
-	}
184
-
185
-	/**
186
-	 * Returns the list of groups a principal is a member of
187
-	 *
188
-	 * @param string $principal
189
-	 * @param bool $needGroups
190
-	 * @return array
191
-	 * @throws Exception
192
-	 */
193
-	public function getGroupMembership($principal, $needGroups = false) {
194
-		list($prefix, $name) = \Sabre\Uri\split($principal);
195
-
196
-		if ($prefix !== $this->principalPrefix) {
197
-			return [];
198
-		}
199
-
200
-		$user = $this->userManager->get($name);
201
-		if (!$user) {
202
-			throw new Exception('Principal not found');
203
-		}
204
-
205
-		$groups = [];
206
-
207
-		if ($this->hasGroups || $needGroups) {
208
-			$userGroups = $this->groupManager->getUserGroups($user);
209
-			foreach($userGroups as $userGroup) {
210
-				$groups[] = 'principals/groups/' . urlencode($userGroup->getGID());
211
-			}
212
-		}
213
-
214
-		$groups = array_unique(array_merge(
215
-			$groups,
216
-			$this->traitGetGroupMembership($principal, $needGroups)
217
-		));
218
-
219
-		return $groups;
220
-	}
221
-
222
-	/**
223
-	 * @param string $path
224
-	 * @param PropPatch $propPatch
225
-	 * @return int
226
-	 */
227
-	function updatePrincipal($path, PropPatch $propPatch) {
228
-		return 0;
229
-	}
230
-
231
-	/**
232
-	 * Search user principals
233
-	 *
234
-	 * @param array $searchProperties
235
-	 * @param string $test
236
-	 * @return array
237
-	 */
238
-	protected function searchUserPrincipals(array $searchProperties, $test = 'allof') {
239
-		$results = [];
240
-
241
-		// If sharing is disabled, return the empty array
242
-		$shareAPIEnabled = $this->shareManager->shareApiEnabled();
243
-		if (!$shareAPIEnabled) {
244
-			return [];
245
-		}
246
-
247
-		$allowEnumeration = $this->shareManager->allowEnumeration();
248
-		$limitEnumeration = $this->shareManager->limitEnumerationToGroups();
249
-
250
-		// If sharing is restricted to group members only,
251
-		// return only members that have groups in common
252
-		$restrictGroups = false;
253
-		if ($this->shareManager->shareWithGroupMembersOnly()) {
254
-			$user = $this->userSession->getUser();
255
-			if (!$user) {
256
-				return [];
257
-			}
258
-
259
-			$restrictGroups = $this->groupManager->getUserGroupIds($user);
260
-		}
261
-
262
-		$currentUserGroups = [];
263
-		if ($limitEnumeration) {
264
-			$currentUser = $this->userSession->getUser();
265
-			if ($currentUser) {
266
-				$currentUserGroups = $this->groupManager->getUserGroupIds($currentUser);
267
-			}
268
-		}
269
-
270
-		foreach ($searchProperties as $prop => $value) {
271
-			switch ($prop) {
272
-				case '{http://sabredav.org/ns}email-address':
273
-					$users = $this->userManager->getByEmail($value);
274
-
275
-					if (!$allowEnumeration) {
276
-						$users = \array_filter($users, static function (IUser $user) use ($value) {
277
-							return $user->getEMailAddress() === $value;
278
-						});
279
-					}
280
-
281
-					if ($limitEnumeration) {
282
-						$users = \array_filter($users, function (IUser $user) use ($currentUserGroups, $value) {
283
-							return !empty(array_intersect(
284
-									$this->groupManager->getUserGroupIds($user),
285
-									$currentUserGroups
286
-								)) || $user->getEMailAddress() === $value;
287
-						});
288
-					}
289
-
290
-					$results[] = array_reduce($users, function (array $carry, IUser $user) use ($restrictGroups) {
291
-						// is sharing restricted to groups only?
292
-						if ($restrictGroups !== false) {
293
-							$userGroups = $this->groupManager->getUserGroupIds($user);
294
-							if (count(array_intersect($userGroups, $restrictGroups)) === 0) {
295
-								return $carry;
296
-							}
297
-						}
298
-
299
-						$carry[] = $this->principalPrefix . '/' . $user->getUID();
300
-						return $carry;
301
-					}, []);
302
-					break;
303
-
304
-				case '{DAV:}displayname':
305
-					$users = $this->userManager->searchDisplayName($value);
306
-
307
-					if (!$allowEnumeration) {
308
-						$users = \array_filter($users, static function (IUser $user) use ($value) {
309
-							return $user->getDisplayName() === $value;
310
-						});
311
-					}
312
-
313
-					if ($limitEnumeration) {
314
-						$users = \array_filter($users, function (IUser $user) use ($currentUserGroups, $value) {
315
-							return !empty(array_intersect(
316
-									$this->groupManager->getUserGroupIds($user),
317
-									$currentUserGroups
318
-								)) || $user->getDisplayName() === $value;
319
-						});
320
-					}
321
-
322
-					$results[] = array_reduce($users, function (array $carry, IUser $user) use ($restrictGroups) {
323
-						// is sharing restricted to groups only?
324
-						if ($restrictGroups !== false) {
325
-							$userGroups = $this->groupManager->getUserGroupIds($user);
326
-							if (count(array_intersect($userGroups, $restrictGroups)) === 0) {
327
-								return $carry;
328
-							}
329
-						}
330
-
331
-						$carry[] = $this->principalPrefix . '/' . $user->getUID();
332
-						return $carry;
333
-					}, []);
334
-					break;
335
-
336
-				case '{urn:ietf:params:xml:ns:caldav}calendar-user-address-set':
337
-					// If you add support for more search properties that qualify as a user-address,
338
-					// please also add them to the array below
339
-					$results[] = $this->searchUserPrincipals([
340
-						// In theory this should also search for principal:principals/users/...
341
-						// but that's used internally only anyway and i don't know of any client querying that
342
-						'{http://sabredav.org/ns}email-address' => $value,
343
-					], 'anyof');
344
-					break;
345
-
346
-				default:
347
-					$results[] = [];
348
-					break;
349
-			}
350
-		}
351
-
352
-		// results is an array of arrays, so this is not the first search result
353
-		// but the results of the first searchProperty
354
-		if (count($results) === 1) {
355
-			return $results[0];
356
-		}
357
-
358
-		switch ($test) {
359
-			case 'anyof':
360
-				return array_values(array_unique(array_merge(...$results)));
361
-
362
-			case 'allof':
363
-			default:
364
-				return array_values(array_intersect(...$results));
365
-		}
366
-	}
367
-
368
-	/**
369
-	 * @param string $prefixPath
370
-	 * @param array $searchProperties
371
-	 * @param string $test
372
-	 * @return array
373
-	 */
374
-	function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof') {
375
-		if (count($searchProperties) === 0) {
376
-			return [];
377
-		}
378
-
379
-		switch ($prefixPath) {
380
-			case 'principals/users':
381
-				return $this->searchUserPrincipals($searchProperties, $test);
382
-
383
-			default:
384
-				return [];
385
-		}
386
-	}
387
-
388
-	/**
389
-	 * @param string $uri
390
-	 * @param string $principalPrefix
391
-	 * @return string
392
-	 */
393
-	function findByUri($uri, $principalPrefix) {
394
-		// If sharing is disabled, return the empty array
395
-		$shareAPIEnabled = $this->shareManager->shareApiEnabled();
396
-		if (!$shareAPIEnabled) {
397
-			return null;
398
-		}
399
-
400
-		// If sharing is restricted to group members only,
401
-		// return only members that have groups in common
402
-		$restrictGroups = false;
403
-		if ($this->shareManager->shareWithGroupMembersOnly()) {
404
-			$user = $this->userSession->getUser();
405
-			if (!$user) {
406
-				return null;
407
-			}
408
-
409
-			$restrictGroups = $this->groupManager->getUserGroupIds($user);
410
-		}
411
-
412
-		if (strpos($uri, 'mailto:') === 0) {
413
-			if ($principalPrefix === 'principals/users') {
414
-				$users = $this->userManager->getByEmail(substr($uri, 7));
415
-				if (count($users) !== 1) {
416
-					return null;
417
-				}
418
-				$user = $users[0];
419
-
420
-				if ($restrictGroups !== false) {
421
-					$userGroups = $this->groupManager->getUserGroupIds($user);
422
-					if (count(array_intersect($userGroups, $restrictGroups)) === 0) {
423
-						return null;
424
-					}
425
-				}
426
-
427
-				return $this->principalPrefix . '/' . $user->getUID();
428
-			}
429
-		}
430
-		if (substr($uri, 0, 10) === 'principal:') {
431
-			$principal = substr($uri, 10);
432
-			$principal = $this->getPrincipalByPath($principal);
433
-			if ($principal !== null) {
434
-				return $principal['uri'];
435
-			}
436
-		}
437
-
438
-		return null;
439
-	}
440
-
441
-	/**
442
-	 * @param IUser $user
443
-	 * @return array
444
-	 */
445
-	protected function userToPrincipal($user) {
446
-		$userId = $user->getUID();
447
-		$displayName = $user->getDisplayName();
448
-		$principal = [
449
-			'uri' => $this->principalPrefix . '/' . $userId,
450
-			'{DAV:}displayname' => is_null($displayName) ? $userId : $displayName,
451
-			'{urn:ietf:params:xml:ns:caldav}calendar-user-type' => 'INDIVIDUAL',
452
-		];
453
-
454
-		$email = $user->getEMailAddress();
455
-		if (!empty($email)) {
456
-			$principal['{http://sabredav.org/ns}email-address'] = $email;
457
-		}
458
-
459
-		return $principal;
460
-	}
461
-
462
-	public function getPrincipalPrefix() {
463
-		return $this->principalPrefix;
464
-	}
465
-
466
-	/**
467
-	 * @param string $circleUniqueId
468
-	 * @return array|null
469
-	 * @throws \OCP\AppFramework\QueryException
470
-	 * @suppress PhanUndeclaredClassMethod
471
-	 * @suppress PhanUndeclaredClassCatch
472
-	 */
473
-	protected function circleToPrincipal($circleUniqueId) {
474
-		if (!$this->appManager->isEnabledForUser('circles') || !class_exists('\OCA\Circles\Api\v1\Circles')) {
475
-			return null;
476
-		}
477
-
478
-		try {
479
-			$circle = \OCA\Circles\Api\v1\Circles::detailsCircle($circleUniqueId, true);
480
-		} catch(QueryException $ex) {
481
-			return null;
482
-		} catch(CircleDoesNotExistException $ex) {
483
-			return null;
484
-		}
485
-
486
-		if (!$circle) {
487
-			return null;
488
-		}
489
-
490
-		$principal = [
491
-			'uri' => 'principals/circles/' . $circleUniqueId,
492
-			'{DAV:}displayname' => $circle->getName(),
493
-		];
494
-
495
-		return $principal;
496
-	}
497
-
498
-	/**
499
-	 * Returns the list of circles a principal is a member of
500
-	 *
501
-	 * @param string $principal
502
-	 * @return array
503
-	 * @throws Exception
504
-	 * @throws \OCP\AppFramework\QueryException
505
-	 * @suppress PhanUndeclaredClassMethod
506
-	 */
507
-	public function getCircleMembership($principal):array {
508
-		if (!$this->appManager->isEnabledForUser('circles') || !class_exists('\OCA\Circles\Api\v1\Circles')) {
509
-			return [];
510
-		}
511
-
512
-		list($prefix, $name) = \Sabre\Uri\split($principal);
513
-		if ($this->hasCircles && $prefix === $this->principalPrefix) {
514
-			$user = $this->userManager->get($name);
515
-			if (!$user) {
516
-				throw new Exception('Principal not found');
517
-			}
518
-
519
-			$circles = \OCA\Circles\Api\v1\Circles::joinedCircles($name, true);
520
-
521
-			$circles = array_map(function ($circle) {
522
-				/** @var \OCA\Circles\Model\Circle $circle */
523
-				return 'principals/circles/' . urlencode($circle->getUniqueId());
524
-			}, $circles);
525
-
526
-			return $circles;
527
-		}
528
-
529
-		return [];
530
-	}
53
+    /** @var IUserManager */
54
+    private $userManager;
55
+
56
+    /** @var IGroupManager */
57
+    private $groupManager;
58
+
59
+    /** @var IShareManager */
60
+    private $shareManager;
61
+
62
+    /** @var IUserSession */
63
+    private $userSession;
64
+
65
+    /** @var IAppManager */
66
+    private $appManager;
67
+
68
+    /** @var string */
69
+    private $principalPrefix;
70
+
71
+    /** @var bool */
72
+    private $hasGroups;
73
+
74
+    /** @var bool */
75
+    private $hasCircles;
76
+
77
+    /** @var ProxyMapper */
78
+    private $proxyMapper;
79
+
80
+    /** @var IConfig */
81
+    private $config;
82
+
83
+    /**
84
+     * Principal constructor.
85
+     *
86
+     * @param IUserManager $userManager
87
+     * @param IGroupManager $groupManager
88
+     * @param IShareManager $shareManager
89
+     * @param IUserSession $userSession
90
+     * @param IAppManager $appManager
91
+     * @param ProxyMapper $proxyMapper
92
+     * @param IConfig $config
93
+     * @param string $principalPrefix
94
+     */
95
+    public function __construct(IUserManager $userManager,
96
+                                IGroupManager $groupManager,
97
+                                IShareManager $shareManager,
98
+                                IUserSession $userSession,
99
+                                IAppManager $appManager,
100
+                                ProxyMapper $proxyMapper,
101
+                                IConfig $config,
102
+                                string $principalPrefix = 'principals/users/') {
103
+        $this->userManager = $userManager;
104
+        $this->groupManager = $groupManager;
105
+        $this->shareManager = $shareManager;
106
+        $this->userSession = $userSession;
107
+        $this->appManager = $appManager;
108
+        $this->principalPrefix = trim($principalPrefix, '/');
109
+        $this->hasGroups = $this->hasCircles = ($principalPrefix === 'principals/users/');
110
+        $this->proxyMapper = $proxyMapper;
111
+        $this->config = $config;
112
+    }
113
+
114
+    use PrincipalProxyTrait {
115
+        getGroupMembership as protected traitGetGroupMembership;
116
+    }
117
+
118
+    /**
119
+     * Returns a list of principals based on a prefix.
120
+     *
121
+     * This prefix will often contain something like 'principals'. You are only
122
+     * expected to return principals that are in this base path.
123
+     *
124
+     * You are expected to return at least a 'uri' for every user, you can
125
+     * return any additional properties if you wish so. Common properties are:
126
+     *   {DAV:}displayname
127
+     *
128
+     * @param string $prefixPath
129
+     * @return string[]
130
+     */
131
+    public function getPrincipalsByPrefix($prefixPath) {
132
+        $principals = [];
133
+
134
+        if ($prefixPath === $this->principalPrefix) {
135
+            foreach($this->userManager->search('') as $user) {
136
+                $principals[] = $this->userToPrincipal($user);
137
+            }
138
+        }
139
+
140
+        return $principals;
141
+    }
142
+
143
+    /**
144
+     * Returns a specific principal, specified by it's path.
145
+     * The returned structure should be the exact same as from
146
+     * getPrincipalsByPrefix.
147
+     *
148
+     * @param string $path
149
+     * @return array
150
+     */
151
+    public function getPrincipalByPath($path) {
152
+        list($prefix, $name) = \Sabre\Uri\split($path);
153
+
154
+        if ($name === 'calendar-proxy-write' || $name === 'calendar-proxy-read') {
155
+            list($prefix2, $name2) = \Sabre\Uri\split($prefix);
156
+
157
+            if ($prefix2 === $this->principalPrefix) {
158
+                $user = $this->userManager->get($name2);
159
+
160
+                if ($user !== null) {
161
+                    return [
162
+                        'uri' => 'principals/users/' . $user->getUID() . '/' . $name,
163
+                    ];
164
+                }
165
+                return null;
166
+            }
167
+        }
168
+
169
+        if ($prefix === $this->principalPrefix) {
170
+            $user = $this->userManager->get($name);
171
+
172
+            if ($user !== null) {
173
+                return $this->userToPrincipal($user);
174
+            }
175
+        } else if ($prefix === 'principals/circles') {
176
+            try {
177
+                return $this->circleToPrincipal($name);
178
+            } catch (QueryException $e) {
179
+                return null;
180
+            }
181
+        }
182
+        return null;
183
+    }
184
+
185
+    /**
186
+     * Returns the list of groups a principal is a member of
187
+     *
188
+     * @param string $principal
189
+     * @param bool $needGroups
190
+     * @return array
191
+     * @throws Exception
192
+     */
193
+    public function getGroupMembership($principal, $needGroups = false) {
194
+        list($prefix, $name) = \Sabre\Uri\split($principal);
195
+
196
+        if ($prefix !== $this->principalPrefix) {
197
+            return [];
198
+        }
199
+
200
+        $user = $this->userManager->get($name);
201
+        if (!$user) {
202
+            throw new Exception('Principal not found');
203
+        }
204
+
205
+        $groups = [];
206
+
207
+        if ($this->hasGroups || $needGroups) {
208
+            $userGroups = $this->groupManager->getUserGroups($user);
209
+            foreach($userGroups as $userGroup) {
210
+                $groups[] = 'principals/groups/' . urlencode($userGroup->getGID());
211
+            }
212
+        }
213
+
214
+        $groups = array_unique(array_merge(
215
+            $groups,
216
+            $this->traitGetGroupMembership($principal, $needGroups)
217
+        ));
218
+
219
+        return $groups;
220
+    }
221
+
222
+    /**
223
+     * @param string $path
224
+     * @param PropPatch $propPatch
225
+     * @return int
226
+     */
227
+    function updatePrincipal($path, PropPatch $propPatch) {
228
+        return 0;
229
+    }
230
+
231
+    /**
232
+     * Search user principals
233
+     *
234
+     * @param array $searchProperties
235
+     * @param string $test
236
+     * @return array
237
+     */
238
+    protected function searchUserPrincipals(array $searchProperties, $test = 'allof') {
239
+        $results = [];
240
+
241
+        // If sharing is disabled, return the empty array
242
+        $shareAPIEnabled = $this->shareManager->shareApiEnabled();
243
+        if (!$shareAPIEnabled) {
244
+            return [];
245
+        }
246
+
247
+        $allowEnumeration = $this->shareManager->allowEnumeration();
248
+        $limitEnumeration = $this->shareManager->limitEnumerationToGroups();
249
+
250
+        // If sharing is restricted to group members only,
251
+        // return only members that have groups in common
252
+        $restrictGroups = false;
253
+        if ($this->shareManager->shareWithGroupMembersOnly()) {
254
+            $user = $this->userSession->getUser();
255
+            if (!$user) {
256
+                return [];
257
+            }
258
+
259
+            $restrictGroups = $this->groupManager->getUserGroupIds($user);
260
+        }
261
+
262
+        $currentUserGroups = [];
263
+        if ($limitEnumeration) {
264
+            $currentUser = $this->userSession->getUser();
265
+            if ($currentUser) {
266
+                $currentUserGroups = $this->groupManager->getUserGroupIds($currentUser);
267
+            }
268
+        }
269
+
270
+        foreach ($searchProperties as $prop => $value) {
271
+            switch ($prop) {
272
+                case '{http://sabredav.org/ns}email-address':
273
+                    $users = $this->userManager->getByEmail($value);
274
+
275
+                    if (!$allowEnumeration) {
276
+                        $users = \array_filter($users, static function (IUser $user) use ($value) {
277
+                            return $user->getEMailAddress() === $value;
278
+                        });
279
+                    }
280
+
281
+                    if ($limitEnumeration) {
282
+                        $users = \array_filter($users, function (IUser $user) use ($currentUserGroups, $value) {
283
+                            return !empty(array_intersect(
284
+                                    $this->groupManager->getUserGroupIds($user),
285
+                                    $currentUserGroups
286
+                                )) || $user->getEMailAddress() === $value;
287
+                        });
288
+                    }
289
+
290
+                    $results[] = array_reduce($users, function (array $carry, IUser $user) use ($restrictGroups) {
291
+                        // is sharing restricted to groups only?
292
+                        if ($restrictGroups !== false) {
293
+                            $userGroups = $this->groupManager->getUserGroupIds($user);
294
+                            if (count(array_intersect($userGroups, $restrictGroups)) === 0) {
295
+                                return $carry;
296
+                            }
297
+                        }
298
+
299
+                        $carry[] = $this->principalPrefix . '/' . $user->getUID();
300
+                        return $carry;
301
+                    }, []);
302
+                    break;
303
+
304
+                case '{DAV:}displayname':
305
+                    $users = $this->userManager->searchDisplayName($value);
306
+
307
+                    if (!$allowEnumeration) {
308
+                        $users = \array_filter($users, static function (IUser $user) use ($value) {
309
+                            return $user->getDisplayName() === $value;
310
+                        });
311
+                    }
312
+
313
+                    if ($limitEnumeration) {
314
+                        $users = \array_filter($users, function (IUser $user) use ($currentUserGroups, $value) {
315
+                            return !empty(array_intersect(
316
+                                    $this->groupManager->getUserGroupIds($user),
317
+                                    $currentUserGroups
318
+                                )) || $user->getDisplayName() === $value;
319
+                        });
320
+                    }
321
+
322
+                    $results[] = array_reduce($users, function (array $carry, IUser $user) use ($restrictGroups) {
323
+                        // is sharing restricted to groups only?
324
+                        if ($restrictGroups !== false) {
325
+                            $userGroups = $this->groupManager->getUserGroupIds($user);
326
+                            if (count(array_intersect($userGroups, $restrictGroups)) === 0) {
327
+                                return $carry;
328
+                            }
329
+                        }
330
+
331
+                        $carry[] = $this->principalPrefix . '/' . $user->getUID();
332
+                        return $carry;
333
+                    }, []);
334
+                    break;
335
+
336
+                case '{urn:ietf:params:xml:ns:caldav}calendar-user-address-set':
337
+                    // If you add support for more search properties that qualify as a user-address,
338
+                    // please also add them to the array below
339
+                    $results[] = $this->searchUserPrincipals([
340
+                        // In theory this should also search for principal:principals/users/...
341
+                        // but that's used internally only anyway and i don't know of any client querying that
342
+                        '{http://sabredav.org/ns}email-address' => $value,
343
+                    ], 'anyof');
344
+                    break;
345
+
346
+                default:
347
+                    $results[] = [];
348
+                    break;
349
+            }
350
+        }
351
+
352
+        // results is an array of arrays, so this is not the first search result
353
+        // but the results of the first searchProperty
354
+        if (count($results) === 1) {
355
+            return $results[0];
356
+        }
357
+
358
+        switch ($test) {
359
+            case 'anyof':
360
+                return array_values(array_unique(array_merge(...$results)));
361
+
362
+            case 'allof':
363
+            default:
364
+                return array_values(array_intersect(...$results));
365
+        }
366
+    }
367
+
368
+    /**
369
+     * @param string $prefixPath
370
+     * @param array $searchProperties
371
+     * @param string $test
372
+     * @return array
373
+     */
374
+    function searchPrincipals($prefixPath, array $searchProperties, $test = 'allof') {
375
+        if (count($searchProperties) === 0) {
376
+            return [];
377
+        }
378
+
379
+        switch ($prefixPath) {
380
+            case 'principals/users':
381
+                return $this->searchUserPrincipals($searchProperties, $test);
382
+
383
+            default:
384
+                return [];
385
+        }
386
+    }
387
+
388
+    /**
389
+     * @param string $uri
390
+     * @param string $principalPrefix
391
+     * @return string
392
+     */
393
+    function findByUri($uri, $principalPrefix) {
394
+        // If sharing is disabled, return the empty array
395
+        $shareAPIEnabled = $this->shareManager->shareApiEnabled();
396
+        if (!$shareAPIEnabled) {
397
+            return null;
398
+        }
399
+
400
+        // If sharing is restricted to group members only,
401
+        // return only members that have groups in common
402
+        $restrictGroups = false;
403
+        if ($this->shareManager->shareWithGroupMembersOnly()) {
404
+            $user = $this->userSession->getUser();
405
+            if (!$user) {
406
+                return null;
407
+            }
408
+
409
+            $restrictGroups = $this->groupManager->getUserGroupIds($user);
410
+        }
411
+
412
+        if (strpos($uri, 'mailto:') === 0) {
413
+            if ($principalPrefix === 'principals/users') {
414
+                $users = $this->userManager->getByEmail(substr($uri, 7));
415
+                if (count($users) !== 1) {
416
+                    return null;
417
+                }
418
+                $user = $users[0];
419
+
420
+                if ($restrictGroups !== false) {
421
+                    $userGroups = $this->groupManager->getUserGroupIds($user);
422
+                    if (count(array_intersect($userGroups, $restrictGroups)) === 0) {
423
+                        return null;
424
+                    }
425
+                }
426
+
427
+                return $this->principalPrefix . '/' . $user->getUID();
428
+            }
429
+        }
430
+        if (substr($uri, 0, 10) === 'principal:') {
431
+            $principal = substr($uri, 10);
432
+            $principal = $this->getPrincipalByPath($principal);
433
+            if ($principal !== null) {
434
+                return $principal['uri'];
435
+            }
436
+        }
437
+
438
+        return null;
439
+    }
440
+
441
+    /**
442
+     * @param IUser $user
443
+     * @return array
444
+     */
445
+    protected function userToPrincipal($user) {
446
+        $userId = $user->getUID();
447
+        $displayName = $user->getDisplayName();
448
+        $principal = [
449
+            'uri' => $this->principalPrefix . '/' . $userId,
450
+            '{DAV:}displayname' => is_null($displayName) ? $userId : $displayName,
451
+            '{urn:ietf:params:xml:ns:caldav}calendar-user-type' => 'INDIVIDUAL',
452
+        ];
453
+
454
+        $email = $user->getEMailAddress();
455
+        if (!empty($email)) {
456
+            $principal['{http://sabredav.org/ns}email-address'] = $email;
457
+        }
458
+
459
+        return $principal;
460
+    }
461
+
462
+    public function getPrincipalPrefix() {
463
+        return $this->principalPrefix;
464
+    }
465
+
466
+    /**
467
+     * @param string $circleUniqueId
468
+     * @return array|null
469
+     * @throws \OCP\AppFramework\QueryException
470
+     * @suppress PhanUndeclaredClassMethod
471
+     * @suppress PhanUndeclaredClassCatch
472
+     */
473
+    protected function circleToPrincipal($circleUniqueId) {
474
+        if (!$this->appManager->isEnabledForUser('circles') || !class_exists('\OCA\Circles\Api\v1\Circles')) {
475
+            return null;
476
+        }
477
+
478
+        try {
479
+            $circle = \OCA\Circles\Api\v1\Circles::detailsCircle($circleUniqueId, true);
480
+        } catch(QueryException $ex) {
481
+            return null;
482
+        } catch(CircleDoesNotExistException $ex) {
483
+            return null;
484
+        }
485
+
486
+        if (!$circle) {
487
+            return null;
488
+        }
489
+
490
+        $principal = [
491
+            'uri' => 'principals/circles/' . $circleUniqueId,
492
+            '{DAV:}displayname' => $circle->getName(),
493
+        ];
494
+
495
+        return $principal;
496
+    }
497
+
498
+    /**
499
+     * Returns the list of circles a principal is a member of
500
+     *
501
+     * @param string $principal
502
+     * @return array
503
+     * @throws Exception
504
+     * @throws \OCP\AppFramework\QueryException
505
+     * @suppress PhanUndeclaredClassMethod
506
+     */
507
+    public function getCircleMembership($principal):array {
508
+        if (!$this->appManager->isEnabledForUser('circles') || !class_exists('\OCA\Circles\Api\v1\Circles')) {
509
+            return [];
510
+        }
511
+
512
+        list($prefix, $name) = \Sabre\Uri\split($principal);
513
+        if ($this->hasCircles && $prefix === $this->principalPrefix) {
514
+            $user = $this->userManager->get($name);
515
+            if (!$user) {
516
+                throw new Exception('Principal not found');
517
+            }
518
+
519
+            $circles = \OCA\Circles\Api\v1\Circles::joinedCircles($name, true);
520
+
521
+            $circles = array_map(function ($circle) {
522
+                /** @var \OCA\Circles\Model\Circle $circle */
523
+                return 'principals/circles/' . urlencode($circle->getUniqueId());
524
+            }, $circles);
525
+
526
+            return $circles;
527
+        }
528
+
529
+        return [];
530
+    }
531 531
 }
Please login to merge, or discard this patch.