Completed
Push — master ( 11f0a5...d67396 )
by Joas
29:01 queued 21s
created
apps/user_status/lib/BackgroundJob/ClearOldStatusesBackgroundJob.php 1 patch
Indentation   +24 added lines, -24 removed lines patch added patch discarded remove patch
@@ -20,28 +20,28 @@
 block discarded – undo
20 20
  */
21 21
 class ClearOldStatusesBackgroundJob extends TimedJob {
22 22
 
23
-	/**
24
-	 * ClearOldStatusesBackgroundJob constructor.
25
-	 *
26
-	 * @param ITimeFactory $time
27
-	 * @param UserStatusMapper $mapper
28
-	 */
29
-	public function __construct(
30
-		ITimeFactory $time,
31
-		private UserStatusMapper $mapper,
32
-	) {
33
-		parent::__construct($time);
34
-
35
-		$this->setInterval(60);
36
-	}
37
-
38
-	/**
39
-	 * @inheritDoc
40
-	 */
41
-	protected function run($argument) {
42
-		$now = $this->time->getTime();
43
-
44
-		$this->mapper->clearOlderThanClearAt($now);
45
-		$this->mapper->clearStatusesOlderThan($now - StatusService::INVALIDATE_STATUS_THRESHOLD, $now);
46
-	}
23
+    /**
24
+     * ClearOldStatusesBackgroundJob constructor.
25
+     *
26
+     * @param ITimeFactory $time
27
+     * @param UserStatusMapper $mapper
28
+     */
29
+    public function __construct(
30
+        ITimeFactory $time,
31
+        private UserStatusMapper $mapper,
32
+    ) {
33
+        parent::__construct($time);
34
+
35
+        $this->setInterval(60);
36
+    }
37
+
38
+    /**
39
+     * @inheritDoc
40
+     */
41
+    protected function run($argument) {
42
+        $now = $this->time->getTime();
43
+
44
+        $this->mapper->clearOlderThanClearAt($now);
45
+        $this->mapper->clearStatusesOlderThan($now - StatusService::INVALIDATE_STATUS_THRESHOLD, $now);
46
+    }
47 47
 }
Please login to merge, or discard this patch.
apps/dav/lib/Listener/UserEventsListener.php 1 patch
Indentation   +148 added lines, -148 removed lines patch added patch discarded remove patch
@@ -35,152 +35,152 @@
 block discarded – undo
35 35
 /** @template-implements IEventListener<UserFirstTimeLoggedInEvent|UserIdAssignedEvent|BeforeUserIdUnassignedEvent|UserIdUnassignedEvent|BeforeUserDeletedEvent|UserDeletedEvent|UserCreatedEvent|UserChangedEvent|UserUpdatedEvent> */
