Passed
Push — master ( a1c1b3...a1f329 )
by Roeland
31:31 queued 18:39
created

Backend::onTouchCalendarObject()   D

Complexity

Conditions 20
Paths 109

Size

Total Lines 69
Code Lines 45

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 20
eloc 45
nc 109
nop 4
dl 0
loc 69
rs 4.0916
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016 Joas Schilling <[email protected]>
4
 *
5
 * @author Christoph Wurst <[email protected]>
6
 * @author Joas Schilling <[email protected]>
7
 * @author Roeland Jago Douma <[email protected]>
8
 * @author Thomas Citharel <[email protected]>
9
 *
10
 * @license GNU AGPL version 3 or any later version
11
 *
12
 * This program is free software: you can redistribute it and/or modify
13
 * it under the terms of the GNU Affero General Public License as
14
 * published by the Free Software Foundation, either version 3 of the
15
 * License, or (at your option) any later version.
16
 *
17
 * This program is distributed in the hope that it will be useful,
18
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
19
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
 * GNU Affero General Public License for more details.
21
 *
22
 * You should have received a copy of the GNU Affero General Public License
23
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
24
 *
25
 */
26
27
namespace OCA\DAV\CalDAV\Activity;
28
29
use OCA\DAV\CalDAV\Activity\Provider\Calendar;
30
use OCA\DAV\CalDAV\Activity\Provider\Event;
31
use OCA\DAV\CalDAV\CalDavBackend;
32
use OCP\Activity\IEvent;
33
use OCP\Activity\IManager as IActivityManager;
34
use OCP\App\IAppManager;
35
use OCP\IGroup;
36
use OCP\IGroupManager;
37
use OCP\IUser;
38
use OCP\IUserSession;
39
use Sabre\VObject\Reader;
40
41
/**
42
 * Class Backend
43
 *
44
 * @package OCA\DAV\CalDAV\Activity
45
 */
