CardService   F
last analyzed

Complexity

Total Complexity 84

Size/Duplication

Total Lines 620
Duplicated Lines 24.84 %

Coupling/Cohesion

Components 1
Dependencies 18

Importance

Changes 0
Metric Value
wmc 84
lcom 1
cbo 18
dl 154
loc 620
rs 1.98
c 0
b 0
f 0

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 33 33 1
A enrich() 0 11 1
A fetchDeleted() 0 8 2
A find() 0 15 2
B create() 0 45 10
A delete() 22 22 3
D update() 0 79 18
B rename() 0 28 6
C reorder() 0 51 11
A archive() 23 23 3
A unarchive() 22 22 3
A assignLabel() 27 27 5
A removeLabel() 27 27 5
B assignUser() 0 44 8
B unassignUser() 0 28 6

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like CardService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CardService, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * @copyright Copyright (c) 2016 Julius Härtl <[email protected]>
4
 *
5
 * @copyright Copyright (c) 2019, Alexandru Puiu ([email protected])
6
 *
7
 * @author Julius Härtl <[email protected]>
8
 * @author Maxence Lange <[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\Deck\Service;
28
29
use OCA\Deck\Activity\ActivityManager;
30
use OCA\Deck\Activity\ChangeSet;
31
use OCA\Deck\Db\AssignedUsers;
32
use OCA\Deck\Db\AssignedUsersMapper;
33
use OCA\Deck\Db\Card;
34
use OCA\Deck\Db\CardMapper;
35
use OCA\Deck\Db\Acl;
36
use OCA\Deck\Db\ChangeHelper;
37
use OCA\Deck\Db\StackMapper;
38
use OCA\Deck\Notification\NotificationHelper;
39
use OCA\Deck\Db\BoardMapper;
40
use OCA\Deck\Db\LabelMapper;
41
use OCA\Deck\NotFoundException;
42
use OCA\Deck\StatusException;
43
use OCA\Deck\BadRequestException;
44
use OCP\Comments\ICommentsManager;
45
use OCP\IUserManager;
46
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
47
use Symfony\Component\EventDispatcher\GenericEvent;
48
49
class CardService {
50
51
	private $cardMapper;
52
	private $stackMapper;
53
	private $boardMapper;
54
	private $labelMapper;
55
	private $permissionService;
56
	private $boardService;
57
	private $notificationHelper;
58
	private $assignedUsersMapper;
59
	private $attachmentService;
60
	private $currentUser;
61
	private $activityManager;
62
	private $commentsManager;
63
	private $changeHelper;
64
	/** @var EventDispatcherInterface */
65
	private $eventDispatcher;
66
	private $userManager;
67
68 View Code Duplication
	public function __construct(
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
69
		CardMapper $cardMapper,
70
		StackMapper $stackMapper,
71
		BoardMapper $boardMapper,
72
		LabelMapper $labelMapper,
73
		PermissionService $permissionService,
74
		BoardService $boardService,
75
		NotificationHelper $notificationHelper,
76
		AssignedUsersMapper $assignedUsersMapper,
77
		AttachmentService $attachmentService,
78
		ActivityManager $activityManager,
79
		ICommentsManager $commentsManager,
80
		IUserManager $userManager,
81
		ChangeHelper $changeHelper,
82
		EventDispatcherInterface $eventDispatcher,
83
		$userId
84
	) {
85
		$this->cardMapper = $cardMapper;
86
		$this->stackMapper = $stackMapper;
87
		$this->boardMapper = $boardMapper;
88
		$this->labelMapper = $labelMapper;
89
		$this->permissionService = $permissionService;
90
		$this->boardService = $boardService;
91
		$this->notificationHelper = $notificationHelper;
92
		$this->assignedUsersMapper = $assignedUsersMapper;
93
		$this->attachmentService = $attachmentService;
94
		$this->activityManager = $activityManager;
95
		$this->commentsManager = $commentsManager;
96
		$this->userManager = $userManager;
97
		$this->changeHelper = $changeHelper;
98
		$this->eventDispatcher = $eventDispatcher;
99
		$this->currentUser = $userId;
100
	}
101
102
	public function enrich($card) {
103
		$cardId = $card->getId();
104
		$this->cardMapper->mapOwner($card);
105
		$card->setAssignedUsers($this->assignedUsersMapper->find($cardId));
106
		$card->setLabels($this->labelMapper->findAssignedLabelsForCard($cardId));
107
		$card->setAttachmentCount($this->attachmentService->count($cardId));
108
		$user = $this->userManager->get($this->currentUser);
109
		$lastRead = $this->commentsManager->getReadMark('deckCard', (string)$card->getId(), $user);
0 ignored issues
show
Bug introduced by
It seems like $user defined by $this->userManager->get($this->currentUser) on line 108 can be null; however, OCP\Comments\ICommentsManager::getReadMark() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
110
		$count = $this->commentsManager->getNumberOfCommentsForObject('deckCard', (string)$card->getId(), $lastRead);
111
		$card->setCommentsUnread($count);
112
	}
113
114
	public function fetchDeleted($boardId) {
115
		$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
116
		$cards = $this->cardMapper->findDeleted($boardId);
117
		foreach ($cards as $card) {
118
			$this->enrich($card);
119
		}
120
		return $cards;
121
	}
122
123
	/**
124
	 * @param $cardId
125
	 * @return \OCA\Deck\Db\RelationalEntity
126
	 * @throws \OCA\Deck\NoPermissionException
127
	 * @throws \OCP\AppFramework\Db\DoesNotExistException
128
	 * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
129
	 * @throws BadRequestException
130
	 */
131
	public function find($cardId) {
132
133
		if (is_numeric($cardId) === false) {
134
			throw new BadRequestException('card id must be a number');
135
		}
136
137
		$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
138
		$card = $this->cardMapper->find($cardId);
139
		$assignedUsers = $this->assignedUsersMapper->find($card->getId());
140
		$attachments = $this->attachmentService->findAll($cardId, true);
141
		$card->setAssignedUsers($assignedUsers);
142
		$card->setAttachments($attachments);
143
		$this->enrich($card);
144
		return $card;
145
	}
146
147
	/**
148
	 * @param $title
149
	 * @param $stackId
150
	 * @param $type
151
	 * @param integer $order
152
	 * @param $description
153
	 * @param $owner
154
	 * @return \OCP\AppFramework\Db\Entity
155
	 * @throws StatusException
156
	 * @throws \OCA\Deck\NoPermissionException
157
	 * @throws \OCP\AppFramework\Db\DoesNotExistException
158
	 * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
159
	 * @throws BadrequestException
160
	 */
161
	public function create($title, $stackId, $type, $order, $owner, $description = '') {
162
		if ($title === 'false' || $title === null) {
163
			throw new BadRequestException('title must be provided');
164
		}
165
166
		if (is_numeric($stackId) === false) {
167
			throw new BadRequestException('stack id must be a number');
168
		}
169
170
		if ($type === 'false' || $type === null) {
171
			throw new BadRequestException('type must be provided');
172
		}
173
174
		if (is_numeric($order) === false) {
175
			throw new BadRequestException('order must be a number');
176
		}
177
178
		if ($owner === false || $owner === null) {
179
			throw new BadRequestException('owner must be provided');
180
		}
181
182
		$this->permissionService->checkPermission($this->stackMapper, $stackId, Acl::PERMISSION_EDIT);
183
		if ($this->boardService->isArchived($this->stackMapper, $stackId)) {
184
			throw new StatusException('Operation not allowed. This board is archived.');
185
		}
186
		$card = new Card();
187
		$card->setTitle($title);
188
		$card->setStackId($stackId);
189
		$card->setType($type);
190
		$card->setOrder($order);
191
		$card->setOwner($owner);
192
		$card->setDescription($description);
193
		$card = $this->cardMapper->insert($card);
194
		$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_CARD_CREATE);
195
		$this->changeHelper->cardChanged($card->getId(), false);
196
197
		$this->eventDispatcher->dispatch(
198
			'\OCA\Deck\Card::onCreate',
199
			new GenericEvent(
200
				null, ['id' => $card->getId(), 'card' => $card, 'userId' => $owner, 'stackId' => $stackId]
201
			)
202
		);
203
204
		return $card;
205
	}
206
207
	/**
208
	 * @param $id
209
	 * @return \OCP\AppFramework\Db\Entity
210
	 * @throws StatusException
211
	 * @throws \OCA\Deck\NoPermissionException
212
	 * @throws \OCP\AppFramework\Db\DoesNotExistException
213
	 * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
214
	 * @throws BadRequestException
215
	 */
216 View Code Duplication
	public function delete($id) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
217
218
		if (is_numeric($id) === false) {
219
			throw new BadRequestException('card id must be a number');
220
		}
221
222
		$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT);
