begin to implement activity stream

This commit is contained in:
Julien Veyssier
2019-10-24 21:26:13 +02:00
parent 7291603c73
commit 0baf800a7d
10 changed files with 1240 additions and 0 deletions

View File

@@ -32,4 +32,17 @@
<settings>
<admin>OCA\Cospend\Settings\Admin</admin>
</settings>
<activity>
<settings>
<setting>OCA\Cospend\Activity\Setting</setting>
<setting>OCA\Cospend\Activity\SettingComment</setting>
<setting>OCA\Cospend\Activity\DescriptionSetting</setting>
</settings>
<filters>
<filter>OCA\Cospend\Activity\Filter</filter>
</filters>
<providers>
<provider>OCA\Cospend\Activity\CospendProvider</provider>
</providers>
</activity>
</info>

28
css/activity.css Normal file
View File

@@ -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;
}

View File

@@ -0,0 +1,379 @@
<?php
/**
* @copyright Copyright (c) 2019 Julien Veyssier <eneiluj@posteo.net>
*
* @author Julien Veyssier <eneiluj@posteo.net>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
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
];
}
}

View File

@@ -0,0 +1,90 @@
<?php
/**
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
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 <b>json_encode</b>,
* 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)
];
}
}

View File

@@ -0,0 +1,91 @@
<?php
/**
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
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());
}
}

View File

@@ -0,0 +1,351 @@
<?php
/**
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
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('<pre class="visualdiff">' . $diff->render($subjectParams['before'], $subjectParams['after']) . '</pre>');
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;
}
}

View File

@@ -0,0 +1,45 @@
<?php
/**
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
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 <strong>card description</strong> inside the Deck app has been changed');
}
}

92
lib/Activity/Filter.php Normal file
View File

@@ -0,0 +1,92 @@
<?php
/**
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
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'];
}
}

98
lib/Activity/Setting.php Normal file
View File

@@ -0,0 +1,98 @@
<?php
/**
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
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 <strong>Deck app</strong>');
}
/**
* @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;
}
}

View File

@@ -0,0 +1,53 @@
<?php
/**
* @copyright Copyright (c) 2019 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
* @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 <http://www.gnu.org/licenses/>.
*
*/
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 <strong>comment</strong> 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;
}
}