46
class Backend {
47
48
	/** @var IActivityManager */
49
	protected $activityManager;
50
51
	/** @var IGroupManager */
52
	protected $groupManager;
53
54
	/** @var IUserSession */
55
	protected $userSession;
56
57
	/** @var IAppManager */
58
	protected $appManager;
59
60
	public function __construct(IActivityManager $activityManager, IGroupManager $groupManager, IUserSession $userSession, IAppManager $appManager) {
61
		$this->activityManager = $activityManager;
62
		$this->groupManager = $groupManager;
63
		$this->userSession = $userSession;
64
		$this->appManager = $appManager;
65
	}
66
67
	/**
68
	 * Creates activities when a calendar was creates
69
	 *
70
	 * @param array $calendarData
71
	 */
72
	public function onCalendarAdd(array $calendarData) {
73
		$this->triggerCalendarActivity(Calendar::SUBJECT_ADD, $calendarData);
74
	}
75
76
	/**
77
	 * Creates activities when a calendar was updated
78
	 *
79
	 * @param array $calendarData
80
	 * @param array $shares
81
	 * @param array $properties
82
	 */
83
	public function onCalendarUpdate(array $calendarData, array $shares, array $properties) {
84
		$this->triggerCalendarActivity(Calendar::SUBJECT_UPDATE, $calendarData, $shares, $properties);
85
	}
86
87
	/**
88
	 * Creates activities when a calendar was deleted
89
	 *
90
	 * @param array $calendarData
91
	 * @param array $shares
92
	 */
93
	public function onCalendarDelete(array $calendarData, array $shares) {
94
		$this->triggerCalendarActivity(Calendar::SUBJECT_DELETE, $calendarData, $shares);
95
	}
96
97
	/**
98
	 * Creates activities when a calendar was (un)published
99
	 *
100
	 * @param array $calendarData
101
	 * @param bool $publishStatus
102
	 */
103
	public function onCalendarPublication(array $calendarData, $publishStatus) {
104
		$this->triggerCalendarActivity($publishStatus ? Calendar::SUBJECT_PUBLISH : Calendar::SUBJECT_UNPUBLISH, $calendarData);
105
	}
106
107
	/**
108
	 * Creates activities for all related users when a calendar was touched
109
	 *
110
	 * @param string $action
111
	 * @param array $calendarData
112
	 * @param array $shares
113
	 * @param array $changedProperties
114
	 */
115
	protected function triggerCalendarActivity($action, array $calendarData, array $shares = [], array $changedProperties = []) {
116
		if (!isset($calendarData['principaluri'])) {
117
			return;
118
		}
119
120
		$principal = explode('/', $calendarData['principaluri']);
121
		$owner = array_pop($principal);
122
123
		$currentUser = $this->userSession->getUser();
124
		if ($currentUser instanceof IUser) {
125
			$currentUser = $currentUser->getUID();
126
		} else {
127
			$currentUser = $owner;
128
		}
129
130
		$event = $this->activityManager->generateEvent();
131
		$event->setApp('dav')
132
			->setObject('calendar', (int) $calendarData['id'])
133
			->setType('calendar')
134
			->setAuthor($currentUser);
135
136
		$changedVisibleInformation = array_intersect([
137
			'{DAV:}displayname',
138
			'{http://apple.com/ns/ical/}calendar-color'
139
		], array_keys($changedProperties));
140
141
		if (empty($shares) || ($action === Calendar::SUBJECT_UPDATE && empty($changedVisibleInformation))) {
142
			$users = [$owner];
143
		} else {
144
			$users = $this->getUsersForShares($shares);
145
			$users[] = $owner;
146
		}
147
148
		foreach ($users as $user) {
149
			$event->setAffectedUser($user)
150
				->setSubject(
151
					$user === $currentUser ? $action . '_self' : $action,
152
					[
153
						'actor' => $currentUser,
154
						'calendar' => [
155
							'id' => (int) $calendarData['id'],
156
							'uri' => $calendarData['uri'],
157
							'name' => $calendarData['{DAV:}displayname'],
158
						],
159
					]
160
				);
161
			$this->activityManager->publish($event);
162
		}
163
	}
164
165
	/**
166
	 * Creates activities for all related users when a calendar was (un-)shared
167
	 *
168
	 * @param array $calendarData
169
	 * @param array $shares
170
	 * @param array $add
171
	 * @param array $remove
172
	 */
173
	public function onCalendarUpdateShares(array $calendarData, array $shares, array $add, array $remove) {
174
		$principal = explode('/', $calendarData['principaluri']);
175
		$owner = $principal[2];
176
177
		$currentUser = $this->userSession->getUser();
178
		if ($currentUser instanceof IUser) {
179
			$currentUser = $currentUser->getUID();
180
		} else {
181
			$currentUser = $owner;
182
		}
183
184
		$event = $this->activityManager->generateEvent();
185
		$event->setApp('dav')
186
			->setObject('calendar', (int) $calendarData['id'])
187
			->setType('calendar')
188
			->setAuthor($currentUser);
189
190
		foreach ($remove as $principal) {
191
			// principal:principals/users/test
192
			$parts = explode(':', $principal, 2);
193
			if ($parts[0] !== 'principal') {
194
				continue;
195
			}
196
			$principal = explode('/', $parts[1]);
197
198
			if ($principal[1] === 'users') {
199
				$this->triggerActivityUser(
200
					$principal[2],
201
					$event,
202
					$calendarData,
203
					Calendar::SUBJECT_UNSHARE_USER,
204
					Calendar::SUBJECT_DELETE . '_self'
205
				);
206
207
				if ($owner !== $principal[2]) {
208
					$parameters = [
209
						'actor' => $event->getAuthor(),
210
						'calendar' => [
211
							'id' => (int) $calendarData['id'],
212
							'uri' => $calendarData['uri'],
213
							'name' => $calendarData['{DAV:}displayname'],
214
						],
215
						'user' => $principal[2],
216
					];
217
218
					if ($owner === $event->getAuthor()) {
219
						$subject = Calendar::SUBJECT_UNSHARE_USER . '_you';
220
					} elseif ($principal[2] === $event->getAuthor()) {
221
						$subject = Calendar::SUBJECT_UNSHARE_USER . '_self';
222
					} else {
223
						$event->setAffectedUser($event->getAuthor())
224
							->setSubject(Calendar::SUBJECT_UNSHARE_USER . '_you', $parameters);
225
						$this->activityManager->publish($event);
226
227
						$subject = Calendar::SUBJECT_UNSHARE_USER . '_by';
228
					}
229
230
					$event->setAffectedUser($owner)
231
						->setSubject($subject, $parameters);
232
					$this->activityManager->publish($event);
233
				}
234
			} elseif ($principal[1] === 'groups') {
235
				$this->triggerActivityGroup($principal[2], $event, $calendarData, Calendar::SUBJECT_UNSHARE_USER);
236
237
				$parameters = [
238
					'actor' => $event->getAuthor(),
239
					'calendar' => [
240
						'id' => (int) $calendarData['id'],
241
						'uri' => $calendarData['uri'],
242
						'name' => $calendarData['{DAV:}displayname'],
243
					],
244
					'group' => $principal[2],
245
				];
246
247
				if ($owner === $event->getAuthor()) {
248
					$subject = Calendar::SUBJECT_UNSHARE_GROUP . '_you';
249
				} else {
250
					$event->setAffectedUser($event->getAuthor())
251
						->setSubject(Calendar::SUBJECT_UNSHARE_GROUP . '_you', $parameters);
252
					$this->activityManager->publish($event);
253
254
					$subject = Calendar::SUBJECT_UNSHARE_GROUP . '_by';
255
				}
256
257
				$event->setAffectedUser($owner)
258
					->setSubject($subject, $parameters);
259
				$this->activityManager->publish($event);
260
			}
261
		}
262
263
		foreach ($add as $share) {
264
			if ($this->isAlreadyShared($share['href'], $shares)) {
265
				continue;
266
			}
267
268
			// principal:principals/users/test
269
			$parts = explode(':', $share['href'], 2);
270
			if ($parts[0] !== 'principal') {
271
				continue;
272
			}
273
			$principal = explode('/', $parts[1]);
274
275
			if ($principal[1] === 'users') {
276
				$this->triggerActivityUser($principal[2], $event, $calendarData, Calendar::SUBJECT_SHARE_USER);
277
278
				if ($owner !== $principal[2]) {
279
					$parameters = [
280
						'actor' => $event->getAuthor(),
281
						'calendar' => [
282
							'id' => (int) $calendarData['id'],
283
							'uri' => $calendarData['uri'],
284
							'name' => $calendarData['{DAV:}displayname'],
285
						],
286
						'user' => $principal[2],
287
					];
288
289
					if ($owner === $event->getAuthor()) {
290
						$subject = Calendar::SUBJECT_SHARE_USER . '_you';
291
					} else {
292
						$event->setAffectedUser($event->getAuthor())
293
							->setSubject(Calendar::SUBJECT_SHARE_USER . '_you', $parameters);
294
						$this->activityManager->publish($event);
295
296
						$subject = Calendar::SUBJECT_SHARE_USER . '_by';
297
					}
298
299
					$event->setAffectedUser($owner)
300
						->setSubject($subject, $parameters);
301
					$this->activityManager->publish($event);
302
				}
303
			} elseif ($principal[1] === 'groups') {
304
				$this->triggerActivityGroup($principal[2], $event, $calendarData, Calendar::SUBJECT_SHARE_USER);
305
306
				$parameters = [
307
					'actor' => $event->getAuthor(),
308
					'calendar' => [
309
						'id' => (int) $calendarData['id'],
310
						'uri' => $calendarData['uri'],
311
						'name' => $calendarData['{DAV:}displayname'],
312
					],
313
					'group' => $principal[2],
314
				];
315
316
				if ($owner === $event->getAuthor()) {
317
					$subject = Calendar::SUBJECT_SHARE_GROUP . '_you';
318
				} else {
319
					$event->setAffectedUser($event->getAuthor())
320
						->setSubject(Calendar::SUBJECT_SHARE_GROUP . '_you', $parameters);
321
					$this->activityManager->publish($event);
322
323
					$subject = Calendar::SUBJECT_SHARE_GROUP . '_by';
324
				}
325
326
				$event->setAffectedUser($owner)
327
					->setSubject($subject, $parameters);
328
				$this->activityManager->publish($event);
329
			}
330
		}
331
	}
332
333
	/**
334
	 * Checks if a calendar is already shared with a principal
335
	 *
336
	 * @param string $principal
337
	 * @param array[] $shares
338
	 * @return bool
339
	 */
340
	protected function isAlreadyShared($principal, $shares) {
341
		foreach ($shares as $share) {
342
			if ($principal === $share['href']) {
343
				return true;
344
			}
345
		}
346
347
		return false;
348
	}
349
350
	/**
351
	 * Creates the given activity for all members of the given group
352
	 *
353
	 * @param string $gid
354
	 * @param IEvent $event
355
	 * @param array $properties
356
	 * @param string $subject
357
	 */
358
	protected function triggerActivityGroup($gid, IEvent $event, array $properties, $subject) {
359
		$group = $this->groupManager->get($gid);
360
361
		if ($group instanceof IGroup) {
362
			foreach ($group->getUsers() as $user) {
363
				// Exclude current user
364
				if ($user->getUID() !== $event->getAuthor()) {
365
					$this->triggerActivityUser($user->getUID(), $event, $properties, $subject);
366
				}
367
			}
368
		}
369
	}
370
371
	/**
372
	 * Creates the given activity for the given user
373
	 *
374
	 * @param string $user
375
	 * @param IEvent $event
376
	 * @param array $properties
377
	 * @param string $subject
378
	 * @param string $subjectSelf
379
	 */
380
	protected function triggerActivityUser($user, IEvent $event, array $properties, $subject, $subjectSelf = '') {
381
		$event->setAffectedUser($user)
382
			->setSubject(
383
				$user === $event->getAuthor() && $subjectSelf ? $subjectSelf : $subject,
384
				[
385
					'actor' => $event->getAuthor(),
386
					'calendar' => [
387
						'id' => (int) $properties['id'],
388
						'uri' => $properties['uri'],
389
						'name' => $properties['{DAV:}displayname'],
390
					],
391
				]
392
			);
393
394
		$this->activityManager->publish($event);
395
	}
396
397
	/**
398
	 * Creates activities when a calendar object was created/updated/deleted
399
	 *
400
	 * @param string $action
401
	 * @param array $calendarData
402
	 * @param array $shares
403
	 * @param array $objectData
404
	 */
405
	public function onTouchCalendarObject($action, array $calendarData, array $shares, array $objectData) {
406
		if (!isset($calendarData['principaluri'])) {
407
			return;
408
		}
409
410
		$principal = explode('/', $calendarData['principaluri']);
411
		$owner = array_pop($principal);
412
413
		$currentUser = $this->userSession->getUser();
414
		if ($currentUser instanceof IUser) {
415
			$currentUser = $currentUser->getUID();
416
		} else {
417
			$currentUser = $owner;
418
		}
419
420
		$classification = $objectData['classification'] ?? CalDavBackend::CLASSIFICATION_PUBLIC;
421
		$object = $this->getObjectNameAndType($objectData);
422
		$action = $action . '_' . $object['type'];
423
424
		if ($object['type'] === 'todo' && strpos($action, Event::SUBJECT_OBJECT_UPDATE) === 0 && $object['status'] === 'COMPLETED') {
425
			$action .= '_completed';
426
		} elseif ($object['type'] === 'todo' && strpos($action, Event::SUBJECT_OBJECT_UPDATE) === 0 && $object['status'] === 'NEEDS-ACTION') {
427
			$action .= '_needs_action';
428
		}
429
430
		$event = $this->activityManager->generateEvent();
431
		$event->setApp('dav')
432
			->setObject('calendar', (int) $calendarData['id'])
433
			->setType($object['type'] === 'event' ? 'calendar_event' : 'calendar_todo')
434
			->setAuthor($currentUser);
435
436
		$users = $this->getUsersForShares($shares);
437
		$users[] = $owner;
438
439
		// Users for share can return the owner itself if the calendar is published
440
		foreach (array_unique($users) as $user) {
441
			if ($classification === CalDavBackend::CLASSIFICATION_PRIVATE && $user !== $owner) {
442
				// Private events are only shown to the owner
443
				continue;
444
			}
445
446
			$params = [
447
				'actor' => $event->getAuthor(),
448
				'calendar' => [
449
					'id' => (int) $calendarData['id'],
450
					'uri' => $calendarData['uri'],
451
					'name' => $calendarData['{DAV:}displayname'],
452
				],
453
				'object' => [
454
					'id' => $object['id'],
455
					'name' => $classification === CalDavBackend::CLASSIFICATION_CONFIDENTIAL && $user !== $owner ? 'Busy' : $object['name'],
456
					'classified' => $classification === CalDavBackend::CLASSIFICATION_CONFIDENTIAL && $user !== $owner,
457
				],
458
			];
459
460
			if ($object['type'] === 'event' && strpos($action, Event::SUBJECT_OBJECT_DELETE) === false && $this->appManager->isEnabledForUser('calendar')) {
461
				$params['object']['link']['object_uri'] = $objectData['uri'];
462
				$params['object']['link']['calendar_uri'] = $calendarData['uri'];
463
				$params['object']['link']['owner'] = $owner;
464
			}
465
466
467
			$event->setAffectedUser($user)
468
				->setSubject(
469
					$user === $currentUser ? $action . '_self' : $action,
470
					$params
471
				);
472
473
			$this->activityManager->publish($event);
474
		}
475
	}
476
477
	/**
478
	 * @param array $objectData
479
	 * @return string[]|bool
480
	 */
481
	protected function getObjectNameAndType(array $objectData) {
482
		$vObject = Reader::read($objectData['calendardata']);
483
		$component = $componentType = null;
484
		foreach ($vObject->getComponents() as $component) {
485
			if (in_array($component->name, ['VEVENT', 'VTODO'])) {
486
				$componentType = $component->name;
487
				break;
488
			}
489
		}
490
491
		if (!$componentType) {
492
			// Calendar objects must have a VEVENT or VTODO component
493
			return false;
494
		}
495
496
		if ($componentType === 'VEVENT') {
497
			return ['id' => (string) $component->UID, 'name' => (string) $component->SUMMARY, 'type' => 'event'];
498
		}
499
		return ['id' => (string) $component->UID, 'name' => (string) $component->SUMMARY, 'type' => 'todo', 'status' => (string) $component->STATUS];
500
	}
501
502
	/**
503
	 * Get all users that have access to a given calendar
504
	 *
505
	 * @param array $shares
506
	 * @return string[]
507
	 */
508
	protected function getUsersForShares(array $shares) {
509
		$users = $groups = [];
510
		foreach ($shares as $share) {
511
			$principal = explode('/', $share['{http://owncloud.org/ns}principal']);
512
			if ($principal[1] === 'users') {
513
				$users[] = $principal[2];
514
			} elseif ($principal[1] === 'groups') {
515
				$groups[] = $principal[2];
516
			}
517
		}
518
519
		if (!empty($groups)) {
520
			foreach ($groups as $gid) {
521
				$group = $this->groupManager->get($gid);
522
				if ($group instanceof IGroup) {
523
					foreach ($group->getUsers() as $user) {
524
						$users[] = $user->getUID();
525
					}
526
				}
527
			}
528
		}
529
530
		return array_unique($users);
531
	}
532
}
533