Compare commits

...

23 Commits

Author SHA1 Message Date
Julius Härtl
1e06aef4e6 Bump version to 1.2.5
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-03-05 15:54:06 +01:00
Julius Härtl
4362fdf200 Merge pull request #2860 from nextcloud/backport/2823/stable1.2
[stable1.2] Properly pass the user to fetch circles when calling through occ
2021-03-05 15:49:24 +01:00
Julius Härtl
d14b4f5853 Properly pass the user to fetch circles when calling through occ
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-03-05 14:41:42 +00:00
Julius Härtl
f289ab2d63 Merge pull request #2845 from nextcloud/backport/2843/stable1.2 2021-03-05 15:39:46 +01:00
Julius Härtl
0ec265b1fb Use proper debounce on the sharing input
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-03-05 13:39:12 +01:00
Julius Härtl
3836363ed4 Search by mail on the board sharing input
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-03-05 13:39:12 +01:00
Julius Härtl
517b3cbaf3 Merge pull request #2850 from nextcloud/backport/2847/stable1.2
[stable1.2] Switch to Content-Disposition attachment and check for sane mimetypes
2021-03-05 13:38:14 +01:00
Julius Härtl
5fb0c401c8 Switch to Content-Disposition attachment and check for sane mimetypes
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-03-04 11:22:47 +01:00
Julius Härtl
0b06c0d180 Merge pull request #2851 from nextcloud/ci/github-actions-ubuntu-update
[stable1.2] Let CI pass again
2021-03-04 09:34:06 +01:00
Julius Härtl
aa38264d7b Properly setup github actions due to switch to ubuntu 20.04
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-03-04 09:21:36 +01:00
Julius Härtl
fa6f50d326 Bump version to 1.2.4
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-02-02 20:33:39 +01:00
Julius Härtl
e193271376 Merge pull request #2758 from nextcloud/backport/2750/stable1.2 2021-02-02 20:05:10 +01:00
Julius Härtl
d0d6b412ea Merge pull request #2757 from nextcloud/backport/2716/stable1.2
[stable1.2] Remove repair step which is no longer needed as we cleanup properly
2021-01-27 16:52:19 +01:00
Joas Schilling
9e438a9bb6 Fix php CS
Signed-off-by: Joas Schilling <coding@schilljs.com>
2021-01-27 15:51:04 +00:00
Joas Schilling
0fdffc19ae Fix deck activity emails not being translated
Signed-off-by: Joas Schilling <coding@schilljs.com>
2021-01-27 15:51:03 +00:00
Julius Härtl
02595e6593 Remove repair step which is no longer needed as we cleanup automatically once the user gets deleted
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-01-27 15:35:26 +00:00
Julius Härtl
2115669ab5 Merge pull request #2755 from nextcloud/backport/2751/stable1.2
[stable1.2] Properly set author for activity events that are triggered by cron
2021-01-27 16:34:42 +01:00
Julius Härtl
8574c71aaf Properly set author for activity events that are triggered by cron
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-01-27 15:21:10 +00:00
Julius Härtl
950e50120f Merge pull request #2752 from nextcloud/backport/2729/stable1.2
[stable1.2] Remove invalid activity parameters
2021-01-27 16:20:57 +01:00
Joas Schilling
1ec75e6b75 Remove invalid activity parameters
Signed-off-by: Joas Schilling <coding@schilljs.com>
2021-01-25 09:58:34 +00:00
Julius Härtl
191b22fb23 Merge pull request #2715 from nextcloud/backport/2683/stable1.2
[stable1.2] Handle clicks on calendar entries
2021-01-08 15:56:55 +01:00
Julius Härtl
6b8318d169 Handle clicks on calendar entries
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-01-08 12:39:45 +00:00
Julius Härtl
c8910fa395 Bump version to 1.2.3
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-01-04 22:53:41 +01:00
17 changed files with 168 additions and 342 deletions

View File

@@ -6,7 +6,7 @@ on:
jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-18.04
strategy:
matrix:

View File

@@ -66,7 +66,7 @@ jobs:
with:
php-version: ${{ matrix.php-versions }}
tools: phpunit
extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite, mysql, pdo_mysql, pgsql, pdo_pgsql
extensions: mbstring, iconv, fileinfo, intl, sqlite, pdo_sqlite, mysql, pdo_mysql, pgsql, pdo_pgsql, zip, gd
coverage: none
- name: Set up PHPUnit

View File