223
		if ($this->boardService->isArchived($this->cardMapper, $id)) {
224
			throw new StatusException('Operation not allowed. This board is archived.');
225
		}
226
		$card = $this->cardMapper->find($id);
227
		$card->setDeletedAt(time());
228
		$this->cardMapper->update($card);
229
		$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_CARD_DELETE);
230
		$this->changeHelper->cardChanged($card->getId(), false);
231
232
		$this->eventDispatcher->dispatch(
233
			'\OCA\Deck\Card::onDelete', new GenericEvent(null, ['id' => $id, 'card' => $card])
234
		);
235
236
		return $card;
237
	}
238
239
	/**
240
	 * @param $id
241
	 * @param $title
242
	 * @param $stackId
243
	 * @param $type
244
	 * @param $order
245
	 * @param $description
246
	 * @param $owner
247
	 * @param $duedate
248
	 * @return \OCP\AppFramework\Db\Entity
249
	 * @throws StatusException
250
	 * @throws \OCA\Deck\NoPermissionException
251
	 * @throws \OCP\AppFramework\Db\DoesNotExistException
252
	 * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
253
	 * @throws BadRequestException
254
	 */
255
	public function update($id, $title, $stackId, $type, $order = 0, $description = '', $owner, $duedate = null, $deletedAt = null, $archived = null) {
256
257
		if (is_numeric($id) === false) {
258
			throw new BadRequestException('card id must be a number');
259
		}
260
261
		if ($title === false || $title === null) {
262
			throw new BadRequestException('title must be provided');
263
		}
264
265
		if (is_numeric($stackId) === false) {
266
			throw new BadRequestException('stack id must be a number $$$');
267
		}
268
269
		if ($type === false || $type === null) {
270
			throw new BadRequestException('type must be provided');
271
		}
272
273
		if ($owner === false || $owner === null) {
274
			throw new BadRequestException('owner must be provided');
275
		}
276
277
		$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT);
