diff --git a/appinfo/app.php b/appinfo/app.php index e7b8bf9b3..4f8c51333 100644 --- a/appinfo/app.php +++ b/appinfo/app.php @@ -21,6 +21,12 @@ * */ +if ((@include_once __DIR__ . '/../vendor/autoload.php')===false) { + throw new Exception('Cannot include autoload. Did you run install dependencies using composer?'); +} + $app = new \OCA\Deck\AppInfo\Application(); $app->registerNavigationEntry(); -$app->registerNotifications(); \ No newline at end of file +$app->registerNotifications(); + +\OC_Util::addStyle('deck', 'activity'); diff --git a/appinfo/autoload.php b/appinfo/autoload.php index 39ded13b0..4459d8394 100644 --- a/appinfo/autoload.php +++ b/appinfo/autoload.php @@ -5,20 +5,20 @@ * @author Julius Härtl * * @license GNU AGPL version 3 or any later version - * + * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. - * + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. - * + * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . - * + * */ namespace OCA\Deck\AppInfo; @@ -28,4 +28,4 @@ use OCP\AppFramework\App; /** * Additional autoloader registration, e.g. registering composer autoloaders */ -// require_once __DIR__ . '/../vendor/autoload.php'; \ No newline at end of file +require_once __DIR__ . '/../vendor/autoload.php'; diff --git a/composer.json b/composer.json index 73377ab9a..0e799ded1 100644 --- a/composer.json +++ b/composer.json @@ -8,8 +8,11 @@ "email": "jus@bitgrid.net" } ], - "require": {}, + "require": { + "cogpowered/finediff": "0.3.*" + }, "require-dev": { + "roave/security-advisories": "dev-master", "christophwurst/nextcloud": "^13.0", "jakub-onderka/php-parallel-lint": "^1.0.0" } diff --git a/css/activity.css b/css/activity.css new file mode 100644 index 000000000..e69de29bb diff --git a/lib/Activity/ActivityManager.php b/lib/Activity/ActivityManager.php index 2930dc588..c4bac4f58 100644 --- a/lib/Activity/ActivityManager.php +++ b/lib/Activity/ActivityManager.php @@ -30,7 +30,6 @@ use OCA\Deck\Db\Board; use OCA\Deck\Db\BoardMapper; use OCA\Deck\Db\Card; use OCA\Deck\Db\CardMapper; -use OCA\Deck\Db\IPermissionMapper; use OCA\Deck\Db\Label; use OCA\Deck\Db\Stack; use OCA\Deck\Db\StackMapper; @@ -78,12 +77,19 @@ class ActivityManager { const SUBJECT_CARD_UPDATE = 'card_update'; const SUBJECT_CARD_UPDATE_TITLE = 'card_update_title'; const SUBJECT_CARD_UPDATE_DESCRIPTION = 'card_update_description'; + const SUBJECT_CARD_UPDATE_DUEDATE = 'card_update_duedate'; const SUBJECT_CARD_UPDATE_ARCHIVE = 'card_update_archive'; const SUBJECT_CARD_UPDATE_UNARCHIVE = 'card_update_unarchive'; + const SUBJECT_CARD_UPDATE_STACKID = 'card_update_stackId'; + const SUBJECT_CARD_USER_ASSIGN = 'card_user_assign'; + const SUBJECT_CARD_USER_UNASSIGN = 'card_user_unassign'; + const SUBJECT_CARD_MOVE_STACK = 'card_move_stack'; + const SUBJECT_ATTACHMENT_CREATE = 'attachment_create'; const SUBJECT_ATTACHMENT_UPDATE = 'attachment_update'; const SUBJECT_ATTACHMENT_DELETE = 'attachment_delete'; + const SUBJECT_ATTACHMENT_RESTORE = 'attachment_restore'; const SUBJECT_LABEL_CREATE = 'label_create'; const SUBJECT_LABEL_UPDATE = 'label_update'; @@ -147,11 +153,11 @@ class ActivityManager { /** * @param $subjectIdentifier + * @param array $subjectParams * @param bool $ownActivity * @return string - * @throws \Exception */ - public function getActivityFormat($subjectIdentifier, $ownActivity = false) { + public function getActivityFormat($subjectIdentifier, $subjectParams = [], $ownActivity = false) { $subject = ''; switch ($subjectIdentifier) { case self::SUBJECT_BOARD_CREATE: @@ -198,8 +204,15 @@ class ActivityManager { case self::SUBJECT_CARD_DELETE: $subject = $ownActivity ? $this->l10n->t('You have deleted {card} in {stack} on {board}') : $this->l10n->t('{user} has deleted {card} in {stack} on {board}'); break; + case self::SUBJECT_CARD_UPDATE_TITLE: + $subject = $ownActivity ? $this->l10n->t('You have renamed the card {before} to {card}') : $this->l10n->t('{user} has renamed the card {before} to {card}'); + break; case self::SUBJECT_CARD_UPDATE_DESCRIPTION: - $subject = $ownActivity ? $this->l10n->t('You have updated the description of {card} in {stack} on {board}') : $this->l10n->t('{user} has updated the description {card} in {stack} on {board}'); + if ($subjectParams['before'] === null) { + $subject = $ownActivity ? $this->l10n->t('You have added a description to {card} in {stack} on {board}') : $this->l10n->t('{user} has added a description to {card} in {stack} on {board}'); + } else { + $subject = $ownActivity ? $this->l10n->t('You have updated the description of {card} in {stack} on {board}') : $this->l10n->t('{user} has updated the description {card} in {stack} on {board}'); + } break; case self::SUBJECT_CARD_UPDATE_ARCHIVE: $subject = $ownActivity ? $this->l10n->t('You have archived {card} in {stack} on {board}') : $this->l10n->t('{user} has archived {card} in {stack} on {board}'); @@ -207,15 +220,52 @@ class ActivityManager { case self::SUBJECT_CARD_UPDATE_UNARCHIVE: $subject = $ownActivity ? $this->l10n->t('You have unarchived {card} in {stack} on {board}') : $this->l10n->t('{user} has unarchived {card} in {stack} on {board}'); break; + case self::SUBJECT_CARD_UPDATE_DUEDATE: + if ($subjectParams['after'] === null) { + $subject = $ownActivity ? $this->l10n->t('You have removed the due date of {card}') : $this->l10n->t('{user} has removed the due date of {card}'); + } else if ($subjectParams['before'] === null && $subjectParams['after'] !== null) { + $subject = $ownActivity ? $this->l10n->t('You have set the due date of {card} to {after}') : $this->l10n->t('{user} has set the due date of {card} to {after}'); + } else { + $subject = $ownActivity ? $this->l10n->t('You have updated the due date of {card} to {after}') : $this->l10n->t('{user} has updated the due date of {card} to {after}'); + } + + break; + case self::SUBJECT_LABEL_ASSIGN: + $subject = $ownActivity ? $this->l10n->t('You have added the label {label} to {card} in {stack} on {board}') : $this->l10n->t('{user} has added the label {label} to {card} in {stack} on {board}'); + break; + case self::SUBJECT_LABEL_UNASSING: + $subject = $ownActivity ? $this->l10n->t('You have removed the label {label} from {card} in {stack} on {board}') : $this->l10n->t('{user} has removed the label {label} from {card} in {stack} on {board}'); + break; + case self::SUBJECT_CARD_USER_ASSIGN: + $subject = $ownActivity ? $this->l10n->t('You have assigned {assigneduser} to {card} on {board}') : $this->l10n->t('{user} has assigned {assigneduser} to {card} on {board}'); + break; + case self::SUBJECT_CARD_USER_UNASSIGN: + $subject = $ownActivity ? $this->l10n->t('You have unassigned {assigneduser} from {card} on {board}') : $this->l10n->t('{user} has unassigned {assigneduser} from {card} on {board}'); + break; + case self::SUBJECT_CARD_UPDATE_STACKID: + $subject = $ownActivity ? $this->l10n->t('You have moved the card {card} from {before} to {stack}') : $this->l10n->t('{user} has moved the card {card} from {before} to {stack}'); + break; + case self::SUBJECT_ATTACHMENT_CREATE: + $subject = $ownActivity ? $this->l10n->t('You have added the attachment {attachment} to {card}') : $this->l10n->t('{user} has added the attachment {attachment} to {card}'); + break; + case self::SUBJECT_ATTACHMENT_UPDATE: + $subject = $ownActivity ? $this->l10n->t('You have updated the attachment {attachment} on {card}') : $this->l10n->t('{user} has updated the attachment {attachment} to {card}'); + break; + case self::SUBJECT_ATTACHMENT_DELETE: + $subject = $ownActivity ? $this->l10n->t('You have deleted the attachment {attachment} from {card}') : $this->l10n->t('{user} has deleted the attachment {attachment} to {card}'); + break; + case self::SUBJECT_ATTACHMENT_RESTORE: + $subject = $ownActivity ? $this->l10n->t('You have restored the attachment {attachment} to {card}') : $this->l10n->t('{user} has restored the attachment {attachment} to {card}'); + break; default: break; } return $subject; } - public function triggerEvent($objectType, $entity, $subject) { + public function triggerEvent($objectType, $entity, $subject, $additionalParams = []) { try { - $event = $this->createEvent($objectType, $entity, $subject); + $event = $this->createEvent($objectType, $entity, $subject, $additionalParams); $this->sendToUsers($event); } catch (\Exception $e) { // Ignore exception for undefined activities on update events @@ -252,7 +302,11 @@ class ActivityManager { } } else { - $events = [$this->createEvent($objectType, $entity, $subject)]; + try { + $events = [$this->createEvent($objectType, $entity, $subject)]; + } catch (\Exception $e) { + // Ignore exception for undefined activities on update events + } } foreach ($events as $event) { $this->sendToUsers($event); @@ -305,16 +359,32 @@ class ActivityManager { case self::SUBJECT_CARD_UPDATE_UNARCHIVE: case self::SUBJECT_CARD_UPDATE_TITLE: case self::SUBJECT_CARD_UPDATE_DESCRIPTION: + case self::SUBJECT_CARD_UPDATE_DUEDATE: + case self::SUBJECT_CARD_UPDATE_STACKID: + case self::SUBJECT_LABEL_ASSIGN: + case self::SUBJECT_LABEL_UNASSING: + case self::SUBJECT_CARD_USER_ASSIGN: + case self::SUBJECT_CARD_USER_UNASSIGN: + case self::SUBJECT_CARD_MOVE_STACK: $subjectParams = $this->findDetailsForCard($entity->getId()); $object = $entity; - $message = $additionalParams['after']; break; - + case self::SUBJECT_ATTACHMENT_CREATE: + case self::SUBJECT_ATTACHMENT_UPDATE: + case self::SUBJECT_ATTACHMENT_DELETE: + case self::SUBJECT_ATTACHMENT_RESTORE: + $subjectParams = $this->findDetailsForAttachment($entity->getId()); + $object = $subjectParams['card']; + break; default: throw new \Exception('Unknown subject for activity.'); break; } + if ($subject === self::SUBJECT_CARD_UPDATE_DESCRIPTION){ + $message = $additionalParams['after']; + } + $event = $this->manager->generateEvent(); $event->setApp('deck') ->setType('deck') @@ -334,7 +404,6 @@ class ActivityManager { * Publish activity to all users that are part of the board of a given object * * @param IEvent $event - * @param IPermissionMapper $mapper */ public function sendToUsers(IEvent $event) { switch ($event->getObjectType()) { @@ -376,17 +445,8 @@ class ActivityManager { public function findDetailsForAttachment($attachmentId) { $attachment = $this->attachmentMapper->find($attachmentId); - $data = $this->findDetailsForCard($attachmentId->getCardId()); - return array_merge($data, [$attachment]); - } - - public function findDetailsForLabel($labelId) { - // LabelMapper - } - - public function findDetailsForLabelAssignment($labelId, $cardId) { - $card = $this->cardMapper->find($cardId); - + $data = $this->findDetailsForCard($attachment->getCardId()); + return array_merge($data, ['attachment' => $attachment]); } } diff --git a/lib/Activity/ChangeSet.php b/lib/Activity/ChangeSet.php index 3d483733b..237b48670 100644 --- a/lib/Activity/ChangeSet.php +++ b/lib/Activity/ChangeSet.php @@ -28,6 +28,7 @@ class ChangeSet { private $before; private $after; + private $diff = false; public function __construct($before = null, $after = null) { if ($before !== null) { @@ -38,6 +39,14 @@ class ChangeSet { } } + public function enableDiff() { + $this->diff = true; + } + + public function getDiff() { + return $this->diff; + } + public function setBefore($before) { $this->before = clone $before; } diff --git a/lib/Activity/DeckProvider.php b/lib/Activity/DeckProvider.php index 081919022..2429e0007 100644 --- a/lib/Activity/DeckProvider.php +++ b/lib/Activity/DeckProvider.php @@ -24,9 +24,9 @@ namespace OCA\Deck\Activity; +use cogpowered\FineDiff\Diff; use OCP\Activity\IEvent; use OCP\Activity\IProvider; -use OCP\IL10N; use OCP\IURLGenerator; class DeckProvider implements IProvider { @@ -61,12 +61,32 @@ class DeckProvider implements IProvider { } $event->setIcon(\OC::$server->getURLGenerator()->imagePath('deck', 'deck-dark.svg')); + if (strpos($event->getSubject(), '_update') !== false) { + $event->setIcon(\OC::$server->getURLGenerator()->imagePath('files', 'change.svg')); + } + if (strpos($event->getSubject(), '_create') !== false) { + $event->setIcon(\OC::$server->getURLGenerator()->imagePath('files', 'add-color.svg')); + } + if (strpos($event->getSubject(), '_delete') !== false) { + $event->setIcon(\OC::$server->getURLGenerator()->imagePath('files', 'delete-color.svg')); + } + if (strpos($event->getSubject(), 'archive') !== false) { + $event->setIcon(\OC::$server->getURLGenerator()->imagePath('deck', 'archive.svg')); + } + if (strpos($event->getSubject(), '_restore') !== false) { + $event->setIcon(\OC::$server->getURLGenerator()->imagePath('core', 'actions/history.svg')); + } + if (strpos($event->getSubject(), 'attachment_') !== false) { + $event->setIcon(\OC::$server->getURLGenerator()->imagePath('core', 'places/files.svg')); + } + $subjectIdentifier = $event->getSubject(); $subjectParams = $event->getSubjectParameters(); $ownActivity = ($event->getAuthor() === $this->userId); + $board = null; if ($event->getObjectType() === ActivityManager::DECK_OBJECT_BOARD) { $board = [ 'type' => 'highlight', @@ -76,6 +96,7 @@ class DeckProvider implements IProvider { ]; } + $card = null; if ($event->getObjectType() === ActivityManager::DECK_OBJECT_CARD) { $card = [ 'type' => 'highlight', @@ -84,8 +105,8 @@ class DeckProvider implements IProvider { ]; if ($subjectParams['board']) { - // TODO: check if archvied? - $card['link'] = $this->deckUrl('/board/' . $subjectParams['board']['id'] . '//card/' . $event->getObjectId()); + $archivedParam = $subjectParams['card']['archived'] ? 'archived' : ''; + $card['link'] = $this->deckUrl('/board/' . $subjectParams['board']['id'] . '/' . $archivedParam . '/card/' . $event->getObjectId()); } } @@ -120,6 +141,32 @@ class DeckProvider implements IProvider { ]; } + if (array_key_exists('label', $subjectParams)) { + $params['label'] = [ + 'type' => 'highlight', + 'id' => $subjectParams['label']['id'], + 'name' => $subjectParams['label']['title'] + ]; + } + + if (array_key_exists('attachment', $subjectParams)) { + $params['attachment'] = [ + 'type' => 'highlight', + 'id' => $subjectParams['attachment']['id'], + 'name' => $subjectParams['attachment']['data'], + 'link' => $this->urlGenerator->linkToRoute('deck.attachment.display', ['cardId' => $subjectParams['card']['id'], 'attachmentId' => $subjectParams['attachment']['id']]), + ]; + } + + if (array_key_exists('assigneduser', $subjectParams)) { + $user = $userManager->get($subjectParams['assigneduser']); + $params['assigneduser'] = [ + 'type' => 'user', + 'id' => $subjectParams['assigneduser'], + 'name' => $user !== null ? $user->getDisplayName() : $subjectParams['assigneduser'] + ]; + } + if (array_key_exists('before', $subjectParams)) { $params['before'] = [ 'type' => 'highlight', @@ -127,15 +174,28 @@ class DeckProvider implements IProvider { 'name' => $subjectParams['before'] ]; } + if (array_key_exists('after', $subjectParams)) { + $params['after'] = [ + 'type' => 'highlight', + 'id' => $subjectParams['after'], + 'name' => $subjectParams['after'] + ]; + } + + try { + $subject = $this->activityManager->getActivityFormat($subjectIdentifier, $subjectParams, $ownActivity); + } catch (\Exception $e) { + return $event; + } - $subject = $this->activityManager->getActivityFormat($subjectIdentifier, $ownActivity); $event->setParsedSubject($subject); $event->setRichSubject( $subject, $params ); if ($event->getMessage() !== '') { - $event->setParsedMessage('
' . $event->getMessage() . '
'); + $diff = new Diff(); + $event->setParsedMessage('
' . $diff->render($subjectParams['before'], $subjectParams['after']) . '
'); } return $event; diff --git a/lib/Activity/Filter.php b/lib/Activity/Filter.php index 4dcbd5260..038603236 100644 --- a/lib/Activity/Filter.php +++ b/lib/Activity/Filter.php @@ -86,4 +86,4 @@ class Filter implements \OCP\Activity\IFilter { public function allowedApps() { return ['deck']; } -} \ No newline at end of file +} diff --git a/lib/Service/AttachmentService.php b/lib/Service/AttachmentService.php index e3f8c4898..8c7a81307 100644 --- a/lib/Service/AttachmentService.php +++ b/lib/Service/AttachmentService.php @@ -24,7 +24,9 @@ namespace OCA\Deck\Service; +use OCA\Deck\Activity\ActivityManager; use OCA\Deck\AppInfo\Application; +use OCA\Deck\BadRequestException; use OCA\Deck\Db\Acl; use OCA\Deck\Db\Attachment; use OCA\Deck\Db\AttachmentMapper; @@ -52,6 +54,8 @@ class AttachmentService { private $cache; /** @var IL10N */ private $l10n; + /** @var ActivityManager */ + private $activityManager; /** * AttachmentService constructor. @@ -65,7 +69,7 @@ class AttachmentService { * @param IL10N $l10n * @throws \OCP\AppFramework\QueryException */ - public function __construct(AttachmentMapper $attachmentMapper, CardMapper $cardMapper, PermissionService $permissionService, Application $application, ICacheFactory $cacheFactory, $userId, IL10N $l10n) { + public function __construct(AttachmentMapper $attachmentMapper, CardMapper $cardMapper, PermissionService $permissionService, Application $application, ICacheFactory $cacheFactory, $userId, IL10N $l10n, ActivityManager $activityManager) { $this->attachmentMapper = $attachmentMapper; $this->cardMapper = $cardMapper; $this->permissionService = $permissionService; @@ -73,6 +77,7 @@ class AttachmentService { $this->application = $application; $this->cache = $cacheFactory->createDistributed('deck-card-attachments-'); $this->l10n = $l10n; + $this->activityManager = $activityManager; // Register shipped attachment services // TODO: move this to a plugin based approach once we have different types of attachments @@ -168,7 +173,7 @@ class AttachmentService { } if ($data === false || $data === null) { - throw new BadRequestException('data must be provided'); + //throw new BadRequestException('data must be provided'); } $this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_EDIT); @@ -200,6 +205,7 @@ class AttachmentService { } catch (InvalidAttachmentType $e) { // just store the data } + $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $attachment, ActivityManager::SUBJECT_ATTACHMENT_CREATE); return $attachment; } @@ -261,7 +267,7 @@ class AttachmentService { } if ($data === false || $data === null) { - throw new BadRequestException('data must be provided'); + //throw new BadRequestException('data must be provided'); } $this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_EDIT); @@ -284,6 +290,7 @@ class AttachmentService { } catch (InvalidAttachmentType $e) { // just store the data } + $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $attachment, ActivityManager::SUBJECT_ATTACHMENT_UPDATE); return $attachment; } @@ -317,13 +324,16 @@ class AttachmentService { $service = $this->getService($attachment->getType()); if ($service->allowUndo()) { $service->markAsDeleted($attachment); + $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $attachment, ActivityManager::SUBJECT_ATTACHMENT_DELETE); return $this->attachmentMapper->update($attachment); } $service->delete($attachment); } catch (InvalidAttachmentType $e) { // just delete without further action } - return $this->attachmentMapper->delete($attachment); + $attachment = $this->attachmentMapper->delete($attachment); + $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $attachment, ActivityManager::SUBJECT_ATTACHMENT_DELETE); + return $attachment; } public function restore($cardId, $attachmentId) { @@ -344,10 +354,12 @@ class AttachmentService { $service = $this->getService($attachment->getType()); if ($service->allowUndo()) { $attachment->setDeletedAt(0); - return $this->attachmentMapper->update($attachment); + $attachment = $this->attachmentMapper->update($attachment); + $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $attachment, ActivityManager::SUBJECT_ATTACHMENT_RESTORE); + return $attachment; } } catch (InvalidAttachmentType $e) { } throw new NoPermissionException('Restore is not allowed.'); } -} \ No newline at end of file +} diff --git a/lib/Service/BoardService.php b/lib/Service/BoardService.php index c5061046a..cea978ded 100644 --- a/lib/Service/BoardService.php +++ b/lib/Service/BoardService.php @@ -382,7 +382,7 @@ class BoardService { $changes->setAfter($board); $this->boardMapper->update($board); // operate on clone so we can check for updated fields $this->boardMapper->mapOwner($newBoard); - $this->activityManager->triggerUpdateEvents(ActivityManager::DECK_OBJECT_BOARD, $changes->getAfter(), ActivityManager::SUBJECT_BOARD_UPDATE, $changes->getBefore()); + $this->activityManager->triggerUpdateEvents(ActivityManager::DECK_OBJECT_BOARD, $changes, ActivityManager::SUBJECT_BOARD_UPDATE); return $board; } diff --git a/lib/Service/CardService.php b/lib/Service/CardService.php index 59e564e9c..45194434d 100644 --- a/lib/Service/CardService.php +++ b/lib/Service/CardService.php @@ -420,7 +420,9 @@ class CardService { if ($card->getArchived()) { throw new StatusException('Operation not allowed. This card is archived.'); } + $label = $this->labelMapper->find($labelId); $this->cardMapper->assignLabel($cardId, $labelId); + $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_LABEL_ASSIGN, ['label' => $label]); } /** @@ -450,7 +452,9 @@ class CardService { if ($card->getArchived()) { throw new StatusException('Operation not allowed. This card is archived.'); } + $label = $this->labelMapper->find($labelId); $this->cardMapper->removeLabel($cardId, $labelId); + $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_LABEL_UNASSING, ['label' => $label]); } /** @@ -478,17 +482,19 @@ class CardService { return false; } } + $card = $this->cardMapper->find($cardId); if ($userId !== $this->currentUser) { /* Notifyuser about the card assignment */ - $card = $this->cardMapper->find($cardId); $this->notificationHelper->sendCardAssigned($card, $userId); } $assignment = new AssignedUsers(); $assignment->setCardId($cardId); $assignment->setParticipant($userId); - return $this->assignedUsersMapper->insert($assignment); + $assignment = $this->assignedUsersMapper->insert($assignment); + $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_CARD_USER_ASSIGN, ['assigneduser' => $userId]); + return $assignment; } /** @@ -514,7 +520,10 @@ class CardService { $assignments = $this->assignedUsersMapper->find($cardId); foreach ($assignments as $assignment) { if ($assignment->getParticipant() === $userId) { - return $this->assignedUsersMapper->delete($assignment); + $assignment = $this->assignedUsersMapper->delete($assignment); + $card = $this->cardMapper->find($cardId); + $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_CARD_USER_UNASSIGN, ['assigneduser' => $userId]); + return $assignment; } } throw new NotFoundException('No assignment for ' . $userId . 'found.');