@@ -1,6 +1,38 @@
# Changelog
All notable changes to this project will be documented in this file.
## 1.2.5 - 2021-03-05
### Fixed
* [#2845](https://github.com/nextcloud/deck/pull/2845) Search by mail on the board sharing input
* [#2850](https://github.com/nextcloud/deck/pull/2850) Switch to Content-Disposition attachment and check for sane mimetypes
* [#2860](https://github.com/nextcloud/deck/pull/2860) Properly pass the user to fetch circles when calling through occ
## 1.2.4 - 2021-02-02
### Fixed
* [#2715](https://github.com/nextcloud/deck/pull/2715) Handle clicks on calendar entries
* [#2752](https://github.com/nextcloud/deck/pull/2752) Remove invalid activity parameters
* [#2755](https://github.com/nextcloud/deck/pull/2755) Properly set author for activity events that are triggered by cron
* [#2757](https://github.com/nextcloud/deck/pull/2757) Remove repair step which is no longer needed as we cleanup properly
* [#2758](https://github.com/nextcloud/deck/pull/2758) Fix deck activity emails not being translated
## 1.2.3 - 2021-01-04
### Fixed
* [#2622](https://github.com/nextcloud/deck/pull/2622) Fix gradient and stack header spacing for safari
* [#2626](https://github.com/nextcloud/deck/pull/2626) Adding a description icon to cards when they contain a description without any checkmarks @MonkeySon
* [#2659](https://github.com/nextcloud/deck/pull/2659) Matching color of description cursor with text color @JonFStr
* [#2676](https://github.com/nextcloud/deck/pull/2676) Only load filter view when shown
* [#2680](https://github.com/nextcloud/deck/pull/2680) Do not try to add change data if it doesn't exist
* [#2681](https://github.com/nextcloud/deck/pull/2681) Filter out deleted stacks from results
* [#2685](https://github.com/nextcloud/deck/pull/2685) Show all boards in move card dialog @jakobroehrl
* [#2687](https://github.com/nextcloud/deck/pull/2687) 3dots no opacity @jakobroehrl
* [#2688](https://github.com/nextcloud/deck/pull/2688) Title > boardname @jakobroehrl
* [#2689](https://github.com/nextcloud/deck/pull/2689) Modal > bigger view wording @jakobroehrl
## 1.2.2 - 2020-11-24
### Fixed

View File

@@ -17,7 +17,7 @@
- 🚀 Get your project organized
</description>
<version>1.2.2</version>
<version>1.2.5</version>
<licence>agpl</licence>
<author>Julius Härtl</author>
<namespace>Deck</namespace>
@@ -36,18 +36,13 @@
<database min-version="9.4">pgsql</database>
<database>sqlite</database>
<database min-version="5.5">mysql</database>
<nextcloud min-version="18" max-version="21" />
<nextcloud min-version="18" max-version="20" />
</dependencies>
<background-jobs>
<job>OCA\Deck\Cron\DeleteCron</job>
<job>OCA\Deck\Cron\ScheduledNotifications</job>
<job>OCA\Deck\Cron\CardDescriptionActivity</job>
</background-jobs>
<repair-steps>
<post-migration>
<step>OCA\Deck\Migration\UnknownUsers</step>
</post-migration>
</repair-steps>
<commands>
<command>OCA\Deck\Command\UserExport</command>
</commands>

View File

@@ -26,6 +26,7 @@
namespace OCA\Deck\Activity;
use InvalidArgumentException;
use OCA\Deck\AppInfo\Application;
use OCA\Deck\Db\Acl;
use OCA\Deck\Db\AclMapper;
use OCA\Deck\Db\Assignment;
@@ -44,8 +45,8 @@ use OCP\Activity\IManager;
use OCP\AppFramework\Db\DoesNotExistException;
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
use OCP\Comments\IComment;
use OCP\IL10N;
use OCP\IUser;
use OCP\L10N\IFactory;
class ActivityManager {
public const DECK_NOAUTHOR_COMMENT_SYSTEM_ENFORCED = 'DECK_NOAUTHOR_COMMENT_SYSTEM_ENFORCED';
@@ -57,7 +58,7 @@ class ActivityManager {
private $attachmentMapper;
private $aclMapper;
private $stackMapper;
private $l10n;
private $l10nFactory;
public const DECK_OBJECT_BOARD = 'deck_board';
public const DECK_OBJECT_CARD = 'deck_card';
@@ -111,7 +112,7 @@ class ActivityManager {
StackMapper $stackMapper,
AttachmentMapper $attachmentMapper,
AclMapper $aclMapper,
IL10N $l10n,
IFactory $l10nFactory,
$userId
) {
$this->manager = $manager;
@@ -121,117 +122,119 @@ class ActivityManager {
$this->stackMapper = $stackMapper;
$this->attachmentMapper = $attachmentMapper;
$this->aclMapper = $aclMapper;
$this->l10n = $l10n;
$this->l10nFactory = $l10nFactory;
$this->userId = $userId;
}
/**
* @param $subjectIdentifier
* @param string $subjectIdentifier
* @param array $subjectParams
* @param bool $ownActivity
* @return string
*/
public function getActivityFormat($subjectIdentifier, $subjectParams = [], $ownActivity = false) {
public function getActivityFormat($language, $subjectIdentifier, $subjectParams = [], $ownActivity = false) {
$subject = '';
$l = $this->l10nFactory->get(Application::APP_ID, $language);
switch ($subjectIdentifier) {
case self::SUBJECT_BOARD_CREATE:
$subject = $ownActivity ? $this->l10n->t('You have created a new board {board}'): $this->l10n->t('{user} has created a new board {board}');
$subject = $ownActivity ? $l->t('You have created a new board {board}'): $l->t('{user} has created a new board {board}');
break;
case self::SUBJECT_BOARD_DELETE:
$subject = $ownActivity ? $this->l10n->t('You have deleted the board {board}') : $this->l10n->t('{user} has deleted the board {board}');
$subject = $ownActivity ? $l->t('You have deleted the board {board}') : $l->t('{user} has deleted the board {board}');
break;
case self::SUBJECT_BOARD_RESTORE:
$subject = $ownActivity ? $this->l10n->t('You have restored the board {board}') : $this->l10n->t('{user} has restored the board {board}');
$subject = $ownActivity ? $l->t('You have restored the board {board}') : $l->t('{user} has restored the board {board}');
break;
case self::SUBJECT_BOARD_SHARE:
$subject = $ownActivity ? $this->l10n->t('You have shared the board {board} with {acl}') : $this->l10n->t('{user} has shared the board {board} with {acl}');
$subject = $ownActivity ? $l->t('You have shared the board {board} with {acl}') : $l->t('{user} has shared the board {board} with {acl}');
break;
case self::SUBJECT_BOARD_UNSHARE:
$subject = $ownActivity ? $this->l10n->t('You have removed {acl} from the board {board}') : $this->l10n->t('{user} has removed {acl} from the board {board}');
$subject = $ownActivity ? $l->t('You have removed {acl} from the board {board}') : $l->t('{user} has removed {acl} from the board {board}');
break;
case self::SUBJECT_BOARD_UPDATE_TITLE:
$subject = $ownActivity ? $this->l10n->t('You have renamed the board {before} to {board}') : $this->l10n->t('{user} has renamed the board {before} to {board}');
$subject = $ownActivity ? $l->t('You have renamed the board {before} to {board}') : $l->t('{user} has renamed the board {before} to {board}');
break;
case self::SUBJECT_BOARD_UPDATE_ARCHIVED:
if (isset($subjectParams['after']) && $subjectParams['after']) {
$subject = $ownActivity ? $this->l10n->t('You have archived the board {board}') : $this->l10n->t('{user} has archived the board {before}');
$subject = $ownActivity ? $l->t('You have archived the board {board}') : $l->t('{user} has archived the board {before}');
} else {
$subject = $ownActivity ? $this->l10n->t('You have unarchived the board {board}') : $this->l10n->t('{user} has unarchived the board {before}');
$subject = $ownActivity ? $l->t('You have unarchived the board {board}') : $l->t('{user} has unarchived the board {before}');
}
break;
case self::SUBJECT_STACK_CREATE:
$subject = $ownActivity ? $this->l10n->t('You have created a new list {stack} on board {board}') : $this->l10n->t('{user} has created a new list {stack} on board {board}');
$subject = $ownActivity ? $l->t('You have created a new list {stack} on board {board}') : $l->t('{user} has created a new list {stack} on board {board}');
break;
case self::SUBJECT_STACK_UPDATE:
$subject = $ownActivity ? $this->l10n->t('You have created a new list {stack} on board {board}') : $this->l10n->t('{user} has created a new list {stack} on board {board}');
$subject = $ownActivity ? $l->t('You have created a new list {stack} on board {board}') : $l->t('{user} has created a new list {stack} on board {board}');
break;
case self::SUBJECT_STACK_UPDATE_TITLE:
$subject = $ownActivity ? $this->l10n->t('You have renamed list {before} to {stack} on board {board}') : $this->l10n->t('{user} has renamed list {before} to {stack} on board {board}');
$subject = $ownActivity ? $l->t('You have renamed list {before} to {stack} on board {board}') : $l->t('{user} has renamed list {before} to {stack} on board {board}');
break;
case self::SUBJECT_STACK_DELETE:
$subject = $ownActivity ? $this->l10n->t('You have deleted list {stack} on board {board}') : $this->l10n->t('{user} has deleted list {stack} on board {board}');
$subject = $ownActivity ? $l->t('You have deleted list {stack} on board {board}') : $l->t('{user} has deleted list {stack} on board {board}');
break;
case self::SUBJECT_CARD_CREATE:
$subject = $ownActivity ? $this->l10n->t('You have created card {card} in list {stack} on board {board}') : $this->l10n->t('{user} has created card {card} in list {stack} on board {board}');
$subject = $ownActivity ? $l->t('You have created card {card} in list {stack} on board {board}') : $l->t('{user} has created card {card} in list {stack} on board {board}');
break;
case self::SUBJECT_CARD_DELETE:
$subject = $ownActivity ? $this->l10n->t('You have deleted card {card} in list {stack} on board {board}') : $this->l10n->t('{user} has deleted card {card} in list {stack} on board {board}');
$subject = $ownActivity ? $l->t('You have deleted card {card} in list {stack} on board {board}') : $l->t('{user} has deleted card {card} in list {stack} on board {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}');
$subject = $ownActivity ? $l->t('You have renamed the card {before} to {card}') : $l->t('{user} has renamed the card {before} to {card}');
break;
case self::SUBJECT_CARD_UPDATE_DESCRIPTION:
if (!isset($subjectParams['before'])) {
$subject = $ownActivity ? $this->l10n->t('You have added a description to card {card} in list {stack} on board {board}') : $this->l10n->t('{user} has added a description to card {card} in list {stack} on board {board}');
$subject = $ownActivity ? $l->t('You have added a description to card {card} in list {stack} on board {board}') : $l->t('{user} has added a description to card {card} in list {stack} on board {board}');
} else {
$subject = $ownActivity ? $this->l10n->t('You have updated the description of card {card} in list {stack} on board {board}') : $this->l10n->t('{user} has updated the description of the card {card} in list {stack} on board {board}');
$subject = $ownActivity ? $l->t('You have updated the description of card {card} in list {stack} on board {board}') : $l->t('{user} has updated the description of the card {card} in list {stack} on board {board}');
}
break;
case self::SUBJECT_CARD_UPDATE_ARCHIVE:
$subject = $ownActivity ? $this->l10n->t('You have archived card {card} in list {stack} on board {board}') : $this->l10n->t('{user} has archived card {card} in list {stack} on board {board}');
$subject = $ownActivity ? $l->t('You have archived card {card} in list {stack} on board {board}') : $l->t('{user} has archived card {card} in list {stack} on board {board}');
break;
case self::SUBJECT_CARD_UPDATE_UNARCHIVE:
$subject = $ownActivity ? $this->l10n->t('You have unarchived card {card} in list {stack} on board {board}') : $this->l10n->t('{user} has unarchived card {card} in list {stack} on board {board}');
$subject = $ownActivity ? $l->t('You have unarchived card {card} in list {stack} on board {board}') : $l->t('{user} has unarchived card {card} in list {stack} on board {board}');
break;
case self::SUBJECT_CARD_UPDATE_DUEDATE:
if (!isset($subjectParams['after'])) {
$subject = $ownActivity ? $this->l10n->t('You have removed the due date of card {card}') : $this->l10n->t('{user} has removed the due date of card {card}');
$subject = $ownActivity ? $l->t('You have removed the due date of card {card}') : $l->t('{user} has removed the due date of card {card}');
} elseif (!isset($subjectParams['before']) && isset($subjectParams['after'])) {
$subject = $ownActivity ? $this->l10n->t('You have set the due date of card {card} to {after}') : $this->l10n->t('{user} has set the due date of card {card} to {after}');
$subject = $ownActivity ? $l->t('You have set the due date of card {card} to {after}') : $l->t('{user} has set the due date of card {card} to {after}');
} else {
$subject = $ownActivity ? $this->l10n->t('You have updated the due date of card {card} to {after}') : $this->l10n->t('{user} has updated the due date of card {card} to {after}');
$subject = $ownActivity ? $l->t('You have updated the due date of card {card} to {after}') : $l->t('{user} has updated the due date of card {card} to {after}');
}
break;
case self::SUBJECT_LABEL_ASSIGN:
$subject = $ownActivity ? $this->l10n->t('You have added the tag {label} to card {card} in list {stack} on board {board}') : $this->l10n->t('{user} has added the tag {label} to card {card} in list {stack} on board {board}');
$subject = $ownActivity ? $l->t('You have added the tag {label} to card {card} in list {stack} on board {board}') : $l->t('{user} has added the tag {label} to card {card} in list {stack} on board {board}');
break;
case self::SUBJECT_LABEL_UNASSING:
$subject = $ownActivity ? $this->l10n->t('You have removed the tag {label} from card {card} in list {stack} on board {board}') : $this->l10n->t('{user} has removed the tag {label} from card {card} in list {stack} on board {board}');
$subject = $ownActivity ? $l->t('You have removed the tag {label} from card {card} in list {stack} on board {board}') : $l->t('{user} has removed the tag {label} from card {card} in list {stack} on board {board}');
break;
case self::SUBJECT_CARD_USER_ASSIGN:
$subject = $ownActivity ? $this->l10n->t('You have assigned {assigneduser} to card {card} on board {board}') : $this->l10n->t('{user} has assigned {assigneduser} to card {card} on board {board}');
$subject = $ownActivity ? $l->t('You have assigned {assigneduser} to card {card} on board {board}') : $l->t('{user} has assigned {assigneduser} to card {card} on board {board}');
break;
case self::SUBJECT_CARD_USER_UNASSIGN:
$subject = $ownActivity ? $this->l10n->t('You have unassigned {assigneduser} from card {card} on board {board}') : $this->l10n->t('{user} has unassigned {assigneduser} from card {card} on board {board}');
$subject = $ownActivity ? $l->t('You have unassigned {assigneduser} from card {card} on board {board}') : $l->t('{user} has unassigned {assigneduser} from card {card} on board {board}');
break;
case self::SUBJECT_CARD_UPDATE_STACKID:
$subject = $ownActivity ? $this->l10n->t('You have moved the card {card} from list {stackBefore} to {stack}') : $this->l10n->t('{user} has moved the card {card} from list {stackBefore} to {stack}');
$subject = $ownActivity ? $l->t('You have moved the card {card} from list {stackBefore} to {stack}') : $l->t('{user} has moved the card {card} from list {stackBefore} to {stack}');
break;
case self::SUBJECT_ATTACHMENT_CREATE:
$subject = $ownActivity ? $this->l10n->t('You have added the attachment {attachment} to card {card}') : $this->l10n->t('{user} has added the attachment {attachment} to card {card}');
$subject = $ownActivity ? $l->t('You have added the attachment {attachment} to card {card}') : $l->t('{user} has added the attachment {attachment} to card {card}');
break;
case self::SUBJECT_ATTACHMENT_UPDATE:
$subject = $ownActivity ? $this->l10n->t('You have updated the attachment {attachment} on card {card}') : $this->l10n->t('{user} has updated the attachment {attachment} on card {card}');
$subject = $ownActivity ? $l->t('You have updated the attachment {attachment} on card {card}') : $l->t('{user} has updated the attachment {attachment} on card {card}');
break;
case self::SUBJECT_ATTACHMENT_DELETE:
$subject = $ownActivity ? $this->l10n->t('You have deleted the attachment {attachment} from card {card}') : $this->l10n->t('{user} has deleted the attachment {attachment} from card {card}');
$subject = $ownActivity ? $l->t('You have deleted the attachment {attachment} from card {card}') : $l->t('{user} has deleted the attachment {attachment} from card {card}');
break;
case self::SUBJECT_ATTACHMENT_RESTORE:
$subject = $ownActivity ? $this->l10n->t('You have restored the attachment {attachment} to card {card}') : $this->l10n->t('{user} has restored the attachment {attachment} to card {card}');
$subject = $ownActivity ? $l->t('You have restored the attachment {attachment} to card {card}') : $l->t('{user} has restored the attachment {attachment} to card {card}');
break;
case self::SUBJECT_CARD_COMMENT_CREATE:
$subject = $ownActivity ? $this->l10n->t('You have commented on card {card}') : $this->l10n->t('{user} has commented on card {card}');
$subject = $ownActivity ? $l->t('You have commented on card {card}') : $l->t('{user} has commented on card {card}');
break;
default:
break;
@@ -402,15 +405,15 @@ class ActivityManager {
if ($subject === self::SUBJECT_CARD_UPDATE_STACKID) {
$subjectParams['stackBefore'] = $this->stackMapper->find($additionalParams['before']);
$subjectParams['stack'] = $this->stackMapper->find($additionalParams['after']);
unset($additionalParams['after'], $additionalParams['before']);
}
$subjectParams['author'] = $this->userId;
$subjectParams['author'] = $author === null ? $this->userId : $author;
$event = $this->manager->generateEvent();
$event->setApp('deck')
->setType($eventType)
->setAuthor($author === null ? $this->userId : $author)
->setAuthor($subjectParams['author'])
->setObject($objectType, (int)$object->getId(), $object->getTitle())
->setSubject($subject, array_merge($subjectParams, $additionalParams))
->setTimestamp(time());

View File

@@ -143,12 +143,14 @@ class DeckProvider implements IProvider {
$params = $this->parseParamForLabel($subjectParams, $params);
$params = $this->parseParamForAssignedUser($subjectParams, $params);
$params = $this->parseParamForAcl($subjectParams, $params);
$params = $this->parseParamForChanges($subjectParams, $params, $event);
if ($subjectIdentifier !== ActivityManager::SUBJECT_CARD_UPDATE_STACKID) {
$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);
$subject = $this->activityManager->getActivityFormat($language, $subjectIdentifier, $subjectParams, $ownActivity);
$this->setSubjects($event, $subject, $params);
} catch (\Exception $e) {
}

View File

@@ -159,7 +159,7 @@ class BoardMapper extends DeckMapper implements IPermissionMapper {
}
$circles = array_map(function ($circle) {
return $circle->getUniqueId();
}, \OCA\Circles\Api\v1\Circles::joinedCircles('', true));
}, \OCA\Circles\Api\v1\Circles::joinedCircles($userId, true));
if (count($circles) === 0) {
return [];
}

View File

@@ -29,9 +29,16 @@ namespace OCA\Deck\Listeners;
use OCP\AppFramework\Http\Events\BeforeTemplateRenderedEvent;
use OCP\EventDispatcher\Event;
use OCP\EventDispatcher\IEventListener;
use OCP\IRequest;
use OCP\Util;
class BeforeTemplateRenderedListener implements IEventListener {
private $request;
public function __construct(IRequest $request) {
$this->request = $request;
}
public function handle(Event $event): void {
if (!($event instanceof BeforeTemplateRenderedEvent)) {
return;
@@ -41,5 +48,9 @@ class BeforeTemplateRenderedListener implements IEventListener {
return;
}
Util::addStyle('deck', 'deck');
if (strpos($this->request->getPathInfo(), '/apps/calendar') === 0) {
Util::addScript('deck', 'calendar');
}
}
}

View File

@@ -1,81 +0,0 @@
<?php
/**
* @copyright Copyright (c) 2017 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\Migration;
use OCA\Deck\Db\Acl;
use OCA\Deck\Db\AclMapper;
use OCA\Deck\Db\Board;
use OCA\Deck\Db\BoardMapper;
use OCP\IGroupManager;
use OCP\IUserManager;
use OCP\Migration\IRepairStep;
use OCP\Migration\IOutput;
class UnknownUsers implements IRepairStep {
private $userManager;
private $groupManager;
private $aclMapper;
private $boardMapper;
public function __construct(IUserManager $userManager, IGroupManager $groupManager, AclMapper $aclMapper, BoardMapper $boardMapper) {
$this->userManager = $userManager;
$this->groupManager = $groupManager;
$this->aclMapper = $aclMapper;
$this->boardMapper = $boardMapper;
}
/*
* @inheritdoc
*/
public function getName() {
return 'Delete orphaned ACL rules';
}
/**
* @inheritdoc
*/
public function run(IOutput $output) {
$boards = $this->boardMapper->findAll();
/** @var Board $board */
foreach ($boards as $board) {
$acls = $this->aclMapper->findAll($board->getId());
/** @var Acl $acl */
foreach ($acls as $acl) {
if ($acl->getType() === Acl::PERMISSION_TYPE_USER) {
$user = $this->userManager->get($acl->getParticipant());
if ($user === null) {
$this->aclMapper->delete($acl);
}
}
if ($acl->getType() === Acl::PERMISSION_TYPE_GROUP) {
$group = $this->groupManager->get($acl->getParticipant());
if ($group === null) {
$this->aclMapper->delete($acl);
}
}
}
}
}
}

View File

@@ -27,10 +27,9 @@ use OCA\Deck\Db\Attachment;
use OCA\Deck\Db\AttachmentMapper;
use OCA\Deck\StatusException;
use OCA\Deck\Exceptions\ConflictException;
use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\FileDisplayResponse;
use OCP\AppFramework\Http\StreamResponse;
use OCP\Files\IAppData;
use OCP\Files\IMimeTypeDetector;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\Files\NotPermittedException;
@@ -49,6 +48,7 @@ class FileService implements IAttachmentService {
private $rootFolder;
private $config;
private $attachmentMapper;
private $mimeTypeDetector;
public function __construct(
IL10N $l10n,
@@ -57,7 +57,8 @@ class FileService implements IAttachmentService {
ILogger $logger,
IRootFolder $rootFolder,
IConfig $config,
AttachmentMapper $attachmentMapper
AttachmentMapper $attachmentMapper,
IMimeTypeDetector $mimeTypeDetector
) {
$this->l10n = $l10n;
$this->appData = $appData;
@@ -66,6 +67,7 @@ class FileService implements IAttachmentService {
$this->rootFolder = $rootFolder;
$this->config = $config;
$this->attachmentMapper = $attachmentMapper;
$this->mimeTypeDetector = $mimeTypeDetector;
}
/**
@@ -225,27 +227,14 @@ class FileService implements IAttachmentService {
/**
* @param Attachment $attachment
* @return FileDisplayResponse|\OCP\AppFramework\Http\Response|StreamResponse
* @return StreamResponse
* @throws \Exception
*/
public function display(Attachment $attachment) {
$file = $this->getFileFromRootFolder($attachment);
if (method_exists($file, 'fopen')) {
$response = new StreamResponse($file->fopen('r'));
$response->addHeader('Content-Disposition', 'inline; filename="' . rawurldecode($file->getName()) . '"');
} else {
$response = new FileDisplayResponse($file);
}
// We need those since otherwise chrome won't show the PDF file with CSP rule object-src 'none'
// https://bugs.chromium.org/p/chromium/issues/detail?id=271452
$policy = new ContentSecurityPolicy();
$policy->addAllowedObjectDomain('\'self\'');
$policy->addAllowedObjectDomain('blob:');
$policy->addAllowedMediaDomain('\'self\'');
$policy->addAllowedMediaDomain('blob:');
$response->setContentSecurityPolicy($policy);
$response->addHeader('Content-Type', $file->getMimeType());
$response = new StreamResponse($file->fopen('rb'));
$response->addHeader('Content-Disposition', 'attachment; filename="' . rawurldecode($file->getName()) . '"');
$response->addHeader('Content-Type', $this->mimeTypeDetector->getSecureMimeType($file->getMimeType()));
return $response;
}

View File

@@ -10,7 +10,7 @@
:loading="isLoading || !!isSearching"
:disabled="isLoading"
track-by="multiselectKey"
:internal-search="true"
:internal-search="false"
@input="clickAddAcl"
@search-change="asyncFind">
<template #noOptions>
@@ -73,6 +73,7 @@ import { CollectionList } from 'nextcloud-vue-collections'
import { mapGetters, mapState } from 'vuex'
import { getCurrentUser } from '@nextcloud/auth'
import { showError } from '@nextcloud/dialogs'
import { debounce } from 'lodash'
export default {
name: 'SharingTabSidebar',
@@ -148,18 +149,13 @@ export default {
this.asyncFind('')
},
methods: {
debouncedFind: debounce(async function(query) {
this.isSearching = true
await this.$store.dispatch('loadSharees', query)
this.isSearching = false
}, 300),
async asyncFind(query) {
// manual debounce to handle async searching more easily and have more control over the loading state
const timestamp = (new Date()).getTime()
if (!this.isSearching || timestamp > this.isSearching + 300) {
this.isSearching = timestamp
await this.$store.dispatch('loadSharees', query)
// only reset searching flag if the most recent search finished
if (this.isSearching === timestamp) {
this.isSearching = false
}
}
await this.debouncedFind(query)
},
async clickAddAcl() {
this.addAclForAPI = {

34
src/init-calendar.js Normal file
View File

@@ -0,0 +1,34 @@
/*
* @copyright Copyright (c) 2020 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/>.
*
*/
import { subscribe } from '@nextcloud/event-bus'
import { generateUrl } from '@nextcloud/router'
subscribe('calendar:handle-todo-click', ({ calendarId, taskId }) => {
const deckAppPrefix = 'app-generated--deck--board-'
if (calendarId.startsWith(deckAppPrefix)) {
const board = calendarId.substr(deckAppPrefix.length)
const card = taskId.substr('card-'.length).replace('.ics', '')
console.debug('[deck] Clicked task matches deck calendar pattern')
window.location = generateUrl(`apps/deck/#/board/${board}/card/${card}`)
}
})

View File

@@ -414,7 +414,7 @@ export default new Vuex.Store({
params.append('search', query)
params.append('format', 'json')
params.append('perPage', 20)
params.append('itemType', [0, 1, 7])
params.append('itemType', [0, 1, 4, 7])
const response = await axios.get(generateOcsUrl('apps/files_sharing/api/v1') + 'sharees', { params })
commit('setSharees', response.data.ocs.data)

View File

@@ -39,6 +39,7 @@ use OCP\Activity\IEvent;
use OCP\Activity\IManager;
use OCP\IL10N;
use OCP\IUser;
use OCP\L10N\IFactory;
use PHPUnit\Framework\TestCase;
use PHPUnit_Framework_MockObject_MockObject as MockObject;
@@ -60,6 +61,8 @@ class ActivityManagerTest extends TestCase {
private $attachmentMapper;
/** @var AclMapper|MockObject */
private $aclMapper;
/** @var IFactory|MockObject */
private $l10nFactory;
/** @var IL10N|MockObject */
private $l10n;
/** @var string */
@@ -73,6 +76,7 @@ class ActivityManagerTest extends TestCase {
$this->stackMapper = $this->createMock(StackMapper::class);
$this->attachmentMapper = $this->createMock(AttachmentMapper::class);
$this->aclMapper = $this->createMock(AclMapper::class);
$this->l10nFactory = $this->createMock(IFactory::class);
$this->l10n = $this->createMock(IL10N::class);
$this->activityManager = new ActivityManager(
$this->manager,
@@ -82,7 +86,7 @@ class ActivityManagerTest extends TestCase {
$this->stackMapper,
$this->attachmentMapper,
$this->aclMapper,
$this->l10n,
$this->l10nFactory,
$this->userId
);
}
@@ -94,17 +98,20 @@ class ActivityManagerTest extends TestCase {
->will($this->returnCallback(function ($s) {
return $s;
}));
$this->l10nFactory->method('get')
->with('deck', 'cz')
->willReturn($this->l10n);
foreach ($managerClass->getConstants() as $constant => $value) {
if (strpos($constant, 'SUBJECT') === 0) {
$format = $this->activityManager->getActivityFormat($value, [], false);
$format = $this->activityManager->getActivityFormat('cz', $value, [], false);
if ($format !== '') {
$this->assertStringContainsString('{user}', $format);
} else {
/** @noinspection ForgottenDebugOutputInspection */
print_r('No activity string found for '. $constant . PHP_EOL);
}
$format = $this->activityManager->getActivityFormat($value, [], true);
$format = $this->activityManager->getActivityFormat('cz', $value, [], true);
if ($format !== '') {
$this->assertStringStartsWith('You', $format);
} else {

View File

@@ -1,128 +0,0 @@
<?php
/**
* @copyright Copyright (c) 2017 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\Migration;
use OCA\Deck\Db\Acl;
use OCA\Deck\Db\AclMapper;
use OCA\Deck\Db\Board;
use OCA\Deck\Db\BoardMapper;
use OCP\IGroupManager;
use OCP\IUserManager;
use OCP\Migration\IOutput;
class UnknownUserTest extends \Test\TestCase {
/** @var IUserManager */
private $userManager;
/** @var IGroupManager */
private $groupManager;
/** @var AclMapper */
private $aclMapper;
/** @var BoardMapper */
private $boardMapper;
/** @var UnknownUsers */
private $unknownUsers;
public function setUp(): void {
parent::setUp();
$this->userManager = $this->createMock(IUserManager::class);
$this->groupManager = $this->createMock(IGroupManager::class);
$this->aclMapper = $this->createMock(AclMapper::class);
$this->boardMapper = $this->createMock(BoardMapper::class);
$this->unknownUsers = new UnknownUsers($this->userManager, $this->groupManager, $this->aclMapper, $this->boardMapper);
}
public function testGetName() {
$this->assertEquals('Delete orphaned ACL rules', $this->unknownUsers->getName());
}
public function testRun() {
/** @var IOutput $output */
$output = $this->createMock(IOutput::class);
$boards = [
$this->getBoard(1,'Test', 'admin'),
];
$acl = [
$this->getAcl(Acl::PERMISSION_TYPE_USER, 'existing', 1),
$this->getAcl(Acl::PERMISSION_TYPE_USER, 'not existing', 1),
$this->getAcl(Acl::PERMISSION_TYPE_GROUP, 'existing', 1),
$this->getAcl(Acl::PERMISSION_TYPE_GROUP, 'not existing', 1),
];
$this->aclMapper->expects($this->at(0))
->method('findAll')
->with(1)
->willReturn($acl);
$this->userManager->expects($this->at(0))
->method('get')
->with('existing')
->willReturn(true);
$this->userManager->expects($this->at(1))
->method('get')
->with('not existing')
->willReturn(null);
$this->groupManager->expects($this->at(0))
->method('get')
->with('existing')
->willReturn(true);
$this->groupManager->expects($this->at(1))
->method('get')
->with('not existing')
->willReturn(null);
$this->boardMapper->expects($this->once())
->method('findAll')
->willReturn($boards);
$this->aclMapper->expects($this->at(1))
->method('delete')
->with($acl[1]);
$this->aclMapper->expects($this->at(2))
->method('delete')
->with($acl[3]);
$this->unknownUsers->run($output);
}
/** @return Acl */
public function getAcl($type = Acl::PERMISSION_TYPE_USER, $participant = 'admin', $boardId = 123) {
$acl = new Acl();
$acl->setParticipant($participant);
$acl->setType($type);
$acl->setBoardId($boardId);
return $acl;
}
/** @return Board */
public function getBoard($id, $title, $owner) {
$board = new Board();
$board->setId($id);
$board->setTitle($title);
$board->setOwner($owner);
return $board;
}
}

View File

@@ -25,10 +25,10 @@ namespace OCA\Deck\Service;
use OCA\Deck\Db\Attachment;
use OCA\Deck\Db\AttachmentMapper;
use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\StreamResponse;
use OCP\Files\Folder;
use OCP\Files\IAppData;
use OCP\Files\IMimeTypeDetector;
use OCP\Files\IRootFolder;
use OCP\Files\SimpleFS\ISimpleFile;
use OCP\Files\SimpleFS\ISimpleFolder;
@@ -57,6 +57,8 @@ class FileServiceTest extends TestCase {
private $config;
/** @var AttachmentMapper|MockObject */
private $attachmentMapper;
/** @var IMimeTypeDetector|MockObject */
private $mimeTypeDetector;
public function setUp(): void {
parent::setUp();
@@ -67,7 +69,8 @@ class FileServiceTest extends TestCase {
$this->rootFolder = $this->createMock(IRootFolder::class);
$this->config = $this->createMock(IConfig::class);
$this->attachmentMapper = $this->createMock(AttachmentMapper::class);
$this->fileService = new FileService($this->l10n, $this->appData, $this->request, $this->logger, $this->rootFolder, $this->config, $this->attachmentMapper);
$this->mimeTypeDetector = $this->createMock(IMimeTypeDetector::class);
$this->fileService = new FileService($this->l10n, $this->appData, $this->request, $this->logger, $this->rootFolder, $this->config, $this->attachmentMapper, $this->mimeTypeDetector);
}
public function mockGetFolder($cardId) {
@@ -268,51 +271,13 @@ class FileServiceTest extends TestCase {
$file->expects($this->any())
->method('fopen')
->willReturn('fileresource');
$this->mimeTypeDetector->expects($this->once())
->method('getSecureMimeType')
->willReturn('image/jpeg');
$actual = $this->fileService->display($attachment);
$expected = new StreamResponse('fileresource');
$expected->addHeader('Content-Type', 'image/jpeg');
$expected->addHeader('Content-Disposition', 'inline; filename="' . rawurldecode($file->getName()) . '"');
$policy = new ContentSecurityPolicy();
$policy->addAllowedObjectDomain('\'self\'');
$policy->addAllowedObjectDomain('blob:');
$policy->addAllowedMediaDomain('\'self\'');
$policy->addAllowedMediaDomain('blob:');
$expected->setContentSecurityPolicy($policy);
$this->assertEquals($expected, $actual);
}
public function testDisplayPdf() {
$this->config->expects($this->once())
->method('getSystemValue')
->willReturn('123');
$appDataFolder = $this->createMock(Folder::class);
$deckAppDataFolder = $this->createMock(Folder::class);
$cardFolder = $this->createMock(Folder::class);
$this->rootFolder->expects($this->once())->method('get')->willReturn($appDataFolder);
$appDataFolder->expects($this->once())->method('get')->willReturn($deckAppDataFolder);
$deckAppDataFolder->expects($this->once())->method('get')->willReturn($cardFolder);
$attachment = $this->getAttachment();
$file = $this->createMock(\OCP\Files\File::class);
$cardFolder->expects($this->once())->method('get')->willReturn($file);
$file->expects($this->any())
->method('getMimeType')
->willReturn('application/pdf');
$file->expects($this->any())
->method('getName')
->willReturn('file1');
$file->expects($this->any())
->method('fopen')
->willReturn('fileresource');
$actual = $this->fileService->display($attachment);
$expected = new StreamResponse('fileresource');
$expected->addHeader('Content-Disposition', 'inline; filename="' . rawurldecode($file->getName()) . '"');
$expected->addHeader('Content-Type', 'application/pdf');
$policy = new ContentSecurityPolicy();
$policy->addAllowedObjectDomain('\'self\'');
$policy->addAllowedObjectDomain('blob:');
$policy->addAllowedMediaDomain('\'self\'');
$policy->addAllowedMediaDomain('blob:');
$expected->setContentSecurityPolicy($policy);
$expected->addHeader('Content-Disposition', 'attachment; filename="' . rawurldecode($file->getName()) . '"');
$this->assertEquals($expected, $actual);
}

View File

@@ -6,6 +6,7 @@ const config = {
entry: {
collections: path.join(__dirname, 'src', 'init-collections.js'),
dashboard: path.join(__dirname, 'src', 'init-dashboard.js'),
calendar: path.join(__dirname, 'src', 'init-calendar.js'),
},
output: {
filename: '[name].js',