diff --git a/appinfo/info.xml b/appinfo/info.xml index 0dabafc6..7e168a08 100644 --- a/appinfo/info.xml +++ b/appinfo/info.xml @@ -32,4 +32,17 @@ OCA\Cospend\Settings\Admin + + + OCA\Cospend\Activity\Setting + OCA\Cospend\Activity\SettingComment + OCA\Cospend\Activity\DescriptionSetting + + + OCA\Cospend\Activity\Filter + + + OCA\Cospend\Activity\CospendProvider + + diff --git a/css/activity.css b/css/activity.css new file mode 100644 index 00000000..a612ba12 --- /dev/null +++ b/css/activity.css @@ -0,0 +1,28 @@ +.activitymessage .visualdiff ins { + background-color: rgba(70, 186, 97, 0.2); + text-decoration: none; +} + +.activitymessage .visualdiff del { + background-color: rgba(233, 50, 45, 0.2); + text-decoration: none; +} + +.activitymessage .visualdiff { + overflow: scroll; + max-height: 200px; +} + +.activityTabView .avatardiv-container { + display: inline-block; + bottom: -3px; + margin-left: 3px; +} + +.activityTabView .avatar-name-wrapper { + font-weight: bold; +} + +.activityTabView .activitysubject a { + font-weight: bold; +} diff --git a/lib/Activity/ActivityManager.php b/lib/Activity/ActivityManager.php new file mode 100644 index 00000000..571b8b4b --- /dev/null +++ b/lib/Activity/ActivityManager.php @@ -0,0 +1,379 @@ + + * + * @author Julien Veyssier + * + * @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\Activity; + +use InvalidArgumentException; +use OCA\Cospend\Service\BillService; +use OCP\Activity\IEvent; +use OCP\Activity\IManager; +use OCP\AppFramework\Db\DoesNotExistException; +use OCP\IL10N; +use OCP\IUser; + +class ActivityManager { + + private $manager; + private $userId; + private $billService; + private $l10n; + + const SUBJECT_BILL_CREATE = 'bill_create'; + const SUBJECT_BILL_UPDATE = 'bill_update'; + const SUBJECT_BILL_DELETE = 'bill_delete'; + + const SUBJECT_PROJECT_SHARE = 'board_share'; + const SUBJECT_PROJECT_UNSHARE = 'board_unshare'; + + public function __construct( + IManager $manager, + PermissionService $permissionsService, + BoardMapper $boardMapper, + CardMapper $cardMapper, + StackMapper $stackMapper, + AttachmentMapper $attachmentMapper, + AclMapper $aclMapper, + IL10N $l10n, + $userId + ) { + $this->manager = $manager; + $this->permissionService = $permissionsService; + $this->boardMapper = $boardMapper; + $this->cardMapper = $cardMapper; + $this->stackMapper = $stackMapper; + $this->attachmentMapper = $attachmentMapper; + $this->aclMapper = $aclMapper; + $this->l10n = $l10n; + $this->userId = $userId; + } + + /** + * @param $subjectIdentifier + * @param array $subjectParams + * @param bool $ownActivity + * @return string + */ + public function getActivityFormat($subjectIdentifier, $subjectParams = [], $ownActivity = false) { + $subject = ''; + switch ($subjectIdentifier) { + case self::SUBJECT_BILL_CREATE: + $subject = $ownActivity ? $this->l10n->t('You have created a new bill {bill} in project {project}'): $this->l10n->t('{user} has created a new bill {bill} in project {project}'); + break; + case self::SUBJECT_BILL_DELETE: + $subject = $ownActivity ? $this->l10n->t('You have deleted the bill {bill} of project {project}') : $this->l10n->t('{user} has deleted the bill {bill} of project {project}'); + break; + case self::SUBJECT_PROJECT_SHARE: + $subject = $ownActivity ? $this->l10n->t('You have shared the project {project} with {who}') : $this->l10n->t('{user} has shared the project {project} with {who}'); + break; + case self::SUBJECT_PROJECT_UNSHARE: + $subject = $ownActivity ? $this->l10n->t('You have removed {who} from the project {project}') : $this->l10n->t('{user} has removed {who} from the project {project}'); + break; + case self::SUBJECT_BILL_UPDATE: + $subject = $ownActivity ? $this->l10n->t('You have updated the bill {bill} of project {project}') : $this->l10n->t('{user} has updated the bill {bill} of project {project}'); + break; + default: + break; + } + return $subject; + } + + public function triggerEvent($objectType, $entity, $subject, $additionalParams = [], $author = null) { + try { + $event = $this->createEvent($objectType, $entity, $subject, $additionalParams, $author); + if ($event !== null) { + $this->sendToUsers($event); + } + } catch (\Exception $e) { + // Ignore exception for undefined activities on update events + } + } + + /** + * + * @param $objectType + * @param ChangeSet $changeSet + * @param $subject + * @throws \Exception + */ + public function triggerUpdateEvents($objectType, ChangeSet $changeSet, $subject) { + $previousEntity = $changeSet->getBefore(); + $entity = $changeSet->getAfter(); + $events = []; + if ($previousEntity !== null) { + foreach ($entity->getUpdatedFields() as $field => $value) { + $getter = 'get' . ucfirst($field); + $subjectComplete = $subject . '_' . $field; + $changes = [ + 'before' => $previousEntity->$getter(), + 'after' => $entity->$getter() + ]; + if ($changes['before'] !== $changes['after']) { + try { + $event = $this->createEvent($objectType, $entity, $subjectComplete, $changes); + if ($event !== null) { + $events[] = $event; + } + } catch (\Exception $e) { + // Ignore exception for undefined activities on update events + } + } + } + } else { + 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); + } + } + + /** + * @param $objectType + * @param $entity + * @param $subject + * @param array $additionalParams + * @return IEvent|null + * @throws \Exception + */ + private function createEvent($objectType, $entity, $subject, $additionalParams = [], $author = null) { + try { + $object = $this->findObjectForEntity($objectType, $entity); + } catch (DoesNotExistException $e) { + \OC::$server->getLogger()->error('Could not create activity entry for ' . $subject . '. Entity not found.', (array)$entity); + return null; + } catch (MultipleObjectsReturnedException $e) { + \OC::$server->getLogger()->error('Could not create activity entry for ' . $subject . '. Entity not found.', (array)$entity); + return null; + } + + /** + * Automatically fetch related details for subject parameters + * depending on the subject + */ + $eventType = 'deck'; + $subjectParams = []; + $message = null; + switch ($subject) { + // No need to enhance parameters since entity already contains the required data + case self::SUBJECT_BOARD_CREATE: + case self::SUBJECT_BOARD_UPDATE_TITLE: + case self::SUBJECT_BOARD_UPDATE_ARCHIVED: + case self::SUBJECT_BOARD_DELETE: + case self::SUBJECT_BOARD_RESTORE: + // Not defined as there is no activity for + // case self::SUBJECT_BOARD_UPDATE_COLOR + break; + case self::SUBJECT_CARD_COMMENT_CREATE: + $eventType = 'deck_comment'; + $subjectParams = $this->findDetailsForCard($entity->getId()); + if (array_key_exists('comment', $additionalParams)) { + /** @var IComment $entity */ + $comment = $additionalParams['comment']; + $subjectParams['comment'] = $comment->getId(); + unset($additionalParams['comment']); + } + break; + case self::SUBJECT_STACK_CREATE: + case self::SUBJECT_STACK_UPDATE: + case self::SUBJECT_STACK_UPDATE_TITLE: + case self::SUBJECT_STACK_UPDATE_ORDER: + case self::SUBJECT_STACK_DELETE: + $subjectParams = $this->findDetailsForStack($entity->getId()); + break; + + case self::SUBJECT_CARD_CREATE: + case self::SUBJECT_CARD_DELETE: + case self::SUBJECT_CARD_UPDATE_ARCHIVE: + 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: + $subjectParams = $this->findDetailsForCard($entity->getId(), $subject); + 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()); + break; + case self::SUBJECT_BOARD_SHARE: + case self::SUBJECT_BOARD_UNSHARE: + $subjectParams = $this->findDetailsForAcl($entity->getId()); + break; + default: + throw new \Exception('Unknown subject for activity.'); + break; + } + + if ($subject === self::SUBJECT_CARD_UPDATE_DESCRIPTION){ + $card = $subjectParams['card']; + if ($card->getLastEditor() === $this->userId) { + return null; + } + $subjectParams['diff'] = true; + $eventType = 'deck_card_description'; + } + if ($subject === self::SUBJECT_CARD_UPDATE_STACKID) { + $subjectParams['stackBefore'] = $this->stackMapper->find($additionalParams['before']); + } + + $subjectParams['author'] = $this->userId; + + + $event = $this->manager->generateEvent(); + $event->setApp('deck') + ->setType($eventType) + ->setAuthor($author === null ? $this->userId : $author) + ->setObject($objectType, (int)$object->getId(), $object->getTitle()) + ->setSubject($subject, array_merge($subjectParams, $additionalParams)) + ->setTimestamp(time()); + + if ($message !== null) { + $event->setMessage($message); + } + return $event; + } + + /** + * Publish activity to all users that are part of the board of a given object + * + * @param IEvent $event + */ + private function sendToUsers(IEvent $event) { + switch ($event->getObjectType()) { + case self::DECK_OBJECT_BOARD: + $mapper = $this->boardMapper; + break; + case self::DECK_OBJECT_CARD: + $mapper = $this->cardMapper; + break; + } + $boardId = $mapper->findBoardId($event->getObjectId()); + /** @var IUser $user */ + foreach ($this->permissionService->findUsers($boardId) as $user) { + $event->setAffectedUser($user->getUID()); + /** @noinspection DisconnectedForeachInstructionInspection */ + $this->manager->publish($event); + } + } + + /** + * @param $objectType + * @param $entity + * @return null|\OCA\Deck\Db\RelationalEntity|\OCP\AppFramework\Db\Entity + * @throws \OCP\AppFramework\Db\DoesNotExistException + * @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException + */ + private function findObjectForEntity($objectType, $entity) { + $className = \get_class($entity); + if ($entity instanceof IComment) { + $className = IComment::class; + } + $objectId = null; + if ($objectType === self::DECK_OBJECT_CARD) { + switch ($className) { + case Card::class: + $objectId = $entity->getId(); + break; + case Attachment::class: + case Label::class: + case AssignedUsers::class: + $objectId = $entity->getCardId(); + break; + case IComment::class: + $objectId = $entity->getObjectId(); + break; + default: + throw new InvalidArgumentException('No entity relation present for '. $className . ' to ' . $objectType); + } + return $this->cardMapper->find($objectId); + } + if ($objectType === self::DECK_OBJECT_BOARD) { + switch ($className) { + case Board::class: + $objectId = $entity->getId(); + break; + case Label::class: + case Stack::class: + case Acl::class: + $objectId = $entity->getBoardId(); + break; + default: + throw new InvalidArgumentException('No entity relation present for '. $className . ' to ' . $objectType); + } + return $this->boardMapper->find($objectId); + } + throw new InvalidArgumentException('No entity relation present for '. $className . ' to ' . $objectType); + } + + private function findDetailsForStack($stackId) { + $stack = $this->stackMapper->find($stackId); + $board = $this->boardMapper->find($stack->getBoardId()); + return [ + 'stack' => $stack, + 'board' => $board + ]; + } + + private function findDetailsForCard($cardId, $subject = null) { + $card = $this->cardMapper->find($cardId); + $stack = $this->stackMapper->find($card->getStackId()); + $board = $this->boardMapper->find($stack->getBoardId()); + if ($subject !== self::SUBJECT_CARD_UPDATE_DESCRIPTION) { + $card = [ + 'id' => $card->getId(), + 'title' => $card->getTitle(), + 'archived' => $card->getArchived() + ]; + } + return [ + 'card' => $card, + 'stack' => $stack, + 'board' => $board + ]; + } + + private function findDetailsForAttachment($attachmentId) { + $attachment = $this->attachmentMapper->find($attachmentId); + $data = $this->findDetailsForCard($attachment->getCardId()); + return array_merge($data, ['attachment' => $attachment]); + } + + private function findDetailsForAcl($aclId) { + $acl = $this->aclMapper->find($aclId); + $board = $this->boardMapper->find($acl->getBoardId()); + return [ + 'acl' => $acl, + 'board' => $board + ]; + } + +} diff --git a/lib/Activity/ChangeSet.php b/lib/Activity/ChangeSet.php new file mode 100644 index 00000000..9040192a --- /dev/null +++ b/lib/Activity/ChangeSet.php @@ -0,0 +1,90 @@ + + * + * @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\Activity; + + +class ChangeSet implements \JsonSerializable { + + private $before; + private $after; + private $diff = false; + + public function __construct($before = null, $after = null) { + if ($before !== null) { + $this->setBefore($before); + } + if ($after !== null) { + $this->setAfter($after); + } + } + + public function enableDiff() { + $this->diff = true; + } + + public function getDiff() { + return $this->diff; + } + + public function setBefore($before) { + if (is_object($before)) { + $this->before = clone $before; + } else { + $this->before = $before; + } + } + + public function setAfter($after) { + if (is_object($after)) { + $this->after = clone $after; + } else { + $this->after = $after; + } + } + + public function getBefore() { + return $this->before; + } + + public function getAfter() { + return $this->after; + } + + /** + * Specify data which should be serialized to JSON + * + * @link http://php.net/manual/en/jsonserializable.jsonserialize.php + * @return mixed data which can be serialized by json_encode, + * which is a value of any type other than a resource. + * @since 5.4.0 + */ + public function jsonSerialize() { + return [ + 'before' => $this->getBefore(), + 'after' => $this->getAfter(), + 'diff' => $this->getDiff(), + 'type' => get_class($this->before) + ]; + } +} diff --git a/lib/Activity/CommentEventHandler.php b/lib/Activity/CommentEventHandler.php new file mode 100644 index 00000000..7a6981a7 --- /dev/null +++ b/lib/Activity/CommentEventHandler.php @@ -0,0 +1,91 @@ + + * + * @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\Activity; + +use OCA\Deck\Db\CardMapper; +use OCA\Deck\Notification\NotificationHelper; +use OCP\Comments\CommentsEvent; +use OCP\Comments\IComment; +use \OCP\Comments\ICommentsEventHandler; + +class CommentEventHandler implements ICommentsEventHandler { + + /** @var ActivityManager */ + private $activityManager; + + /** @var NotificationHelper */ + private $notificationHelper; + + /** @var CardMapper */ + private $cardMapper; + + public function __construct(ActivityManager $activityManager, NotificationHelper $notificationHelper, CardMapper $cardMapper) { + $this->notificationHelper = $notificationHelper; + $this->activityManager = $activityManager; + $this->cardMapper = $cardMapper; + } + + /** + * @param CommentsEvent $event + */ + public function handle(CommentsEvent $event) { + if($event->getComment()->getObjectType() !== 'deckCard') { + return; + } + + $eventType = $event->getEvent(); + if( $eventType === CommentsEvent::EVENT_ADD + ) { + $this->notificationHandler($event); + $this->activityHandler($event); + return; + } + + $applicableEvents = [ + CommentsEvent::EVENT_UPDATE + ]; + if(in_array($eventType, $applicableEvents)) { + $this->notificationHandler($event); + return; + } + } + + /** + * @param CommentsEvent $event + */ + private function activityHandler(CommentsEvent $event) { + /** @var IComment $comment */ + $comment = $event->getComment(); + $card = $this->cardMapper->find($comment->getObjectId()); + $this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $card, ActivityManager::SUBJECT_CARD_COMMENT_CREATE, ['comment' => $comment]); + + } + + /** + * @param CommentsEvent $event + */ + private function notificationHandler(CommentsEvent $event) { + $this->notificationHelper->sendMention($event->getComment()); + } +} diff --git a/lib/Activity/CospendProvider.php b/lib/Activity/CospendProvider.php new file mode 100644 index 00000000..1efefdd2 --- /dev/null +++ b/lib/Activity/CospendProvider.php @@ -0,0 +1,351 @@ + + * + * @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\Activity; + + +use cogpowered\FineDiff\Diff; +use OCA\Deck\Db\Acl; +use OCP\Activity\IEvent; +use OCP\Activity\IProvider; +use OCP\Comments\IComment; +use OCP\Comments\ICommentsManager; +use OCP\Comments\NotFoundException; +use OCP\IConfig; +use OCP\IURLGenerator; +use OCP\IUserManager; +use OCP\L10N\IFactory; + +class DeckProvider implements IProvider { + + /** @var string */ + private $userId; + /** @var IURLGenerator */ + private $urlGenerator; + /** @var ActivityManager */ + private $activityManager; + /** @var IUserManager */ + private $userManager; + /** @var ICommentsManager */ + private $commentsManager; + /** @var IFactory */ + private $l10nFactory; + /** @var IConfig */ + private $config; + + public function __construct(IURLGenerator $urlGenerator, ActivityManager $activityManager, IUserManager $userManager, ICommentsManager $commentsManager, IFactory $l10n, IConfig $config, $userId) { + $this->userId = $userId; + $this->urlGenerator = $urlGenerator; + $this->activityManager = $activityManager; + $this->commentsManager = $commentsManager; + $this->userManager = $userManager; + $this->l10nFactory = $l10n; + $this->config = $config; + } + + /** + * @param string $language The language which should be used for translating, e.g. "en" + * @param IEvent $event The current event which should be parsed + * @param IEvent|null $previousEvent A potential previous event which you can combine with the current one. + * To do so, simply use setChildEvent($previousEvent) after setting the + * combined subject on the current event. + * @return IEvent + * @throws \InvalidArgumentException Should be thrown if your provider does not know this event + * @since 11.0.0 + */ + public function parse($language, IEvent $event, IEvent $previousEvent = null) { + if ($event->getApp() !== 'deck') { + throw new \InvalidArgumentException(); + } + + $event = $this->getIcon($event); + + $subjectIdentifier = $event->getSubject(); + $subjectParams = $event->getSubjectParameters(); + $ownActivity = ($event->getAuthor() === $this->userId); + + /** + * Map stored parameter objects to rich string types + */ + + $author = $event->getAuthor(); + // get author if + if (($author === '' || $author === ActivityManager::DECK_NOAUTHOR_COMMENT_SYSTEM_ENFORCED) && array_key_exists('author', $subjectParams)) { + $author = $subjectParams['author']; + unset($subjectParams['author']); + } + $user = $this->userManager->get($author); + if ($user !== null) { + $params = [ + 'user' => [ + 'type' => 'user', + 'id' => $author, + 'name' => $user !== null ? $user->getDisplayName() : $author + ], + ]; + $event->setAuthor($author); + } + if ($event->getObjectType() === ActivityManager::DECK_OBJECT_BOARD) { + if (isset($subjectParams['board']) && $event->getObjectName() === '') { + $event->setObject($event->getObjectType(), $event->getObjectId(), $subjectParams['board']['title']); + } + $board = [ + 'type' => 'highlight', + 'id' => $event->getObjectId(), + 'name' => $event->getObjectName(), + 'link' => $this->deckUrl('/board/' . $event->getObjectId()), + ]; + $params['board'] = $board; + } + + if (isset($subjectParams['card']) && $event->getObjectType() === ActivityManager::DECK_OBJECT_CARD) { + if ($event->getObjectName() === '') { + $event->setObject($event->getObjectType(), $event->getObjectId(), $subjectParams['card']['title']); + } + $card = [ + 'type' => 'highlight', + 'id' => $event->getObjectId(), + 'name' => $event->getObjectName(), + ]; + + if (array_key_exists('board', $subjectParams)) { + $archivedParam = $subjectParams['card']['archived'] ? 'archived' : ''; + $card['link'] = $this->deckUrl('/board/' . $subjectParams['board']['id'] . '/' . $archivedParam . '/card/' . $event->getObjectId()); + } + $params['card'] = $card; + } + + $params = $this->parseParamForBoard('board', $subjectParams, $params); + $params = $this->parseParamForStack('stack', $subjectParams, $params); + $params = $this->parseParamForStack('stackBefore', $subjectParams, $params); + $params = $this->parseParamForAttachment('attachment', $subjectParams, $params); + $params = $this->parseParamForLabel($subjectParams, $params); + $params = $this->parseParamForAssignedUser($subjectParams, $params); + $params = $this->parseParamForAcl($subjectParams, $params); + $params = $this->parseParamForChanges($subjectParams, $params, $event); + $params = $this->parseParamForComment($subjectParams, $params, $event); + $params = $this->parseParamForDuedate($subjectParams, $params, $event); + + try { + $subject = $this->activityManager->getActivityFormat($subjectIdentifier, $subjectParams, $ownActivity); + $this->setSubjects($event, $subject, $params); + } catch (\Exception $e) { + } + return $event; + } + + /** + * @param IEvent $event + * @param string $subject + * @param array $parameters + */ + protected function setSubjects(IEvent $event, $subject, array $parameters) { + $placeholders = $replacements = $richParameters = []; + foreach ($parameters as $placeholder => $parameter) { + $placeholders[] = '{' . $placeholder . '}'; + if (is_array($parameter) && array_key_exists('name', $parameter)) { + $replacements[] = $parameter['name']; + $richParameters[$placeholder] = $parameter; + } else { + $replacements[] = ''; + } + } + + $event->setParsedSubject(str_replace($placeholders, $replacements, $subject)) + ->setRichSubject($subject, $richParameters); + $event->setSubject($subject, $parameters); + } + + private function getIcon(IEvent $event) { + $event->setIcon($this->urlGenerator->imagePath('deck', 'deck-dark.svg')); + if (strpos($event->getSubject(), '_update') !== false) { + $event->setIcon($this->urlGenerator->imagePath('files', 'change.svg')); + } + if (strpos($event->getSubject(), '_create') !== false) { + $event->setIcon($this->urlGenerator->imagePath('files', 'add-color.svg')); + } + if (strpos($event->getSubject(), '_delete') !== false) { + $event->setIcon($this->urlGenerator->imagePath('files', 'delete-color.svg')); + } + if (strpos($event->getSubject(), 'archive') !== false) { + $event->setIcon($this->urlGenerator->imagePath('deck', 'archive.svg')); + } + if (strpos($event->getSubject(), '_restore') !== false) { + $event->setIcon($this->urlGenerator->imagePath('core', 'actions/history.svg')); + } + if (strpos($event->getSubject(), 'attachment_') !== false) { + $event->setIcon($this->urlGenerator->imagePath('core', 'places/files.svg')); + } + if (strpos($event->getSubject(), 'comment_') !== false) { + $event->setIcon($this->urlGenerator->imagePath('core', 'actions/comment.svg')); + } + return $event; + } + + private function parseParamForBoard($paramName, $subjectParams, $params) { + if (array_key_exists($paramName, $subjectParams)) { + $params[$paramName] = [ + 'type' => 'highlight', + 'id' => $subjectParams[$paramName]['id'], + 'name' => $subjectParams[$paramName]['title'], + 'link' => $this->deckUrl('/board/' . $subjectParams[$paramName]['id'] . '/'), + ]; + } + return $params; + } + private function parseParamForStack($paramName, $subjectParams, $params) { + if (array_key_exists($paramName, $subjectParams)) { + $params[$paramName] = [ + 'type' => 'highlight', + 'id' => $subjectParams[$paramName]['id'], + 'name' => $subjectParams[$paramName]['title'], + ]; + } + return $params; + } + + private function parseParamForAttachment($paramName, $subjectParams, $params) { + if (array_key_exists($paramName, $subjectParams)) { + $params[$paramName] = [ + 'type' => 'highlight', + 'id' => $subjectParams[$paramName]['id'], + 'name' => $subjectParams[$paramName]['data'], + 'link' => $this->urlGenerator->linkToRoute('deck.attachment.display', ['cardId' => $subjectParams['card']['id'], 'attachmentId' => $subjectParams['attachment']['id']]), + ]; + } + return $params; + } + + private function parseParamForAssignedUser($subjectParams, $params) { + if (array_key_exists('assigneduser', $subjectParams)) { + $user = $this->userManager->get($subjectParams['assigneduser']); + $params['assigneduser'] = [ + 'type' => 'user', + 'id' => $subjectParams['assigneduser'], + 'name' => $user !== null ? $user->getDisplayName() : $subjectParams['assigneduser'] + ]; + } + return $params; + } + + private function parseParamForLabel($subjectParams, $params) { + if (array_key_exists('label', $subjectParams)) { + $params['label'] = [ + 'type' => 'highlight', + 'id' => $subjectParams['label']['id'], + 'name' => $subjectParams['label']['title'] + ]; + } + return $params; + } + + private function parseParamForAcl($subjectParams, $params) { + if (array_key_exists('acl', $subjectParams)) { + if ($subjectParams['acl']['type'] === Acl::PERMISSION_TYPE_USER) { + $user = $this->userManager->get($subjectParams['acl']['participant']); + $params['acl'] = [ + 'type' => 'user', + 'id' => $subjectParams['acl']['participant'], + 'name' => $user !== null ? $user->getDisplayName() : $subjectParams['acl']['participant'] + ]; + } else { + $params['acl'] = [ + 'type' => 'highlight', + 'id' => $subjectParams['acl']['participant'], + 'name' => $subjectParams['acl']['participant'] + ]; + } + } + return $params; + } + + private function parseParamForComment($subjectParams, $params, IEvent $event) { + if (array_key_exists('comment', $subjectParams)) { + /** @var IComment $comment */ + try { + $comment = $this->commentsManager->get((int)$subjectParams['comment']); + $event->setParsedMessage($comment->getMessage()); + $params['comment'] =[ + 'type' => 'highlight', + 'id' => $subjectParams['comment'], + 'name' => $comment->getMessage() + ]; + } catch (NotFoundException $e) { + } + } + return $params; + } + + private function parseParamForDuedate($subjectParams, $params, IEvent $event) { + if (array_key_exists('after', $subjectParams) && $event->getSubject() === ActivityManager::SUBJECT_CARD_UPDATE_DUEDATE) { + $userLanguage = $this->config->getUserValue($event->getAuthor(), 'core', 'lang', $this->l10nFactory->findLanguage()); + $userLocale = $this->config->getUserValue($event->getAuthor(), 'core', 'locale', $this->l10nFactory->findLocale()); + $l10n = $this->l10nFactory->get('deck', $userLanguage, $userLocale); + $date = new \DateTime($subjectParams['after']); + $date->setTimezone(new \DateTimeZone(\date_default_timezone_get())); + $params['after'] = [ + 'type' => 'highlight', + 'id' => 'dt:' . $subjectParams['after'], + 'name' => $l10n->l('datetime', $date) + ]; + } + return $params; + } + + /** + * Add diff to message if the subject parameter 'diff' is set, otherwise + * the changed values are added to before/after + * + * @param $subjectParams + * @param $params + * @return mixed + */ + private function parseParamForChanges($subjectParams, $params, $event) { + if (array_key_exists('diff', $subjectParams) && $subjectParams['diff']) { + $diff = new Diff(); + // Don't add diff as message since we are limited to 255 chars here + //$event->setMessage($subjectParams['after']); + $event->setParsedMessage('
' . $diff->render($subjectParams['before'], $subjectParams['after']) . '
'); + return $params; + } + if (array_key_exists('before', $subjectParams)) { + $params['before'] = [ + 'type' => 'highlight', + 'id' => $subjectParams['before'], + 'name' => $subjectParams['before'] + ]; + } + if (array_key_exists('after', $subjectParams)) { + $params['after'] = [ + 'type' => 'highlight', + 'id' => $subjectParams['after'], + 'name' => $subjectParams['after'] + ]; + } + return $params; + } + + public function deckUrl($endpoint) { + return $this->urlGenerator->linkToRouteAbsolute('deck.page.index') . '#!' . $endpoint; + } +} diff --git a/lib/Activity/DescriptionSetting.php b/lib/Activity/DescriptionSetting.php new file mode 100644 index 00000000..787647ff --- /dev/null +++ b/lib/Activity/DescriptionSetting.php @@ -0,0 +1,45 @@ + + * + * @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\Activity; + + +class DescriptionSetting extends Setting { + + /** + * @return string Lowercase a-z and underscore only identifier + * @since 11.0.0 + */ + public function getIdentifier() { + return 'deck_card_description'; + } + + /** + * @return string A translated string + * @since 11.0.0 + */ + public function getName() { + return $this->l->t('A card description inside the Deck app has been changed'); + } + +} diff --git a/lib/Activity/Filter.php b/lib/Activity/Filter.php new file mode 100644 index 00000000..62b9a7c8 --- /dev/null +++ b/lib/Activity/Filter.php @@ -0,0 +1,92 @@ + + * + * @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\Activity; + +use OCP\IL10N; +use OCP\IURLGenerator; + +class Filter implements \OCP\Activity\IFilter { + + private $l10n; + private $urlGenerator; + + public function __construct( + IL10N $l10n, + IURLGenerator $urlGenerator + ) { + $this->l10n = $l10n; + $this->urlGenerator = $urlGenerator; + } + + /** + * @return string Lowercase a-z and underscore only identifier + * @since 11.0.0 + */ + public function getIdentifier() { + return 'deck'; + } + + /** + * @return string A translated string + * @since 11.0.0 + */ + public function getName() { + return $this->l10n->t('Deck'); + } + + /** + * @return int whether the filter should be rather on the top or bottom of + * the admin section. The filters are arranged in ascending order of the + * priority values. It is required to return a value between 0 and 100. + * @since 11.0.0 + */ + public function getPriority() { + return 90; + } + + /** + * @return string Full URL to an icon, empty string when none is given + * @since 11.0.0 + */ + public function getIcon() { + return $this->urlGenerator->imagePath('deck', 'deck-dark.svg'); + } + + /** + * @param string[] $types + * @return string[] An array of allowed apps from which activities should be displayed + * @since 11.0.0 + */ + public function filterTypes(array $types) { + return array_merge($types, ['deck_comment']); + } + + /** + * @return string[] An array of allowed apps from which activities should be displayed + * @since 11.0.0 + */ + public function allowedApps() { + return ['deck']; + } +} diff --git a/lib/Activity/Setting.php b/lib/Activity/Setting.php new file mode 100644 index 00000000..9da38530 --- /dev/null +++ b/lib/Activity/Setting.php @@ -0,0 +1,98 @@ + + * + * @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\Activity; + + +use OCP\IL10N; + +class Setting implements \OCP\Activity\ISetting { + + /** @var IL10N */ + protected $l; + + /** + * @param IL10N $l + */ + public function __construct(IL10N $l) { + $this->l = $l; + } + + /** + * @return string Lowercase a-z and underscore only identifier + * @since 11.0.0 + */ + public function getIdentifier() { + return 'deck'; + } + + /** + * @return string A translated string + * @since 11.0.0 + */ + public function getName() { + return $this->l->t('Changes in the Deck app'); + } + + /** + * @return int whether the filter should be rather on the top or bottom of + * the admin section. The filters are arranged in ascending order of the + * priority values. It is required to return a value between 0 and 100. + * @since 11.0.0 + */ + public function getPriority() { + return 90; + } + + /** + * @return bool True when the option can be changed for the stream + * @since 11.0.0 + */ + public function canChangeStream() { + return true; + } + + /** + * @return bool True when the option can be changed for the stream + * @since 11.0.0 + */ + public function isDefaultEnabledStream() { + return true; + } + + /** + * @return bool True when the option can be changed for the mail + * @since 11.0.0 + */ + public function canChangeMail() { + return true; + } + + /** + * @return bool True when the option can be changed for the stream + * @since 11.0.0 + */ + public function isDefaultEnabledMail() { + return false; + } +} diff --git a/lib/Activity/SettingComment.php b/lib/Activity/SettingComment.php new file mode 100644 index 00000000..fd8ad178 --- /dev/null +++ b/lib/Activity/SettingComment.php @@ -0,0 +1,53 @@ + + * + * @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\Activity; + + +class SettingComment extends Setting { + + /** + * @return string Lowercase a-z and underscore only identifier + * @since 11.0.0 + */ + public function getIdentifier() { + return 'deck_comment'; + } + + /** + * @return string A translated string + * @since 11.0.0 + */ + public function getName() { + return $this->l->t('A comment was created on a card'); + } + + /** + * @return bool True when the option can be changed for the stream + * @since 11.0.0 + */ + public function canChangeStream() { + return false; + } + +}