278
		if ($this->boardService->isArchived($this->cardMapper, $id)) {
279
			throw new StatusException('Operation not allowed. This board is archived.');
280
		}
281
		$card = $this->cardMapper->find($id);
282
		if ($archived !== null && $card->getArchived() && $archived === true) {
283
			throw new StatusException('Operation not allowed. This card is archived.');
284
		}
285
		$changes = new ChangeSet($card);
286
		if ($card->getLastEditor() !== $this->currentUser && $card->getLastEditor() !== null) {
287
			$this->activityManager->triggerEvent(
288
				ActivityManager::DECK_OBJECT_CARD,
289
				$card,
290
				ActivityManager::SUBJECT_CARD_UPDATE_DESCRIPTION,
291
				[
292
					'before' => $card->getDescriptionPrev(),
293
					'after' => $card->getDescription()
294
				],
295
				$card->getLastEditor()
296
			);
297
298
			$card->setDescriptionPrev($card->getDescription());
299
			$card->setLastEditor($this->currentUser);
300
		}
301
		$card->setTitle($title);
302
		$card->setStackId($stackId);
303
		$card->setType($type);
304
		$card->setOrder($order);
305
		$card->setOwner($owner);
306
		$card->setDuedate($duedate);
307
		if ($deletedAt) {
308
			$card->setDeletedAt($deletedAt);
309
		}
310
		if ($archived !== null) {
311
			$card->setArchived($archived);
312
		}
313
314
315
		// Trigger update events before setting description as it is handled separately
316
		$changes->setAfter($card);
317
		$this->activityManager->triggerUpdateEvents(ActivityManager::DECK_OBJECT_CARD, $changes, ActivityManager::SUBJECT_CARD_UPDATE);
318
319
		if ($card->getDescriptionPrev() === null) {
320
			$card->setDescriptionPrev($card->getDescription());
321
		}
322
		$card->setDescription($description);
323
324
325
		$card = $this->cardMapper->update($card);
326
		$this->changeHelper->cardChanged($card->getId(), true);
327
328
		$this->eventDispatcher->dispatch(
329
			'\OCA\Deck\Card::onUpdate', new GenericEvent(null, ['id' => $id, 'card' => $card])
330
		);
331
332
		return $card;
333
	}
