Passed
Push — master ( 56b68c...ae4907 )
by Blizzz
13:29 queued 10s
created

StatusService::findByUserId()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @copyright Copyright (c) 2020, Georg Ehrke
7
 *
8
 * @author Georg Ehrke <[email protected]>
9
 * @author Joas Schilling <[email protected]>
10
 *
11
 * @license GNU AGPL version 3 or any later version
12
 *
13
 * This program is free software: you can redistribute it and/or modify
14
 * it under the terms of the GNU Affero General Public License as
15
 * published by the Free Software Foundation, either version 3 of the
16
 * License, or (at your option) any later version.
17
 *
18
 * This program is distributed in the hope that it will be useful,
19
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21
 * GNU Affero General Public License for more details.
22
 *
23
 * You should have received a copy of the GNU Affero General Public License
24
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
25
 *
26
 */
27
namespace OCA\UserStatus\Service;
28
29
use OCA\UserStatus\Db\UserStatus;
30
use OCA\UserStatus\Db\UserStatusMapper;
31
use OCA\UserStatus\Exception\InvalidClearAtException;
32
use OCA\UserStatus\Exception\InvalidMessageIdException;
33
use OCA\UserStatus\Exception\InvalidStatusIconException;
34
use OCA\UserStatus\Exception\InvalidStatusTypeException;
35
use OCA\UserStatus\Exception\StatusMessageTooLongException;
36
use OCP\AppFramework\Db\DoesNotExistException;
37
use OCP\AppFramework\Utility\ITimeFactory;
38
use OCP\UserStatus\IUserStatus;
39
40
/**
41
 * Class StatusService
42
 *
43
 * @package OCA\UserStatus\Service
44
 */
