Completed
Push — stable13 ( 81b02e...968127 )
by Morris
11:09 queued 10s
created

Backend::onTouchCalendarObject()   D

Complexity

Conditions 18
Paths 37

Size

Total Lines 60

Duplication

Lines 0
Ratio 0 %

Importance

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