334
335
	/**
336
	 * @param $id
337
	 * @param $title
338
	 * @return \OCP\AppFramework\Db\Entity
339
	 * @throws StatusException
340
	 * @throws \OCA\Deck\NoPermissionException
341
	 * @throws \OCP\AppFramework\Db\DoesNotExistException
342
	 * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
343
	 * @throws BadRequestException
344
	 */
345
	public function rename($id, $title) {
346
347
		if (is_numeric($id) === false) {
348
			throw new BadRequestException('id must be a number');
349
		}
350
351
		if ($title === false || $title === null) {
352
			throw new BadRequestException('title must be provided');
353
		}
354
355
		$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT);
356
		if ($this->boardService->isArchived($this->cardMapper, $id)) {
357
			throw new StatusException('Operation not allowed. This board is archived.');
358
		}
359
		$card = $this->cardMapper->find($id);
360
		if ($card->getArchived()) {
361
			throw new StatusException('Operation not allowed. This card is archived.');
362
		}
363
		$card->setTitle($title);
364
		$this->changeHelper->cardChanged($card->getId(), false);
365
		$update = $this->cardMapper->update($card);
366
367
		$this->eventDispatcher->dispatch(
368
			'\OCA\Deck\Card::onUpdate', new GenericEvent(null, ['id' => $id, 'card' => $card])
369
		);
370
371
		return $update;
372
	}
373
374
	/**
375
	 * @param $id
376
	 * @param $stackId
377
	 * @param $order
378
	 * @return array
379
	 * @throws StatusException
380
	 * @throws \OCA\Deck\NoPermissionException
381
	 * @throws \OCP\AppFramework\Db\DoesNotExistException
382
	 * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
383
	 * @throws BadRequestException
384
	 */
385
	public function reorder($id, $stackId, $order) {
386
387
		if (is_numeric($id) === false) {
388
			throw new BadRequestException('card id must be a number');
389
		}
390
391
		if (is_numeric($stackId) === false) {
392
			throw new BadRequestException('stack id must be a number');
393
		}
394
395
		if (is_numeric($order) === false) {
396
			throw new BadRequestException('order must be a number');
397
		}
398
399
		$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT);
400
		if ($this->boardService->isArchived($this->cardMapper, $id)) {
401
			throw new StatusException('Operation not allowed. This board is archived.');
402
		}
403
404
		$card = $this->cardMapper->find($id);
405
		if ($card->getArchived()) {
406
			throw new StatusException('Operation not allowed. This card is archived.');
407
		}
408
		$card->setStackId($stackId);
409
		$this->cardMapper->update($card);
410
411
		$cards = $this->cardMapper->findAll($stackId);
412
		$result = [];
413
		$i = 0;
414
		foreach ($cards as $card) {
415
			if ($card->getArchived()) {
416
				throw new StatusException('Operation not allowed. This card is archived.');
417
			}
418
			if ($card->id === $id) {
419
				$card->setOrder($order);
420
				$card->setLastModified(time());
421
			}
422
423
			if ($i === $order) {
424
				$i++;
425
			}
426
427
			if ($card->id !== $id) {
428
				$card->setOrder($i++);
429
			}
430
			$this->cardMapper->update($card);
431
			$result[$card->getOrder()] = $card;
432
		}
433
		$this->changeHelper->cardChanged($id, false);
434
		return array_values($result);
435
	}
436
437
	/**
438
	 * @param $id
439
	 * @return \OCP\AppFramework\Db\Entity
440
	 * @throws StatusException
441
	 * @throws \OCA\Deck\NoPermissionException
442
	 * @throws \OCP\AppFramework\Db\DoesNotExistException
443
	 * @throws \OCP\AppFramework\Db\
444
	 * @throws BadRequestException
445
	 */
446 View Code Duplication
	public function archive($id) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
447
448
		if (is_numeric($id) === false) {
449
			throw new BadRequestException('id must be a number');
450
		}
451
452
		$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT);
453
		if ($this->boardService->isArchived($this->cardMapper, $id)) {
454
			throw new StatusException('Operation not allowed. This board is archived.');
455
		}
456
		$card = $this->cardMapper->find($id);
457
		$card->setArchived(true);
458
		$newCard = $this->cardMapper->update($card);
459
		$this->notificationHelper->markDuedateAsRead($card);
460
		$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $newCard, ActivityManager::SUBJECT_CARD_UPDATE_ARCHIVE);