45
class StatusService {
46
47
	/** @var UserStatusMapper */
48
	private $mapper;
49
50
	/** @var ITimeFactory */
51
	private $timeFactory;
52
53
	/** @var PredefinedStatusService */
54
	private $predefinedStatusService;
55
56
	/** @var EmojiService */
57
	private $emojiService;
58
59
	/**
60
	 * List of priorities ordered by their priority
61
	 */
62
	public const PRIORITY_ORDERED_STATUSES = [
63
		IUserStatus::ONLINE,
64
		IUserStatus::AWAY,
65
		IUserStatus::DND,
66
		IUserStatus::INVISIBLE,
67
		IUserStatus::OFFLINE
68
	];
69
70
	/**
71
	 * List of statuses that persist the clear-up
72
	 * or UserLiveStatusEvents
73
	 */
74
	public const PERSISTENT_STATUSES = [
75
		IUserStatus::AWAY,
76
		IUserStatus::DND,
77
		IUserStatus::INVISIBLE,
78
	];
79
80
	/** @var int */
81
	public const INVALIDATE_STATUS_THRESHOLD = 15 /* minutes */ * 60 /* seconds */;
82
83
	/** @var int */
84
	public const MAXIMUM_MESSAGE_LENGTH = 80;
85
86
	/**
87
	 * StatusService constructor.
88
	 *
89
	 * @param UserStatusMapper $mapper
90
	 * @param ITimeFactory $timeFactory
91
	 * @param PredefinedStatusService $defaultStatusService,
92
	 * @param EmojiService $emojiService
93
	 */
94
	public function __construct(UserStatusMapper $mapper,
95
								ITimeFactory $timeFactory,
96
								PredefinedStatusService $defaultStatusService,
97
								EmojiService $emojiService) {
98
		$this->mapper = $mapper;
99
		$this->timeFactory = $timeFactory;
100
		$this->predefinedStatusService = $defaultStatusService;
101
		$this->emojiService = $emojiService;
102
	}
103
104
	/**
105
	 * @param int|null $limit
106
	 * @param int|null $offset
107
	 * @return UserStatus[]
108
	 */
109
	public function findAll(?int $limit = null, ?int $offset = null): array {
110
		return array_map(function ($status) {
111
			return $this->processStatus($status);
112
		}, $this->mapper->findAll($limit, $offset));
113
	}
114
115
	/**
116
	 * @param int|null $limit
117
	 * @param int|null $offset
118
	 * @return array
119
	 */
120
	public function findAllRecentStatusChanges(?int $limit = null, ?int $offset = null): array {
121
		return array_map(function ($status) {
122
			return $this->processStatus($status);
123
		}, $this->mapper->findAllRecent($limit, $offset));
124
	}
125
126
	/**
127
	 * @param string $userId
128
	 * @return UserStatus
129
	 * @throws DoesNotExistException
130
	 */
131
	public function findByUserId(string $userId):UserStatus {
132
		return $this->processStatus($this->mapper->findByUserId($userId));
133
	}
134
135
	/**
136
	 * @param array $userIds
137
	 * @return UserStatus[]
138
	 */
139
	public function findByUserIds(array $userIds):array {
140
		return array_map(function ($status) {
141
			return $this->processStatus($status);
142
		}, $this->mapper->findByUserIds($userIds));
143
	}
144
145
	/**
146
	 * @param string $userId
147
	 * @param string $status
148
	 * @param int|null $statusTimestamp
149
	 * @param bool $isUserDefined
150
	 * @return UserStatus
151
	 * @throws InvalidStatusTypeException
152
	 */
153
	public function setStatus(string $userId,
154
							  string $status,
155
							  ?int $statusTimestamp,
156
							  bool $isUserDefined): UserStatus {
157
		try {
158
			$userStatus = $this->mapper->findByUserId($userId);
159
		} catch (DoesNotExistException $ex) {
160
			$userStatus = new UserStatus();
161
			$userStatus->setUserId($userId);
162
		}
163
164
		// Check if status-type is valid
165
		if (!\in_array($status, self::PRIORITY_ORDERED_STATUSES, true)) {
166
			throw new InvalidStatusTypeException('Status-type "' . $status . '" is not supported');
167
		}
168
		if ($statusTimestamp === null) {
169
			$statusTimestamp = $this->timeFactory->getTime();
170
		}
171
172
		$userStatus->setStatus($status);
173
		$userStatus->setStatusTimestamp($statusTimestamp);
174
		$userStatus->setIsUserDefined($isUserDefined);
175
176
		if ($userStatus->getId() === null) {
0 ignored issues
show
introduced by
The condition $userStatus->getId() === null is always false.
Loading history...
177
			return $this->mapper->insert($userStatus);
178
		}
179
180
		return $this->mapper->update($userStatus);
181
	}
182
183
	/**
184
	 * @param string $userId
185
	 * @param string $messageId
186
	 * @param int|null $clearAt
187
	 * @return UserStatus
188
	 * @throws InvalidMessageIdException
189
	 * @throws InvalidClearAtException
190
	 */
191
	public function setPredefinedMessage(string $userId,
192
										 string $messageId,
193
										 ?int $clearAt): UserStatus {
194
		try {
195
			$userStatus = $this->mapper->findByUserId($userId);
196
		} catch (DoesNotExistException $ex) {
197
			$userStatus = new UserStatus();
198
			$userStatus->setUserId($userId);
199
			$userStatus->setStatus(IUserStatus::OFFLINE);
200
			$userStatus->setStatusTimestamp(0);
201
			$userStatus->setIsUserDefined(false);
202
		}
203
204
		if (!$this->predefinedStatusService->isValidId($messageId)) {
205
			throw new InvalidMessageIdException('Message-Id "' . $messageId . '" is not supported');
206
		}
207
208
		// Check that clearAt is in the future
209
		if ($clearAt !== null && $clearAt < $this->timeFactory->getTime()) {
210
			throw new InvalidClearAtException('ClearAt is in the past');
211
		}
212
213
		$userStatus->setMessageId($messageId);
214
		$userStatus->setCustomIcon(null);
215
		$userStatus->setCustomMessage(null);
216
		$userStatus->setClearAt($clearAt);
217
218
		if ($userStatus->getId() === null) {
0 ignored issues
show
introduced by
The condition $userStatus->getId() === null is always false.
Loading history...
219
			return $this->mapper->insert($userStatus);
220
		}
221
222
		return $this->mapper->update($userStatus);
223
	}
224
225
	/**
226
	 * @param string $userId
227
	 * @param string|null $statusIcon
228
	 * @param string|null $message
229
	 * @param int|null $clearAt
230
	 * @return UserStatus
231
	 * @throws InvalidClearAtException
232
	 * @throws InvalidStatusIconException
233
	 * @throws StatusMessageTooLongException
234
	 */
235
	public function setCustomMessage(string $userId,
236
									 ?string $statusIcon,
237
									 string $message,
238
									 ?int $clearAt): UserStatus {
239
		try {
240
			$userStatus = $this->mapper->findByUserId($userId);
241
		} catch (DoesNotExistException $ex) {
242
			$userStatus = new UserStatus();
243
			$userStatus->setUserId($userId);
244
			$userStatus->setStatus(IUserStatus::OFFLINE);
245
			$userStatus->setStatusTimestamp(0);
246
			$userStatus->setIsUserDefined(false);
247
		}
248
249
		// Check if statusIcon contains only one character
250
		if ($statusIcon !== null && !$this->emojiService->isValidEmoji($statusIcon)) {
251
			throw new InvalidStatusIconException('Status-Icon is longer than one character');
252
		}
253
		// Check for maximum length of custom message
254
		if (\mb_strlen($message) > self::MAXIMUM_MESSAGE_LENGTH) {
255
			throw new StatusMessageTooLongException('Message is longer than supported length of ' . self::MAXIMUM_MESSAGE_LENGTH . ' characters');
256
		}
257
		// Check that clearAt is in the future
258
		if ($clearAt !== null && $clearAt < $this->timeFactory->getTime()) {
259
			throw new InvalidClearAtException('ClearAt is in the past');
260
		}
261
262
		$userStatus->setMessageId(null);
263
		$userStatus->setCustomIcon($statusIcon);
264
		$userStatus->setCustomMessage($message);
265
		$userStatus->setClearAt($clearAt);
266
267
		if ($userStatus->getId() === null) {
0 ignored issues
show
introduced by
The condition $userStatus->getId() === null is always false.
Loading history...
268
			return $this->mapper->insert($userStatus);
269
		}
270
271
		return $this->mapper->update($userStatus);
272
	}
273
274
	/**
275
	 * @param string $userId
276
	 * @return bool
277
	 */
278
	public function clearStatus(string $userId): bool {
279
		try {
280
			$userStatus = $this->mapper->findByUserId($userId);
281
		} catch (DoesNotExistException $ex) {
282
			// if there is no status to remove, just return
283
			return false;
284
		}
285
286
		$userStatus->setStatus(IUserStatus::OFFLINE);
287
		$userStatus->setStatusTimestamp(0);
288
		$userStatus->setIsUserDefined(false);
289
290
		$this->mapper->update($userStatus);
291
		return true;
292
	}
293
294
	/**
295
	 * @param string $userId
296
	 * @return bool
297
	 */
298
	public function clearMessage(string $userId): bool {
299
		try {
300
			$userStatus = $this->mapper->findByUserId($userId);
301
		} catch (DoesNotExistException $ex) {
302
			// if there is no status to remove, just return
303
			return false;
304
		}
305
306
		$userStatus->setMessageId(null);
307
		$userStatus->setCustomMessage(null);
308
		$userStatus->setCustomIcon(null);
309
		$userStatus->setClearAt(null);
310
311
		$this->mapper->update($userStatus);
312
		return true;
313
	}
314
315
	/**
316
	 * @param string $userId
317
	 * @return bool
318
	 */
319
	public function removeUserStatus(string $userId): bool {
320
		try {
321
			$userStatus = $this->mapper->findByUserId($userId);
322
		} catch (DoesNotExistException $ex) {
323
			// if there is no status to remove, just return
324
			return false;
325
		}
326
327
		$this->mapper->delete($userStatus);
328
		return true;
329
	}
330
331
	/**
332
	 * Processes a status to check if custom message is still
333
	 * up to date and provides translated default status if needed
334
	 *
335
	 * @param UserStatus $status
336
	 * @returns UserStatus
337
	 */
338
	private function processStatus(UserStatus $status): UserStatus {
339
		$clearAt = $status->getClearAt();
340
341
		if ($status->getStatusTimestamp() < $this->timeFactory->getTime() - self::INVALIDATE_STATUS_THRESHOLD
342
			&& (!$status->getIsUserDefined() || $status->getStatus() === IUserStatus::ONLINE)) {
343
			$this->cleanStatus($status);
344
		}
345
		if ($clearAt !== null && $clearAt < $this->timeFactory->getTime()) {
346
			$this->cleanStatusMessage($status);
347
		}
348
		if ($status->getMessageId() !== null) {
0 ignored issues
show
introduced by
The condition $status->getMessageId() !== null is always true.
Loading history...
349
			$this->addDefaultMessage($status);
350
		}
351
352
		return $status;
353
	}
354
355
	/**
356
	 * @param UserStatus $status
357
	 */
358
	private function cleanStatus(UserStatus $status): void {
359
		if ($status->getStatus() === IUserStatus::OFFLINE && !$status->getIsUserDefined()) {
360
			return;
361
		}
362
363
		$status->setStatus(IUserStatus::OFFLINE);
364
		$status->setStatusTimestamp($this->timeFactory->getTime());
365
		$status->setIsUserDefined(false);
366
367
		$this->mapper->update($status);
368
	}
369
370
	/**
371
	 * @param UserStatus $status
372
	 */
373
	private function cleanStatusMessage(UserStatus $status): void {
374
		$status->setMessageId(null);
375
		$status->setCustomIcon(null);
376
		$status->setCustomMessage(null);
377
		$status->setClearAt(null);
378
379
		$this->mapper->update($status);
380
	}
381
382
	/**
383
	 * @param UserStatus $status
384
	 */
385
	private function addDefaultMessage(UserStatus $status): void {
386
		// If the message is predefined, insert the translated message and icon
387
		$predefinedMessage = $this->predefinedStatusService->getDefaultStatusById($status->getMessageId());
388
		if ($predefinedMessage !== null) {
389
			$status->setCustomMessage($predefinedMessage['message']);
390
			$status->setCustomIcon($predefinedMessage['icon']);
391
		}
392
	}
393
}
394