36 36
 class UserEventsListener implements IEventListener {
37 37
 
38
-	/** @var IUser[] */
39
-	private array $usersToDelete = [];
40
-
41
-	private array $calendarsToDelete = [];
42
-	private array $subscriptionsToDelete = [];
43
-	private array $addressBooksToDelete = [];
44
-
45
-	public function __construct(
46
-		private IUserManager $userManager,
47
-		private SyncService $syncService,
48
-		private CalDavBackend $calDav,
49
-		private CardDavBackend $cardDav,
50
-		private Defaults $themingDefaults,
51
-		private ExampleContactService $exampleContactService,
52
-		private ExampleEventService $exampleEventService,
53
-		private LoggerInterface $logger,
54
-		private IJobList $jobList,
55
-	) {
56
-	}
57
-
58
-	public function handle(Event $event): void {
59
-		if ($event instanceof UserCreatedEvent) {
60
-			$this->postCreateUser($event->getUser());
61
-		} elseif ($event instanceof UserIdAssignedEvent) {
62
-			$user = $this->userManager->get($event->getUserId());
63
-			if ($user !== null) {
64
-				$this->postCreateUser($user);
65
-			}
66
-		} elseif ($event instanceof BeforeUserDeletedEvent) {
67
-			$this->preDeleteUser($event->getUser());
68
-		} elseif ($event instanceof BeforeUserIdUnassignedEvent) {
69
-			$this->preUnassignedUserId($event->getUserId());
70
-		} elseif ($event instanceof UserDeletedEvent) {
71
-			$this->postDeleteUser($event->getUid());
72
-		} elseif ($event instanceof UserIdUnassignedEvent) {
73
-			$this->postDeleteUser($event->getUserId());
74
-		} elseif ($event instanceof UserChangedEvent) {
75
-			$this->changeUser($event->getUser(), $event->getFeature());
76
-		} elseif ($event instanceof UserFirstTimeLoggedInEvent) {
77
-			$this->firstLogin($event->getUser());
78
-		} elseif ($event instanceof UserUpdatedEvent) {
79
-			$this->updateUser($event->getUser());
80
-		}
81
-	}
82
-
83
-	public function postCreateUser(IUser $user): void {
84
-		$this->syncService->updateUser($user);
85
-	}
86
-
87
-	public function updateUser(IUser $user): void {
88
-		$this->syncService->updateUser($user);
89
-	}
90
-
91
-	public function preDeleteUser(IUser $user): void {
92
-		$uid = $user->getUID();
93
-		$userPrincipalUri = 'principals/users/' . $uid;
94
-		$this->usersToDelete[$uid] = $user;
95
-		$this->calendarsToDelete[$uid] = $this->calDav->getUsersOwnCalendars($userPrincipalUri);
96
-		$this->subscriptionsToDelete[$uid] = $this->calDav->getSubscriptionsForUser($userPrincipalUri);
97
-		$this->addressBooksToDelete[$uid] = $this->cardDav->getUsersOwnAddressBooks($userPrincipalUri);
98
-	}
99
-
100
-	public function preUnassignedUserId(string $uid): void {
101
-		$user = $this->userManager->get($uid);
102
-		if ($user !== null) {
103
-			$this->usersToDelete[$uid] = $user;
104
-		}
105
-	}
106
-
107
-	public function postDeleteUser(string $uid): void {
108
-		if (isset($this->usersToDelete[$uid])) {
109
-			$this->syncService->deleteUser($this->usersToDelete[$uid]);
110
-		}
111
-
112
-		foreach ($this->calendarsToDelete[$uid] as $calendar) {
113
-			$this->calDav->deleteCalendar(
114
-				$calendar['id'],
115
-				true // Make sure the data doesn't go into the trashbin, a new user with the same UID would later see it otherwise
116
-			);
117
-		}
118
-
119
-		foreach ($this->subscriptionsToDelete[$uid] as $subscription) {
120
-			$this->calDav->deleteSubscription(
121
-				$subscription['id'],
122
-			);
123
-		}
124
-		$this->calDav->deleteAllSharesByUser('principals/users/' . $uid);
125
-
126
-		foreach ($this->addressBooksToDelete[$uid] as $addressBook) {
127
-			$this->cardDav->deleteAddressBook($addressBook['id']);
128
-		}
129
-
130
-		$this->jobList->remove(UserStatusAutomation::class, ['userId' => $uid]);
131
-
132
-		unset($this->calendarsToDelete[$uid]);
133
-		unset($this->subscriptionsToDelete[$uid]);
134
-		unset($this->addressBooksToDelete[$uid]);
135
-	}
136
-
137
-	public function changeUser(IUser $user, string $feature): void {
138
-		// This case is already covered by the account manager firing up a signal
139
-		// later on
140
-		if ($feature !== 'eMailAddress' && $feature !== 'displayName') {
141
-			$this->syncService->updateUser($user);
142
-		}
143
-	}
144
-
145
-	public function firstLogin(IUser $user): void {
146
-		$principal = 'principals/users/' . $user->getUID();
147
-
148
-		$calendarId = null;
149
-		if ($this->calDav->getCalendarsForUserCount($principal) === 0) {
150
-			try {
151
-				$calendarId = $this->calDav->createCalendar($principal, CalDavBackend::PERSONAL_CALENDAR_URI, [
152
-					'{DAV:}displayname' => CalDavBackend::PERSONAL_CALENDAR_NAME,
153
-					'{http://apple.com/ns/ical/}calendar-color' => $this->themingDefaults->getColorPrimary(),
154
-					'components' => 'VEVENT'
155
-				]);
156
-			} catch (\Exception $e) {
157
-				$this->logger->error($e->getMessage(), ['exception' => $e]);
158
-			}
159
-		}
160
-		if ($calendarId !== null) {
161
-			try {
162
-				$this->exampleEventService->createExampleEvent($calendarId);
163
-			} catch (\Exception $e) {
164
-				$this->logger->error('Failed to create example event: ' . $e->getMessage(), [
165
-					'exception' => $e,
166
-					'userId' => $user->getUID(),
167
-					'calendarId' => $calendarId,
168
-				]);
169
-			}
170
-		}
171
-
172
-		$addressBookId = null;
173
-		if ($this->cardDav->getAddressBooksForUserCount($principal) === 0) {
174
-			try {
175
-				$addressBookId = $this->cardDav->createAddressBook($principal, CardDavBackend::PERSONAL_ADDRESSBOOK_URI, [
176
-					'{DAV:}displayname' => CardDavBackend::PERSONAL_ADDRESSBOOK_NAME,
177
-				]);
178
-			} catch (\Exception $e) {
179
-				$this->logger->error($e->getMessage(), ['exception' => $e]);
180
-			}
181
-		}
182
-		if ($addressBookId) {
183
-			$this->exampleContactService->createDefaultContact($addressBookId);
184
-		}
185
-	}
38
+    /** @var IUser[] */
39
+    private array $usersToDelete = [];
40
+
41
+    private array $calendarsToDelete = [];
42
+    private array $subscriptionsToDelete = [];
43
+    private array $addressBooksToDelete = [];
44
+
45
+    public function __construct(
46
+        private IUserManager $userManager,
47
+        private SyncService $syncService,
48
+        private CalDavBackend $calDav,
49
+        private CardDavBackend $cardDav,
50
+        private Defaults $themingDefaults,
51
+        private ExampleContactService $exampleContactService,
52
+        private ExampleEventService $exampleEventService,
53
+        private LoggerInterface $logger,
54
+        private IJobList $jobList,
55
+    ) {
56
+    }
57
+
58
+    public function handle(Event $event): void {
59
+        if ($event instanceof UserCreatedEvent) {
60
+            $this->postCreateUser($event->getUser());
61
+        } elseif ($event instanceof UserIdAssignedEvent) {
62
+            $user = $this->userManager->get($event->getUserId());
63
+            if ($user !== null) {
64
+                $this->postCreateUser($user);
65
+            }
66
+        } elseif ($event instanceof BeforeUserDeletedEvent) {
67
+            $this->preDeleteUser($event->getUser());
68
+        } elseif ($event instanceof BeforeUserIdUnassignedEvent) {
69
+            $this->preUnassignedUserId($event->getUserId());
70
+        } elseif ($event instanceof UserDeletedEvent) {
71
+            $this->postDeleteUser($event->getUid());
72
+        } elseif ($event instanceof UserIdUnassignedEvent) {
73
+            $this->postDeleteUser($event->getUserId());
74
+        } elseif ($event instanceof UserChangedEvent) {
75
+            $this->changeUser($event->getUser(), $event->getFeature());
76
+        } elseif ($event instanceof UserFirstTimeLoggedInEvent) {
77
+            $this->firstLogin($event->getUser());
78
+        } elseif ($event instanceof UserUpdatedEvent) {
79
+            $this->updateUser($event->getUser());
80
+        }
81
+    }
82
+
83
+    public function postCreateUser(IUser $user): void {
84
+        $this->syncService->updateUser($user);
85
+    }
86
+
87
+    public function updateUser(IUser $user): void {
88
+        $this->syncService->updateUser($user);
89
+    }
90
+
91
+    public function preDeleteUser(IUser $user): void {
92
+        $uid = $user->getUID();
93
+        $userPrincipalUri = 'principals/users/' . $uid;
94
+        $this->usersToDelete[$uid] = $user;
95
+        $this->calendarsToDelete[$uid] = $this->calDav->getUsersOwnCalendars($userPrincipalUri);
96
+        $this->subscriptionsToDelete[$uid] = $this->calDav->getSubscriptionsForUser($userPrincipalUri);
97
+        $this->addressBooksToDelete[$uid] = $this->cardDav->getUsersOwnAddressBooks($userPrincipalUri);
98
+    }
99
+
100
+    public function preUnassignedUserId(string $uid): void {
101
+        $user = $this->userManager->get($uid);
102
+        if ($user !== null) {
103
+            $this->usersToDelete[$uid] = $user;
104
+        }
105
+    }
106
+
107
+    public function postDeleteUser(string $uid): void {
108
+        if (isset($this->usersToDelete[$uid])) {
109
+            $this->syncService->deleteUser($this->usersToDelete[$uid]);
110
+        }
111
+
112
+        foreach ($this->calendarsToDelete[$uid] as $calendar) {
113
+            $this->calDav->deleteCalendar(
114
+                $calendar['id'],
115
+                true // Make sure the data doesn't go into the trashbin, a new user with the same UID would later see it otherwise
116
+            );
117
+        }
118
+
119
+        foreach ($this->subscriptionsToDelete[$uid] as $subscription) {
120
+            $this->calDav->deleteSubscription(
121
+                $subscription['id'],
122
+            );
123
+        }
124
+        $this->calDav->deleteAllSharesByUser('principals/users/' . $uid);
125
+
126
+        foreach ($this->addressBooksToDelete[$uid] as $addressBook) {
127
+            $this->cardDav->deleteAddressBook($addressBook['id']);
128
+        }
129
+
130
+        $this->jobList->remove(UserStatusAutomation::class, ['userId' => $uid]);
131
+
132
+        unset($this->calendarsToDelete[$uid]);
133
+        unset($this->subscriptionsToDelete[$uid]);
134
+        unset($this->addressBooksToDelete[$uid]);
135
+    }
136
+
137
+    public function changeUser(IUser $user, string $feature): void {
138
+        // This case is already covered by the account manager firing up a signal
139
+        // later on
140
+        if ($feature !== 'eMailAddress' && $feature !== 'displayName') {
141
+            $this->syncService->updateUser($user);
142
+        }
143
+    }
144
+
145
+    public function firstLogin(IUser $user): void {
146
+        $principal = 'principals/users/' . $user->getUID();
147
+
148
+        $calendarId = null;
149
+        if ($this->calDav->getCalendarsForUserCount($principal) === 0) {
150
+            try {
151
+                $calendarId = $this->calDav->createCalendar($principal, CalDavBackend::PERSONAL_CALENDAR_URI, [
152
+                    '{DAV:}displayname' => CalDavBackend::PERSONAL_CALENDAR_NAME,
153
+                    '{http://apple.com/ns/ical/}calendar-color' => $this->themingDefaults->getColorPrimary(),
154
+                    'components' => 'VEVENT'
155
+                ]);
156
+            } catch (\Exception $e) {
157
+                $this->logger->error($e->getMessage(), ['exception' => $e]);
158
+            }
159
+        }
160
+        if ($calendarId !== null) {
161
+            try {
162
+                $this->exampleEventService->createExampleEvent($calendarId);
163
+            } catch (\Exception $e) {
164
+                $this->logger->error('Failed to create example event: ' . $e->getMessage(), [
165
+                    'exception' => $e,
166
+                    'userId' => $user->getUID(),
167
+                    'calendarId' => $calendarId,
168
+                ]);
169
+            }
170
+        }
171
+
172
+        $addressBookId = null;
173
+        if ($this->cardDav->getAddressBooksForUserCount($principal) === 0) {
174
+            try {
175
+                $addressBookId = $this->cardDav->createAddressBook($principal, CardDavBackend::PERSONAL_ADDRESSBOOK_URI, [
176
+                    '{DAV:}displayname' => CardDavBackend::PERSONAL_ADDRESSBOOK_NAME,
177
+                ]);
178
+            } catch (\Exception $e) {
179
+                $this->logger->error($e->getMessage(), ['exception' => $e]);
180
+            }
181
+        }
182
+        if ($addressBookId) {
183
+            $this->exampleContactService->createDefaultContact($addressBookId);
184
+        }
185
+    }
186 186
 }