461
		$this->changeHelper->cardChanged($id, false);
462
463
		$this->eventDispatcher->dispatch(
464
			'\OCA\Deck\Card::onUpdate', new GenericEvent(null, ['id' => $id, 'card' => $card])
465
		);
466
467
		return $newCard;
468
	}
469
470
	/**
471
	 * @param $id
472
	 * @return \OCP\AppFramework\Db\Entity
473
	 * @throws StatusException
474
	 * @throws \OCA\Deck\NoPermissionException
475
	 * @throws \OCP\AppFramework\Db\DoesNotExistException
476
	 * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
477
	 * @throws BadRequestException
478
	 */
479 View Code Duplication
	public function unarchive($id) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
480
481
		if (is_numeric($id) === false) {
482
			throw new BadRequestException('id must be a number');
483
		}
484
485
		$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT);
486
		if ($this->boardService->isArchived($this->cardMapper, $id)) {
487
			throw new StatusException('Operation not allowed. This board is archived.');
488
		}
489
		$card = $this->cardMapper->find($id);
490
		$card->setArchived(false);
491
		$newCard = $this->cardMapper->update($card);
492
		$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $newCard, ActivityManager::SUBJECT_CARD_UPDATE_UNARCHIVE);
493
		$this->changeHelper->cardChanged($id, false);
494
495
		$this->eventDispatcher->dispatch(
496
			'\OCA\Deck\Card::onUpdate', new GenericEvent(null, ['id' => $id, 'card' => $card])
497
		);
498
499
		return $newCard;
500
	}
501
502
	/**
503
	 * @param $cardId
504
	 * @param $labelId
505
	 * @throws StatusException
506
	 * @throws \OCA\Deck\NoPermissionException
507
	 * @throws \OCP\AppFramework\Db\DoesNotExistException
508
	 * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
509
	 * @throws BadRequestException
510
	 */
511 View Code Duplication
	public function assignLabel($cardId, $labelId) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
512
513
		if (is_numeric($cardId) === false) {
514
			throw new BadRequestException('card id must be a number');
515
		}
516
517
		if (is_numeric($labelId) === false) {
518
			throw new BadRequestException('label id must be a number');
519
		}
520
521
		$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_EDIT);
522
		if ($this->boardService->isArchived($this->cardMapper, $cardId)) {
523
			throw new StatusException('Operation not allowed. This board is archived.');
524
		}
525
		$card = $this->cardMapper->find($cardId);
526
		if ($card->getArchived()) {
527
			throw new StatusException('Operation not allowed. This card is archived.');
528
		}
529
		$label = $this->labelMapper->find($labelId);
530
		$this->cardMapper->assignLabel($cardId, $labelId);
531
		$this->changeHelper->cardChanged($cardId, false);
532
		$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_LABEL_ASSIGN, ['label' => $label]);
533
534
		$this->eventDispatcher->dispatch(
535
			'\OCA\Deck\Card::onUpdate', new GenericEvent(null, ['id' => $cardId, 'card' => $card])
536
		);
537
	}
538
539
	/**
540
	 * @param $cardId
541
	 * @param $labelId
542
	 * @throws StatusException
543
	 * @throws \OCA\Deck\NoPermissionException
544
	 * @throws \OCP\AppFramework\Db\DoesNotExistException
545
	 * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
546
	 * @throws BadRequestException
547
	 */
548 View Code Duplication
	public function removeLabel($cardId, $labelId) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
549
550
		if (is_numeric($cardId) === false) {
551
			throw new BadRequestException('card id must be a number');
552
		}
553
554
		if (is_numeric($labelId) === false) {
555
			throw new BadRequestException('label id must be a number');
556
		}
557
558
		$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_EDIT);
559
		if ($this->boardService->isArchived($this->cardMapper, $cardId)) {
560
			throw new StatusException('Operation not allowed. This board is archived.');
561
		}
562
		$card = $this->cardMapper->find($cardId);
563
		if ($card->getArchived()) {
564
			throw new StatusException('Operation not allowed. This card is archived.');
565
		}
566
		$label = $this->labelMapper->find($labelId);
567
		$this->cardMapper->removeLabel($cardId, $labelId);
568
		$this->changeHelper->cardChanged($cardId, false);
569
		$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_LABEL_UNASSING, ['label' => $label]);