Please login to merge, or discard this patch.
apps/dav/lib/BackgroundJob/UserStatusAutomation.php 1 patch
Indentation   +212 added lines, -212 removed lines patch added patch discarded remove patch
@@ -28,216 +28,216 @@
 block discarded – undo
28 28
 use Sabre\VObject\Recur\RRuleIterator;
29 29
 
30 30
 class UserStatusAutomation extends TimedJob {
31
-	public function __construct(
32
-		private ITimeFactory $timeFactory,
33
-		private IDBConnection $connection,
34
-		private IJobList $jobList,
35
-		private LoggerInterface $logger,
36
-		private IManager $manager,
37
-		private IConfig $config,
38
-		private IAvailabilityCoordinator $coordinator,
39
-		private IUserManager $userManager,
40
-	) {
41
-		parent::__construct($timeFactory);
42
-
43
-		// interval = 0 might look odd, but it's intentional. last_run is set to
44
-		// the user's next available time, so the job runs immediately when
45
-		// that time comes.
46
-		$this->setInterval(0);
47
-	}
48
-
49
-	/**
50
-	 * @inheritDoc
51
-	 */
52
-	protected function run($argument) {
53
-		if (!isset($argument['userId'])) {
54
-			$this->jobList->remove(self::class, $argument);
55
-			$this->logger->info('Removing invalid ' . self::class . ' background job');
56
-			return;
57
-		}
58
-
59
-		$userId = $argument['userId'];
60
-		$user = $this->userManager->get($userId);
61
-		if ($user === null) {
62
-			return;
63
-		}
64
-
65
-		$ooo = $this->coordinator->getCurrentOutOfOfficeData($user);
66
-
67
-		$continue = $this->processOutOfOfficeData($user, $ooo);
68
-		if ($continue === false) {
69
-			return;
70
-		}
71
-
72
-		$property = $this->getAvailabilityFromPropertiesTable($userId);
73
-		$hasDndForOfficeHours = $this->config->getUserValue($userId, 'dav', 'user_status_automation', 'no') === 'yes';
74
-
75
-		if (!$property) {
76
-			// We found no ooo data and no availability settings, so we need to delete the job because there is no next runtime
77
-			$this->logger->info('Removing ' . self::class . ' background job for user "' . $userId . '" because the user has no valid availability rules and no OOO data set');
78
-			$this->jobList->remove(self::class, $argument);
79
-			$this->manager->revertUserStatus($user->getUID(), IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND);
80
-			$this->manager->revertUserStatus($user->getUID(), IUserStatus::MESSAGE_OUT_OF_OFFICE, IUserStatus::DND);
81
-			return;
82
-		}
83
-
84
-		$this->processAvailability($property, $user->getUID(), $hasDndForOfficeHours);
85
-	}
86
-
87
-	protected function setLastRunToNextToggleTime(string $userId, int $timestamp): void {
88
-		$query = $this->connection->getQueryBuilder();
89
-
90
-		$query->update('jobs')
91
-			->set('last_run', $query->createNamedParameter($timestamp, IQueryBuilder::PARAM_INT))
92
-			->where($query->expr()->eq('id', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT)));
93
-		$query->executeStatement();
94
-
95
-		$this->logger->debug('Updated user status automation last_run to ' . $timestamp . ' for user ' . $userId);
96
-	}
97
-
98
-	/**
99
-	 * @param string $userId
100
-	 * @return false|string
101
-	 */
102
-	protected function getAvailabilityFromPropertiesTable(string $userId) {
103
-		$propertyPath = 'calendars/' . $userId . '/inbox';
104
-		$propertyName = '{' . Plugin::NS_CALDAV . '}calendar-availability';
105
-
106
-		$query = $this->connection->getQueryBuilder();
107
-		$query->select('propertyvalue')
108
-			->from('properties')
109
-			->where($query->expr()->eq('userid', $query->createNamedParameter($userId)))
110
-			->andWhere($query->expr()->eq('propertypath', $query->createNamedParameter($propertyPath)))
111
-			->andWhere($query->expr()->eq('propertyname', $query->createNamedParameter($propertyName)))
112
-			->setMaxResults(1);
113
-
114
-		$result = $query->executeQuery();
115
-		$property = $result->fetchOne();
116
-		$result->closeCursor();
117
-
118
-		return $property;
119
-	}
120
-
121
-	/**
122
-	 * @param string $property
123
-	 * @param $userId
124
-	 * @param $argument
125
-	 * @return void
126
-	 */
127
-	private function processAvailability(string $property, string $userId, bool $hasDndForOfficeHours): void {
128
-		$isCurrentlyAvailable = false;
129
-		$nextPotentialToggles = [];
130
-
131
-		$now = $this->time->getDateTime();
132
-		$lastMidnight = (clone $now)->setTime(0, 0);
133
-
134
-		$vObject = Reader::read($property);
135
-		foreach ($vObject->getComponents() as $component) {
136
-			if ($component->name !== 'VAVAILABILITY') {
137
-				continue;
138
-			}
139
-			/** @var VAvailability $component */
140
-			$availables = $component->getComponents();
141
-			foreach ($availables as $available) {
142
-				/** @var Available $available */
143
-				if ($available->name === 'AVAILABLE') {
144
-					/** @var \DateTimeImmutable $originalStart */
145
-					/** @var \DateTimeImmutable $originalEnd */
146
-					[$originalStart, $originalEnd] = $available->getEffectiveStartEnd();
147
-
148
-					// Little shenanigans to fix the automation on the day the rules were adjusted
149
-					// Otherwise the $originalStart would match rules for Thursdays on a Friday, etc.
150
-					// So we simply wind back a week and then fastForward to the next occurrence
151
-					// since today's midnight, which then also accounts for the week days.
152
-					$effectiveStart = \DateTime::createFromImmutable($originalStart)->sub(new \DateInterval('P7D'));
153
-					$effectiveEnd = \DateTime::createFromImmutable($originalEnd)->sub(new \DateInterval('P7D'));
154
-
155
-					try {
156
-						$it = new RRuleIterator((string)$available->RRULE, $effectiveStart);
157
-						$it->fastForward($lastMidnight);
158
-
159
-						$startToday = $it->current();
160
-						if ($startToday && $startToday <= $now) {
161
-							$duration = $effectiveStart->diff($effectiveEnd);
162
-							$endToday = $startToday->add($duration);
163
-							if ($endToday > $now) {
164
-								// User is currently available
165
-								// Also queuing the end time as next status toggle
166
-								$isCurrentlyAvailable = true;
167
-								$nextPotentialToggles[] = $endToday->getTimestamp();
168
-							}
169
-
170
-							// Availability enabling already done for today,
171
-							// so jump to the next recurrence to find the next status toggle
172
-							$it->next();
173
-						}
174
-
175
-						if ($it->current()) {
176
-							$nextPotentialToggles[] = $it->current()->getTimestamp();
177
-						}
178
-					} catch (\Exception $e) {
179
-						$this->logger->error($e->getMessage(), ['exception' => $e]);
180
-					}
181
-				}
182
-			}
183
-		}
184
-
185
-		if (empty($nextPotentialToggles)) {
186
-			$this->logger->info('Removing ' . self::class . ' background job for user "' . $userId . '" because the user has no valid availability rules set');
187
-			$this->jobList->remove(self::class, ['userId' => $userId]);
188
-			$this->manager->revertUserStatus($userId, IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND);
189
-			return;
190
-		}
191
-
192
-		$nextAutomaticToggle = min($nextPotentialToggles);
193
-		$this->setLastRunToNextToggleTime($userId, $nextAutomaticToggle - 1);
194
-
195
-		if ($isCurrentlyAvailable) {
196
-			$this->logger->debug('User is currently available, reverting DND status if applicable');
197
-			$this->manager->revertUserStatus($userId, IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND);
198
-			$this->logger->debug('User status automation ran');
199
-			return;
200
-		}
201
-
202
-		if (!$hasDndForOfficeHours) {
203
-			// Office hours are not set to DND, so there is nothing to do.
204
-			return;
205
-		}
206
-
207
-		$this->logger->debug('User is currently NOT available, reverting call and meeting status if applicable and then setting DND');
208
-		$this->manager->setUserStatus($userId, IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND, true);
209
-		$this->logger->debug('User status automation ran');
210
-	}
211
-
212
-	private function processOutOfOfficeData(IUser $user, ?IOutOfOfficeData $ooo): bool {
213
-		if (empty($ooo)) {
214
-			// Reset the user status if the absence doesn't exist
215
-			$this->logger->debug('User has no OOO period in effect, reverting DND status if applicable');
216
-			$this->manager->revertUserStatus($user->getUID(), IUserStatus::MESSAGE_OUT_OF_OFFICE, IUserStatus::DND);
217
-			// We need to also run the availability automation
218
-			return true;
219
-		}
220
-
221
-		if (!$this->coordinator->isInEffect($ooo)) {
222
-			// Reset the user status if the absence is (no longer) in effect
223
-			$this->logger->debug('User has no OOO period in effect, reverting DND status if applicable');
224
-			$this->manager->revertUserStatus($user->getUID(), IUserStatus::MESSAGE_OUT_OF_OFFICE, IUserStatus::DND);
225
-
226
-			if ($ooo->getStartDate() > $this->time->getTime()) {
227
-				// Set the next run to take place at the start of the ooo period if it is in the future
228
-				// This might be overwritten if there is an availability setting, but we can't determine
229
-				// if this is the case here
230
-				$this->setLastRunToNextToggleTime($user->getUID(), $ooo->getStartDate());
231
-			}
232
-			return true;
233
-		}
234
-
235
-		$this->logger->debug('User is currently in an OOO period, reverting other automated status and setting OOO DND status');
236
-		$this->manager->setUserStatus($user->getUID(), IUserStatus::MESSAGE_OUT_OF_OFFICE, IUserStatus::DND, true, $ooo->getShortMessage());
237
-
238
-		// Run at the end of an ooo period to return to availability / regular user status
239
-		// If it's overwritten by a custom status in the meantime, there's nothing we can do about it
240
-		$this->setLastRunToNextToggleTime($user->getUID(), $ooo->getEndDate());
241
-		return false;
242
-	}
31
+    public function __construct(
32
+        private ITimeFactory $timeFactory,
33
+        private IDBConnection $connection,
34
+        private IJobList $jobList,
35
+        private LoggerInterface $logger,
36
+        private IManager $manager,
37
+        private IConfig $config,
38
+        private IAvailabilityCoordinator $coordinator,
39
+        private IUserManager $userManager,
40
+    ) {
41
+        parent::__construct($timeFactory);
42
+
43
+        // interval = 0 might look odd, but it's intentional. last_run is set to
44
+        // the user's next available time, so the job runs immediately when
45
+        // that time comes.
46
+        $this->setInterval(0);
47
+    }
48
+
49
+    /**
50
+     * @inheritDoc
51
+     */
52
+    protected function run($argument) {
53
+        if (!isset($argument['userId'])) {
54
+            $this->jobList->remove(self::class, $argument);
55
+            $this->logger->info('Removing invalid ' . self::class . ' background job');
56
+            return;
57
+        }
58
+
59
+        $userId = $argument['userId'];
60
+        $user = $this->userManager->get($userId);
61
+        if ($user === null) {
62
+            return;
63
+        }
64
+
65
+        $ooo = $this->coordinator->getCurrentOutOfOfficeData($user);
66
+
67
+        $continue = $this->processOutOfOfficeData($user, $ooo);
68
+        if ($continue === false) {
69
+            return;
70
+        }
71
+
72
+        $property = $this->getAvailabilityFromPropertiesTable($userId);
73
+        $hasDndForOfficeHours = $this->config->getUserValue($userId, 'dav', 'user_status_automation', 'no') === 'yes';
74
+
75
+        if (!$property) {
76
+            // We found no ooo data and no availability settings, so we need to delete the job because there is no next runtime
77
+            $this->logger->info('Removing ' . self::class . ' background job for user "' . $userId . '" because the user has no valid availability rules and no OOO data set');
78
+            $this->jobList->remove(self::class, $argument);
79
+            $this->manager->revertUserStatus($user->getUID(), IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND);
80
+            $this->manager->revertUserStatus($user->getUID(), IUserStatus::MESSAGE_OUT_OF_OFFICE, IUserStatus::DND);
81
+            return;
82
+        }
83
+
84
+        $this->processAvailability($property, $user->getUID(), $hasDndForOfficeHours);
85
+    }
86
+
87
+    protected function setLastRunToNextToggleTime(string $userId, int $timestamp): void {
88
+        $query = $this->connection->getQueryBuilder();
89
+
90
+        $query->update('jobs')
91
+            ->set('last_run', $query->createNamedParameter($timestamp, IQueryBuilder::PARAM_INT))
92
+            ->where($query->expr()->eq('id', $query->createNamedParameter($this->getId(), IQueryBuilder::PARAM_INT)));
93
+        $query->executeStatement();
94
+
95
+        $this->logger->debug('Updated user status automation last_run to ' . $timestamp . ' for user ' . $userId);
96
+    }
97
+
98
+    /**
99
+     * @param string $userId
100
+     * @return false|string
101
+     */
102
+    protected function getAvailabilityFromPropertiesTable(string $userId) {
103
+        $propertyPath = 'calendars/' . $userId . '/inbox';
104
+        $propertyName = '{' . Plugin::NS_CALDAV . '}calendar-availability';
105
+
106
+        $query = $this->connection->getQueryBuilder();
107
+        $query->select('propertyvalue')
108
+            ->from('properties')
109
+            ->where($query->expr()->eq('userid', $query->createNamedParameter($userId)))
110
+            ->andWhere($query->expr()->eq('propertypath', $query->createNamedParameter($propertyPath)))
111
+            ->andWhere($query->expr()->eq('propertyname', $query->createNamedParameter($propertyName)))
112
+            ->setMaxResults(1);
113
+
114
+        $result = $query->executeQuery();
115
+        $property = $result->fetchOne();
116
+        $result->closeCursor();
117
+
118
+        return $property;
119
+    }
120
+
121
+    /**
122
+     * @param string $property
123
+     * @param $userId
124
+     * @param $argument
125
+     * @return void
126
+     */
127
+    private function processAvailability(string $property, string $userId, bool $hasDndForOfficeHours): void {
128
+        $isCurrentlyAvailable = false;
129
+        $nextPotentialToggles = [];
130
+
131
+        $now = $this->time->getDateTime();
132
+        $lastMidnight = (clone $now)->setTime(0, 0);
133
+
134
+        $vObject = Reader::read($property);
135
+        foreach ($vObject->getComponents() as $component) {
136
+            if ($component->name !== 'VAVAILABILITY') {
137
+                continue;
138
+            }
139
+            /** @var VAvailability $component */
140
+            $availables = $component->getComponents();
141
+            foreach ($availables as $available) {
142
+                /** @var Available $available */
143
+                if ($available->name === 'AVAILABLE') {
144
+                    /** @var \DateTimeImmutable $originalStart */
145
+                    /** @var \DateTimeImmutable $originalEnd */
146
+                    [$originalStart, $originalEnd] = $available->getEffectiveStartEnd();
147
+
148
+                    // Little shenanigans to fix the automation on the day the rules were adjusted
149
+                    // Otherwise the $originalStart would match rules for Thursdays on a Friday, etc.
150
+                    // So we simply wind back a week and then fastForward to the next occurrence
151
+                    // since today's midnight, which then also accounts for the week days.
152
+                    $effectiveStart = \DateTime::createFromImmutable($originalStart)->sub(new \DateInterval('P7D'));
153
+                    $effectiveEnd = \DateTime::createFromImmutable($originalEnd)->sub(new \DateInterval('P7D'));
154
+
155
+                    try {
156
+                        $it = new RRuleIterator((string)$available->RRULE, $effectiveStart);
157
+                        $it->fastForward($lastMidnight);
158
+
159
+                        $startToday = $it->current();
160
+                        if ($startToday && $startToday <= $now) {
161
+                            $duration = $effectiveStart->diff($effectiveEnd);
162
+                            $endToday = $startToday->add($duration);
163
+                            if ($endToday > $now) {
164
+                                // User is currently available
165
+                                // Also queuing the end time as next status toggle
166
+                                $isCurrentlyAvailable = true;
167
+                                $nextPotentialToggles[] = $endToday->getTimestamp();
168
+                            }
169
+
170
+                            // Availability enabling already done for today,
171
+                            // so jump to the next recurrence to find the next status toggle
172
+                            $it->next();
173
+                        }
174
+
175
+                        if ($it->current()) {
176
+                            $nextPotentialToggles[] = $it->current()->getTimestamp();
177
+                        }
178
+                    } catch (\Exception $e) {
179
+                        $this->logger->error($e->getMessage(), ['exception' => $e]);
180
+                    }
181
+                }
182
+            }
183
+        }
184
+
185
+        if (empty($nextPotentialToggles)) {
186
+            $this->logger->info('Removing ' . self::class . ' background job for user "' . $userId . '" because the user has no valid availability rules set');
187
+            $this->jobList->remove(self::class, ['userId' => $userId]);
188
+            $this->manager->revertUserStatus($userId, IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND);
189
+            return;
190
+        }
191
+
192
+        $nextAutomaticToggle = min($nextPotentialToggles);
193
+        $this->setLastRunToNextToggleTime($userId, $nextAutomaticToggle - 1);
194
+
195
+        if ($isCurrentlyAvailable) {
196
+            $this->logger->debug('User is currently available, reverting DND status if applicable');
197
+            $this->manager->revertUserStatus($userId, IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND);
198
+            $this->logger->debug('User status automation ran');
199
+            return;
200
+        }
201
+
202
+        if (!$hasDndForOfficeHours) {
203
+            // Office hours are not set to DND, so there is nothing to do.
204
+            return;
205
+        }
206
+
207
+        $this->logger->debug('User is currently NOT available, reverting call and meeting status if applicable and then setting DND');
208
+        $this->manager->setUserStatus($userId, IUserStatus::MESSAGE_AVAILABILITY, IUserStatus::DND, true);
209
+        $this->logger->debug('User status automation ran');
210
+    }
211
+
212
+    private function processOutOfOfficeData(IUser $user, ?IOutOfOfficeData $ooo): bool {
213
+        if (empty($ooo)) {
214
+            // Reset the user status if the absence doesn't exist
215
+            $this->logger->debug('User has no OOO period in effect, reverting DND status if applicable');
216
+            $this->manager->revertUserStatus($user->getUID(), IUserStatus::MESSAGE_OUT_OF_OFFICE, IUserStatus::DND);
217
+            // We need to also run the availability automation
218
+            return true;
219
+        }
220
+
221
+        if (!$this->coordinator->isInEffect($ooo)) {
222
+            // Reset the user status if the absence is (no longer) in effect
223
+            $this->logger->debug('User has no OOO period in effect, reverting DND status if applicable');
224
+            $this->manager->revertUserStatus($user->getUID(), IUserStatus::MESSAGE_OUT_OF_OFFICE, IUserStatus::DND);
225
+
226
+            if ($ooo->getStartDate() > $this->time->getTime()) {
227
+                // Set the next run to take place at the start of the ooo period if it is in the future
228
+                // This might be overwritten if there is an availability setting, but we can't determine
229
+                // if this is the case here
230
+                $this->setLastRunToNextToggleTime($user->getUID(), $ooo->getStartDate());
231
+            }
232
+            return true;
233
+        }
234
+
235
+        $this->logger->debug('User is currently in an OOO period, reverting other automated status and setting OOO DND status');
236
+        $this->manager->setUserStatus($user->getUID(), IUserStatus::MESSAGE_OUT_OF_OFFICE, IUserStatus::DND, true, $ooo->getShortMessage());
237
+
238
+        // Run at the end of an ooo period to return to availability / regular user status
239
+        // If it's overwritten by a custom status in the meantime, there's nothing we can do about it
240
+        $this->setLastRunToNextToggleTime($user->getUID(), $ooo->getEndDate());
241
+        return false;
242
+    }
243 243
 }