570
571
		$this->eventDispatcher->dispatch(
572
			'\OCA\Deck\Card::onUpdate', new GenericEvent(null, ['id' => $cardId, 'card' => $card])
573
		);
574
	}
575
576
	/**
577
	 * @param $cardId
578
	 * @param $userId
579
	 * @return bool|null|\OCP\AppFramework\Db\Entity
580
	 * @throws BadRequestException
581
	 * @throws \OCA\Deck\NoPermissionException
582
	 * @throws \OCP\AppFramework\Db\DoesNotExistException
583
	 * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
584
	 */
585
	public function assignUser($cardId, $userId) {
586
587
		if (is_numeric($cardId) === false) {
588
			throw new BadRequestException('card id must be a number');
589
		}
590
591
		if ($userId === false || $userId === null) {
592
			throw new BadRequestException('user id must be provided');
593
		}
594
595
		$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_EDIT);
596
		$assignments = $this->assignedUsersMapper->find($cardId);
597
		foreach ($assignments as $assignment) {
0 ignored issues
show
Bug introduced by
The expression $assignments of type array|object<OCP\AppFramework\Db\Entity> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
598
			if ($assignment->getParticipant() === $userId) {
599
				throw new BadRequestException('The user is already assigned to the card');
600
			}
601
		}
602
603
		$card = $this->cardMapper->find($cardId);
604
		$boardId = $this->cardMapper->findBoardId($cardId);
605
		$boardUsers = array_keys($this->permissionService->findUsers($boardId, true));
606
		if (!in_array($userId, $boardUsers)) {
607
			throw new BadRequestException('The user is not part of the board');
608
		}
609
610
611
		if ($userId !== $this->currentUser) {
612
			/* Notifyuser about the card assignment */
613
			$this->notificationHelper->sendCardAssigned($card, $userId);
614
		}
615
616
		$assignment = new AssignedUsers();
617
		$assignment->setCardId($cardId);
618
		$assignment->setParticipant($userId);
619
		$assignment = $this->assignedUsersMapper->insert($assignment);
620
		$this->changeHelper->cardChanged($cardId, false);
621
		$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_CARD_USER_ASSIGN, ['assigneduser' => $userId]);
622
623
		$this->eventDispatcher->dispatch(
624
			'\OCA\Deck\Card::onUpdate', new GenericEvent(null, ['id' => $cardId, 'card' => $card])
625
		);
626
627
		return $assignment;
628
	}
629
630
	/**
631
	 * @param $cardId
632
	 * @param $userId
633
	 * @return \OCP\AppFramework\Db\Entity
634
	 * @throws BadRequestException
635
	 * @throws NotFoundException
636
	 * @throws \OCA\Deck\NoPermissionException
637
	 * @throws \OCP\AppFramework\Db\DoesNotExistException
638
	 * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
639
	 */
640
	public function unassignUser($cardId, $userId) {
641
		$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_EDIT);
642
643
		if (is_numeric($cardId) === false) {
644
			throw new BadRequestException('card id must be a number');
645
		}
646
647
		if ($userId === false || $userId === null) {
648
			throw new BadRequestException('user must be provided');
649
		}
650
651
		$assignments = $this->assignedUsersMapper->find($cardId);
652
		foreach ($assignments as $assignment) {
0 ignored issues
show
Bug introduced by
The expression $assignments of type array|object<OCP\AppFramework\Db\Entity> is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
653
			if ($assignment->getParticipant() === $userId) {
654
				$assignment = $this->assignedUsersMapper->delete($assignment);
0 ignored issues
show
Deprecated Code introduced by
The method OCP\AppFramework\Db\Mapper::delete() has been deprecated with message: 14.0.0 Move over to QBMapper

This method has been deprecated. The supplier of the class has supplied an explanatory message.

The explanatory message should give you some clue as to whether and when the method will be removed from the class and what other method or class to use instead.

Loading history...
655
				$card = $this->cardMapper->find($cardId);
656
				$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_CARD_USER_UNASSIGN, ['assigneduser' => $userId]);
657
				$this->changeHelper->cardChanged($cardId, false);
658
659
				$this->eventDispatcher->dispatch(
660
					'\OCA\Deck\Card::onUpdate', new GenericEvent(null, ['id' => $cardId, 'card' => $card])
661
				);
662
663
				return $assignment;
664
			}
665
		}
666
		throw new NotFoundException('No assignment for ' . $userId . 'found.');
667
	}
668
}
669