Please login to merge, or discard this patch.
apps/dav/tests/unit/DAV/Listener/UserEventsListenerTest.php 1 patch
Indentation   +154 added lines, -154 removed lines patch added patch discarded remove patch
@@ -26,158 +26,158 @@
 block discarded – undo
26 26
 use Test\TestCase;
27 27
 
28 28
 class UserEventsListenerTest extends TestCase {
29
-	private IUserManager&MockObject $userManager;
30
-	private SyncService&MockObject $syncService;
31
-	private CalDavBackend&MockObject $calDavBackend;
32
-	private CardDavBackend&MockObject $cardDavBackend;
33
-	private Defaults&MockObject $defaults;
34
-	private ExampleContactService&MockObject $exampleContactService;
35
-	private ExampleEventService&MockObject $exampleEventService;
36
-	private LoggerInterface&MockObject $logger;
37
-
38
-	private UserEventsListener $userEventsListener;
39
-
40
-	protected function setUp(): void {
41
-		parent::setUp();
42
-
43
-		$this->userManager = $this->createMock(IUserManager::class);
44
-		$this->syncService = $this->createMock(SyncService::class);
45
-		$this->calDavBackend = $this->createMock(CalDavBackend::class);
46
-		$this->cardDavBackend = $this->createMock(CardDavBackend::class);
47
-		$this->defaults = $this->createMock(Defaults::class);
48
-		$this->exampleContactService = $this->createMock(ExampleContactService::class);
49
-		$this->exampleEventService = $this->createMock(ExampleEventService::class);
50
-		$this->logger = $this->createMock(LoggerInterface::class);
51
-		$this->jobList = $this->createMock(IJobList::class);
52
-
53
-		$this->userEventsListener = new UserEventsListener(
54
-			$this->userManager,
55
-			$this->syncService,
56
-			$this->calDavBackend,
57
-			$this->cardDavBackend,
58
-			$this->defaults,
59
-			$this->exampleContactService,
60
-			$this->exampleEventService,
61
-			$this->logger,
62
-			$this->jobList,
63
-		);
64
-	}
65
-
66
-	public function test(): void {
67
-		$user = $this->createMock(IUser::class);
68
-		$user->expects($this->once())->method('getUID')->willReturn('newUser');
69
-
70
-		$this->defaults->expects($this->once())->method('getColorPrimary')->willReturn('#745bca');
71
-
72
-		$this->calDavBackend->expects($this->once())->method('getCalendarsForUserCount')->willReturn(0);
73
-		$this->calDavBackend->expects($this->once())->method('createCalendar')->with(
74
-			'principals/users/newUser',
75
-			'personal', [
76
-				'{DAV:}displayname' => 'Personal',
77
-				'{http://apple.com/ns/ical/}calendar-color' => '#745bca',
78
-				'components' => 'VEVENT'
79
-			])
80
-			->willReturn(1000);
81
-		$this->calDavBackend->expects(self::never())
82
-			->method('getCalendarsForUser');
83
-		$this->exampleEventService->expects(self::once())
84
-			->method('createExampleEvent')
85
-			->with(1000);
86
-
87
-		$this->cardDavBackend->expects($this->once())->method('getAddressBooksForUserCount')->willReturn(0);
88
-		$this->cardDavBackend->expects($this->once())->method('createAddressBook')->with(
89
-			'principals/users/newUser',
90
-			'contacts', ['{DAV:}displayname' => 'Contacts']);
91
-
92
-		$this->userEventsListener->firstLogin($user);
93
-	}
94
-
95
-	public function testWithExisting(): void {
96
-		$user = $this->createMock(IUser::class);
97
-		$user->expects($this->once())->method('getUID')->willReturn('newUser');
98
-
99
-		$this->calDavBackend->expects($this->once())->method('getCalendarsForUserCount')->willReturn(1);
100
-		$this->calDavBackend->expects($this->never())->method('createCalendar');
101
-		$this->calDavBackend->expects(self::never())
102
-			->method('createCalendar');
103
-		$this->exampleEventService->expects(self::never())
104
-			->method('createExampleEvent');
105
-
106
-		$this->cardDavBackend->expects($this->once())->method('getAddressBooksForUserCount')->willReturn(1);
107
-		$this->cardDavBackend->expects($this->never())->method('createAddressBook');
108
-
109
-		$this->userEventsListener->firstLogin($user);
110
-	}
111
-
112
-	public function testWithBirthdayCalendar(): void {
113
-		$user = $this->createMock(IUser::class);
114
-		$user->expects($this->once())->method('getUID')->willReturn('newUser');
115
-
116
-		$this->defaults->expects($this->once())->method('getColorPrimary')->willReturn('#745bca');
117
-
118
-		$this->calDavBackend->expects($this->once())->method('getCalendarsForUserCount')->willReturn(0);
119
-		$this->calDavBackend->expects($this->once())->method('createCalendar')->with(
120
-			'principals/users/newUser',
121
-			'personal', [
122
-				'{DAV:}displayname' => 'Personal',
123
-				'{http://apple.com/ns/ical/}calendar-color' => '#745bca',
124
-				'components' => 'VEVENT'
125
-			]);
126
-
127
-		$this->cardDavBackend->expects($this->once())->method('getAddressBooksForUserCount')->willReturn(0);
128
-		$this->cardDavBackend->expects($this->once())->method('createAddressBook')->with(
129
-			'principals/users/newUser',
130
-			'contacts', ['{DAV:}displayname' => 'Contacts']);
131
-
132
-		$this->userEventsListener->firstLogin($user);
133
-	}
134
-
135
-	public function testDeleteCalendar(): void {
136
-		$user = $this->createMock(IUser::class);
137
-		$user->expects($this->once())->method('getUID')->willReturn('newUser');
138
-
139
-		$this->syncService->expects($this->once())
140
-			->method('deleteUser');
141
-
142
-		$this->calDavBackend->expects($this->once())->method('getUsersOwnCalendars')->willReturn([
143
-			['id' => 'personal']
144
-		]);
145
-		$this->calDavBackend->expects($this->once())->method('getSubscriptionsForUser')->willReturn([
146
-			['id' => 'some-subscription']
147
-		]);
148
-		$this->calDavBackend->expects($this->once())->method('deleteCalendar')->with('personal');
149
-		$this->calDavBackend->expects($this->once())->method('deleteSubscription')->with('some-subscription');
150
-		$this->calDavBackend->expects($this->once())->method('deleteAllSharesByUser');
151
-
152
-		$this->cardDavBackend->expects($this->once())->method('getUsersOwnAddressBooks')->willReturn([
153
-			['id' => 'personal']
154
-		]);
155
-		$this->cardDavBackend->expects($this->once())->method('deleteAddressBook');
156
-
157
-		$this->userEventsListener->preDeleteUser($user);
158
-		$this->userEventsListener->postDeleteUser('newUser');
159
-	}
160
-
161
-	public function testDeleteUserAutomationEvent(): void {
162
-		$user = $this->createMock(IUser::class);
163
-		$user->expects($this->once())->method('getUID')->willReturn('newUser');
164
-
165
-		$this->syncService->expects($this->once())
166
-			->method('deleteUser');
167
-
168
-		$this->calDavBackend->expects($this->once())->method('getUsersOwnCalendars')->willReturn([
169
-			['id' => []]
170
-		]);
171
-		$this->calDavBackend->expects($this->once())->method('getSubscriptionsForUser')->willReturn([
172
-			['id' => []]
173
-		]);
174
-		$this->cardDavBackend->expects($this->once())->method('getUsersOwnAddressBooks')->willReturn([
175
-			['id' => []]
176
-		]);
177
-
178
-		$this->jobList->expects(self::once())->method('remove')->with(UserStatusAutomation::class, ['userId' => 'newUser']);
179
-
180
-		$this->userEventsListener->preDeleteUser($user);
181
-		$this->userEventsListener->postDeleteUser('newUser');
182
-	}
29
+    private IUserManager&MockObject $userManager;
30
+    private SyncService&MockObject $syncService;
31
+    private CalDavBackend&MockObject $calDavBackend;
32
+    private CardDavBackend&MockObject $cardDavBackend;
33
+    private Defaults&MockObject $defaults;
34
+    private ExampleContactService&MockObject $exampleContactService;
35
+    private ExampleEventService&MockObject $exampleEventService;
36
+    private LoggerInterface&MockObject $logger;
37
+
38
+    private UserEventsListener $userEventsListener;
39
+
40
+    protected function setUp(): void {
41
+        parent::setUp();
42
+
43
+        $this->userManager = $this->createMock(IUserManager::class);
44
+        $this->syncService = $this->createMock(SyncService::class);
45
+        $this->calDavBackend = $this->createMock(CalDavBackend::class);
46
+        $this->cardDavBackend = $this->createMock(CardDavBackend::class);
47
+        $this->defaults = $this->createMock(Defaults::class);
48
+        $this->exampleContactService = $this->createMock(ExampleContactService::class);
49
+        $this->exampleEventService = $this->createMock(ExampleEventService::class);
50
+        $this->logger = $this->createMock(LoggerInterface::class);
51
+        $this->jobList = $this->createMock(IJobList::class);
52
+
53
+        $this->userEventsListener = new UserEventsListener(
54
+            $this->userManager,
55
+            $this->syncService,
56
+            $this->calDavBackend,
57
+            $this->cardDavBackend,
58
+            $this->defaults,
59
+            $this->exampleContactService,
60
+            $this->exampleEventService,
61
+            $this->logger,
62
+            $this->jobList,
63
+        );
64
+    }
65
+
66
+    public function test(): void {
67
+        $user = $this->createMock(IUser::class);
68
+        $user->expects($this->once())->method('getUID')->willReturn('newUser');
69
+
70
+        $this->defaults->expects($this->once())->method('getColorPrimary')->willReturn('#745bca');
71
+
72
+        $this->calDavBackend->expects($this->once())->method('getCalendarsForUserCount')->willReturn(0);
73
+        $this->calDavBackend->expects($this->once())->method('createCalendar')->with(
74
+            'principals/users/newUser',
75
+            'personal', [
76
+                '{DAV:}displayname' => 'Personal',
77
+                '{http://apple.com/ns/ical/}calendar-color' => '#745bca',
78
+                'components' => 'VEVENT'
79
+            ])
80
+            ->willReturn(1000);
81
+        $this->calDavBackend->expects(self::never())
82
+            ->method('getCalendarsForUser');
83
+        $this->exampleEventService->expects(self::once())
84
+            ->method('createExampleEvent')
85
+            ->with(1000);
86
+
87
+        $this->cardDavBackend->expects($this->once())->method('getAddressBooksForUserCount')->willReturn(0);
88
+        $this->cardDavBackend->expects($this->once())->method('createAddressBook')->with(
89
+            'principals/users/newUser',
90
+            'contacts', ['{DAV:}displayname' => 'Contacts']);
91
+
92
+        $this->userEventsListener->firstLogin($user);
93
+    }
94
+
95
+    public function testWithExisting(): void {
96
+        $user = $this->createMock(IUser::class);
97
+        $user->expects($this->once())->method('getUID')->willReturn('newUser');
98
+
99
+        $this->calDavBackend->expects($this->once())->method('getCalendarsForUserCount')->willReturn(1);
100
+        $this->calDavBackend->expects($this->never())->method('createCalendar');
101
+        $this->calDavBackend->expects(self::never())
102
+            ->method('createCalendar');
103
+        $this->exampleEventService->expects(self::never())
104
+            ->method('createExampleEvent');
105
+
106
+        $this->cardDavBackend->expects($this->once())->method('getAddressBooksForUserCount')->willReturn(1);
107
+        $this->cardDavBackend->expects($this->never())->method('createAddressBook');
108
+
109
+        $this->userEventsListener->firstLogin($user);
110
+    }
111
+
112
+    public function testWithBirthdayCalendar(): void {
113
+        $user = $this->createMock(IUser::class);
114
+        $user->expects($this->once())->method('getUID')->willReturn('newUser');
115
+
116
+        $this->defaults->expects($this->once())->method('getColorPrimary')->willReturn('#745bca');
117
+
118
+        $this->calDavBackend->expects($this->once())->method('getCalendarsForUserCount')->willReturn(0);
119
+        $this->calDavBackend->expects($this->once())->method('createCalendar')->with(
120
+            'principals/users/newUser',
121
+            'personal', [
122
+                '{DAV:}displayname' => 'Personal',
123
+                '{http://apple.com/ns/ical/}calendar-color' => '#745bca',
124
+                'components' => 'VEVENT'
125
+            ]);
126
+
127
+        $this->cardDavBackend->expects($this->once())->method('getAddressBooksForUserCount')->willReturn(0);
128
+        $this->cardDavBackend->expects($this->once())->method('createAddressBook')->with(
129
+            'principals/users/newUser',
130
+            'contacts', ['{DAV:}displayname' => 'Contacts']);
131
+
132
+        $this->userEventsListener->firstLogin($user);
133
+    }
134
+
135
+    public function testDeleteCalendar(): void {
136
+        $user = $this->createMock(IUser::class);
137
+        $user->expects($this->once())->method('getUID')->willReturn('newUser');
138
+
139
+        $this->syncService->expects($this->once())
140
+            ->method('deleteUser');
141
+
142
+        $this->calDavBackend->expects($this->once())->method('getUsersOwnCalendars')->willReturn([
143
+            ['id' => 'personal']
144
+        ]);
145
+        $this->calDavBackend->expects($this->once())->method('getSubscriptionsForUser')->willReturn([
146
+            ['id' => 'some-subscription']
147
+        ]);
148
+        $this->calDavBackend->expects($this->once())->method('deleteCalendar')->with('personal');
149
+        $this->calDavBackend->expects($this->once())->method('deleteSubscription')->with('some-subscription');
150
+        $this->calDavBackend->expects($this->once())->method('deleteAllSharesByUser');
151
+
152
+        $this->cardDavBackend->expects($this->once())->method('getUsersOwnAddressBooks')->willReturn([
153
+            ['id' => 'personal']
154
+        ]);
155
+        $this->cardDavBackend->expects($this->once())->method('deleteAddressBook');
156
+
157
+        $this->userEventsListener->preDeleteUser($user);
158
+        $this->userEventsListener->postDeleteUser('newUser');
159
+    }
160
+
161
+    public function testDeleteUserAutomationEvent(): void {
162
+        $user = $this->createMock(IUser::class);
163
+        $user->expects($this->once())->method('getUID')->willReturn('newUser');
164
+
165
+        $this->syncService->expects($this->once())
166
+            ->method('deleteUser');
167
+
168
+        $this->calDavBackend->expects($this->once())->method('getUsersOwnCalendars')->willReturn([
169
+            ['id' => []]
170
+        ]);
171
+        $this->calDavBackend->expects($this->once())->method('getSubscriptionsForUser')->willReturn([
172
+            ['id' => []]
173
+        ]);
174
+        $this->cardDavBackend->expects($this->once())->method('getUsersOwnAddressBooks')->willReturn([
175
+            ['id' => []]
176
+        ]);
177
+
178
+        $this->jobList->expects(self::once())->method('remove')->with(UserStatusAutomation::class, ['userId' => 'newUser']);
179
+
180
+        $this->userEventsListener->preDeleteUser($user);
181
+        $this->userEventsListener->postDeleteUser('newUser');
182
+    }
183 183
 }
Please login to merge, or discard this patch.