Compare commits
20 Commits
debug/cypr
...
v1.6.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d4c9147419 | ||
|
|
21b81ff56c | ||
|
|
4645c5077d | ||
|
|
9a7bcafa6c | ||
|
|
53ebe480cf | ||
|
|
e4db5e4d28 | ||
|
|
2b8dee5081 | ||
|
|
a8d41797ef | ||
|
|
bc9fe51036 | ||
|
|
d16799948f | ||
|
|
764dd947d4 | ||
|
|
ea35f64d2a | ||
|
|
a2205db748 | ||
|
|
a80d87a3e9 | ||
|
|
12ed617a56 | ||
|
|
ffccbf73fd | ||
|
|
8514e91edc | ||
|
|
8926363092 | ||
|
|
2be4fff781 | ||
|
|
8f52667d2c |
2
.github/workflows/integration.yml
vendored
2
.github/workflows/integration.yml
vendored
@@ -19,7 +19,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
php-versions: ['7.4']
|
php-versions: ['7.4']
|
||||||
databases: ['sqlite', 'mysql', 'pgsql']
|
databases: ['sqlite', 'mysql', 'pgsql']
|
||||||
server-versions: ['master']
|
server-versions: ['stable23']
|
||||||
|
|
||||||
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
|
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/phpunit.yml
vendored
2
.github/workflows/phpunit.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
php-versions: ['7.3', '7.4']
|
php-versions: ['7.3', '7.4']
|
||||||
databases: ['sqlite', 'mysql', 'pgsql']
|
databases: ['sqlite', 'mysql', 'pgsql']
|
||||||
server-versions: ['master']
|
server-versions: ['stable23']
|
||||||
|
|
||||||
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
|
name: php${{ matrix.php-versions }}-${{ matrix.databases }}-${{ matrix.server-versions }}
|
||||||
|
|
||||||
|
|||||||
2
.github/workflows/static-analysis.yml
vendored
2
.github/workflows/static-analysis.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
ocp-version: [ 'dev-master' ]
|
ocp-version: [ 'dev-stable23' ]
|
||||||
name: Nextcloud ${{ matrix.ocp-version }}
|
name: Nextcloud ${{ matrix.ocp-version }}
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
## 1.6.0-beta1
|
## 1.6.0
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
|
- #3449 Cache most frequent queries
|
||||||
- #3177 Use async import for vue component on collections entrypoint @juliushaertl
|
- #3177 Use async import for vue component on collections entrypoint @juliushaertl
|
||||||
- #2791 Open description links in new tab @fm-sys
|
- #2791 Open description links in new tab @fm-sys
|
||||||
- #3344 Improve combined search @eneiluj
|
- #3344 Improve combined search @eneiluj
|
||||||
@@ -13,6 +14,11 @@ All notable changes to this project will be documented in this file.
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- #3446 Switch to QBMapper in BoardMapper
|
||||||
|
- #3433 Fix event name for updating the description
|
||||||
|
- #3463 Fix overview card listing
|
||||||
|
- #3440 Allow to download an attachment without navigating to the files app
|
||||||
|
- #3462 Fix cursor generation if no results are found
|
||||||
- #3161 Reduce duplicate queries when fetching user boards an permissions @juliushaertl
|
- #3161 Reduce duplicate queries when fetching user boards an permissions @juliushaertl
|
||||||
- #3151 Always log generic exceptions @juliushaertl
|
- #3151 Always log generic exceptions @juliushaertl
|
||||||
- #3217 Move circle checks to a unified service and improve member checks @juliushaertl
|
- #3217 Move circle checks to a unified service and improve member checks @juliushaertl
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
- 🚀 Get your project organized
|
- 🚀 Get your project organized
|
||||||
|
|
||||||
</description>
|
</description>
|
||||||
<version>1.6.0-beta1</version>
|
<version>1.6.0</version>
|
||||||
<licence>agpl</licence>
|
<licence>agpl</licence>
|
||||||
<author>Julius Härtl</author>
|
<author>Julius Härtl</author>
|
||||||
<namespace>Deck</namespace>
|
<namespace>Deck</namespace>
|
||||||
|
|||||||
@@ -100,6 +100,9 @@ class ResourceProvider implements IProvider {
|
|||||||
if ($board->getOwner() === $user->getUID()) {
|
if ($board->getOwner() === $user->getUID()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if ($board->getAcl() === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return $this->permissionService->userCan($board->getAcl(), Acl::PERMISSION_READ, $user->getUID());
|
return $this->permissionService->userCan($board->getAcl(), Acl::PERMISSION_READ, $user->getUID());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -127,6 +127,9 @@ class ResourceProviderCard implements IProvider {
|
|||||||
if ($board->getOwner() === $user->getUID()) {
|
if ($board->getOwner() === $user->getUID()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if ($board->getAcl() === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return $this->permissionService->userCan($board->getAcl(), Acl::PERMISSION_READ, $user->getUID());
|
return $this->permissionService->userCan($board->getAcl(), Acl::PERMISSION_READ, $user->getUID());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,8 +28,10 @@ class Board extends RelationalEntity {
|
|||||||
protected $owner;
|
protected $owner;
|
||||||
protected $color;
|
protected $color;
|
||||||
protected $archived = false;
|
protected $archived = false;
|
||||||
protected $labels = [];
|
/** @var Label[]|null */
|
||||||
protected $acl = [];
|
protected $labels = null;
|
||||||
|
/** @var Acl[]|null */
|
||||||
|
protected $acl = null;
|
||||||
protected $permissions = [];
|
protected $permissions = [];
|
||||||
protected $users = [];
|
protected $users = [];
|
||||||
protected $shared;
|
protected $shared;
|
||||||
@@ -61,6 +63,10 @@ class Board extends RelationalEntity {
|
|||||||
if ($this->shared === -1) {
|
if ($this->shared === -1) {
|
||||||
unset($json['shared']);
|
unset($json['shared']);
|
||||||
}
|
}
|
||||||
|
// FIXME: Ideally the API responses should follow the internal data structure and return null if the labels/acls have not been fetched from the db
|
||||||
|
// however this would be a breaking change for consumers of the API
|
||||||
|
$json['acl'] = $this->acl ?? [];
|
||||||
|
$json['labels'] = $this->labels ?? [];
|
||||||
return $json;
|
return $json;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,21 +74,27 @@ class Board extends RelationalEntity {
|
|||||||
* @param Label[] $labels
|
* @param Label[] $labels
|
||||||
*/
|
*/
|
||||||
public function setLabels($labels) {
|
public function setLabels($labels) {
|
||||||
foreach ($labels as $l) {
|
$this->labels = $labels;
|
||||||
$this->labels[] = $l;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param Acl[] $acl
|
* @param Acl[] $acl
|
||||||
*/
|
*/
|
||||||
public function setAcl($acl) {
|
public function setAcl($acl) {
|
||||||
foreach ($acl as $a) {
|
$this->acl = $acl;
|
||||||
$this->acl[] = $a;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getETag() {
|
public function getETag() {
|
||||||
return md5((string)$this->getLastModified());
|
return md5((string)$this->getLastModified());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @returns Acl[]|null */
|
||||||
|
public function getAcl(): ?array {
|
||||||
|
return $this->acl;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @returns Label[]|null */
|
||||||
|
public function getLabels(): ?array {
|
||||||
|
return $this->labels;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,12 +25,14 @@ namespace OCA\Deck\Db;
|
|||||||
|
|
||||||
use OC\Cache\CappedMemoryCache;
|
use OC\Cache\CappedMemoryCache;
|
||||||
use OCP\AppFramework\Db\DoesNotExistException;
|
use OCP\AppFramework\Db\DoesNotExistException;
|
||||||
|
use OCP\AppFramework\Db\QBMapper;
|
||||||
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||||
use OCP\IDBConnection;
|
use OCP\IDBConnection;
|
||||||
use OCP\IUserManager;
|
use OCP\IUserManager;
|
||||||
use OCP\IGroupManager;
|
use OCP\IGroupManager;
|
||||||
use Psr\Log\LoggerInterface;
|
use Psr\Log\LoggerInterface;
|
||||||
|
|
||||||
class BoardMapper extends DeckMapper implements IPermissionMapper {
|
class BoardMapper extends QBMapper implements IPermissionMapper {
|
||||||
private $labelMapper;
|
private $labelMapper;
|
||||||
private $aclMapper;
|
private $aclMapper;
|
||||||
private $stackMapper;
|
private $stackMapper;
|
||||||
@@ -40,7 +42,10 @@ class BoardMapper extends DeckMapper implements IPermissionMapper {
|
|||||||
|
|
||||||
private $circlesEnabled;
|
private $circlesEnabled;
|
||||||
|
|
||||||
|
/** @var CappedMemoryCache */
|
||||||
private $userBoardCache;
|
private $userBoardCache;
|
||||||
|
/** @var CappedMemoryCache */
|
||||||
|
private $boardCache;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
IDBConnection $db,
|
IDBConnection $db,
|
||||||
@@ -60,7 +65,7 @@ class BoardMapper extends DeckMapper implements IPermissionMapper {
|
|||||||
$this->logger = $logger;
|
$this->logger = $logger;
|
||||||
|
|
||||||
$this->userBoardCache = new CappedMemoryCache();
|
$this->userBoardCache = new CappedMemoryCache();
|
||||||
|
$this->boardCache = new CappedMemoryCache();
|
||||||
|
|
||||||
$this->circlesEnabled = \OC::$server->getAppManager()->isEnabledForUser('circles');
|
$this->circlesEnabled = \OC::$server->getAppManager()->isEnabledForUser('circles');
|
||||||
}
|
}
|
||||||
@@ -70,28 +75,36 @@ class BoardMapper extends DeckMapper implements IPermissionMapper {
|
|||||||
* @param $id
|
* @param $id
|
||||||
* @param bool $withLabels
|
* @param bool $withLabels
|
||||||
* @param bool $withAcl
|
* @param bool $withAcl
|
||||||
* @return \OCP\AppFramework\Db\Entity
|
* @return Board
|
||||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||||
* @throws DoesNotExistException
|
* @throws DoesNotExistException
|
||||||
*/
|
*/
|
||||||
public function find($id, $withLabels = false, $withAcl = false) {
|
public function find($id, $withLabels = false, $withAcl = false): Board {
|
||||||
$sql = 'SELECT id, title, owner, color, archived, deleted_at, last_modified FROM `*PREFIX*deck_boards` ' .
|
if (!isset($this->boardCache[$id])) {
|
||||||
'WHERE `id` = ?';
|
$qb = $this->db->getQueryBuilder();
|
||||||
$board = $this->findEntity($sql, [$id]);
|
$qb->select('*')
|
||||||
|
->from('deck_boards')
|
||||||
|
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)))
|
||||||
|
->orderBy('id');
|
||||||
|
$this->boardCache[$id] = $this->findEntity($qb);
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME is this necessary? it was NOT done with the old mapper
|
||||||
|
// $this->mapOwner($board);
|
||||||
|
|
||||||
// Add labels
|
// Add labels
|
||||||
if ($withLabels) {
|
if ($withLabels && $this->boardCache[$id]->getLabels() === null) {
|
||||||
$labels = $this->labelMapper->findAll($id);
|
$labels = $this->labelMapper->findAll($id);
|
||||||
$board->setLabels($labels);
|
$this->boardCache[$id]->setLabels($labels);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add acl
|
// Add acl
|
||||||
if ($withAcl) {
|
if ($withAcl && $this->boardCache[$id]->getAcl() === null) {
|
||||||
$acl = $this->aclMapper->findAll($id);
|
$acl = $this->aclMapper->findAll($id);
|
||||||
$board->setAcl($acl);
|
$this->boardCache[$id]->setAcl($acl);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $board;
|
return $this->boardCache[$id];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findAllForUser(string $userId, ?int $since = null, bool $includeArchived = true, ?int $before = null,
|
public function findAllForUser(string $userId, ?int $since = null, bool $includeArchived = true, ?int $before = null,
|
||||||
@@ -105,6 +118,9 @@ class BoardMapper extends DeckMapper implements IPermissionMapper {
|
|||||||
$groupBoards = $this->findAllByGroups($userId, $groups, null, null, $since, $includeArchived, $before, $term);
|
$groupBoards = $this->findAllByGroups($userId, $groups, null, null, $since, $includeArchived, $before, $term);
|
||||||
$circleBoards = $this->findAllByCircles($userId, null, null, $since, $includeArchived, $before, $term);
|
$circleBoards = $this->findAllByCircles($userId, null, null, $since, $includeArchived, $before, $term);
|
||||||
$allBoards = array_unique(array_merge($userBoards, $groupBoards, $circleBoards));
|
$allBoards = array_unique(array_merge($userBoards, $groupBoards, $circleBoards));
|
||||||
|
foreach ($allBoards as $board) {
|
||||||
|
$this->boardCache[$board->getId()] = $board;
|
||||||
|
}
|
||||||
if ($useCache) {
|
if ($useCache) {
|
||||||
$this->userBoardCache[$userId] = $allBoards;
|
$this->userBoardCache[$userId] = $allBoards;
|
||||||
}
|
}
|
||||||
@@ -123,44 +139,89 @@ class BoardMapper extends DeckMapper implements IPermissionMapper {
|
|||||||
*/
|
*/
|
||||||
public function findAllByUser(string $userId, ?int $limit = null, ?int $offset = null, ?int $since = null,
|
public function findAllByUser(string $userId, ?int $limit = null, ?int $offset = null, ?int $since = null,
|
||||||
bool $includeArchived = true, ?int $before = null, ?string $term = null) {
|
bool $includeArchived = true, ?int $before = null, ?string $term = null) {
|
||||||
// FIXME: One moving to QBMapper we should allow filtering the boards probably by method chaining for additional where clauses
|
// FIXME this used to be a UNION to get boards owned by $userId and the user shares in one single query
|
||||||
$sql = 'SELECT id, title, owner, color, archived, deleted_at, 0 as shared, last_modified FROM `*PREFIX*deck_boards` WHERE owner = ?';
|
// Is it possible with the query builder?
|
||||||
$params = [$userId];
|
$qb = $this->db->getQueryBuilder();
|
||||||
|
$qb->select('id', 'title', 'owner', 'color', 'archived', 'deleted_at', 'last_modified')
|
||||||
|
// this does not work in MySQL/PostgreSQL
|
||||||
|
//->selectAlias('0', 'shared')
|
||||||
|
->from('deck_boards', 'b')
|
||||||
|
->where($qb->expr()->eq('owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
|
||||||
if (!$includeArchived) {
|
if (!$includeArchived) {
|
||||||
$sql .= ' AND NOT archived AND deleted_at = 0';
|
$qb->andWhere($qb->expr()->eq('archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
|
||||||
|
->andWhere($qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
|
||||||
}
|
}
|
||||||
if ($since !== null) {
|
if ($since !== null) {
|
||||||
$sql .= ' AND last_modified > ?';
|
$qb->andWhere($qb->expr()->gt('last_modified', $qb->createNamedParameter($since, IQueryBuilder::PARAM_INT)));
|
||||||
$params[] = $since;
|
|
||||||
}
|
}
|
||||||
if ($before !== null) {
|
if ($before !== null) {
|
||||||
$sql .= ' AND last_modified < ?';
|
$qb->andWhere($qb->expr()->lt('last_modified', $qb->createNamedParameter($before, IQueryBuilder::PARAM_INT)));
|
||||||
$params[] = $before;
|
|
||||||
}
|
}
|
||||||
if ($term !== null) {
|
if ($term !== null) {
|
||||||
$sql .= ' AND lower(title) LIKE ?';
|
$qb->andWhere(
|
||||||
$params[] = '%' . $term . '%';
|
$qb->expr()->iLike(
|
||||||
|
'title',
|
||||||
|
$qb->createNamedParameter(
|
||||||
|
'%' . $this->db->escapeLikeParameter($term) . '%',
|
||||||
|
IQueryBuilder::PARAM_STR
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
$sql .= ' UNION ' .
|
$qb->orderBy('b.id');
|
||||||
'SELECT boards.id, title, owner, color, archived, deleted_at, 1 as shared, last_modified FROM `*PREFIX*deck_boards` as boards ' .
|
if ($limit !== null) {
|
||||||
'JOIN `*PREFIX*deck_board_acl` as acl ON boards.id=acl.board_id WHERE acl.participant=? AND acl.type=? AND boards.owner != ?';
|
$qb->setMaxResults($limit);
|
||||||
array_push($params, $userId, Acl::PERMISSION_TYPE_USER, $userId);
|
}
|
||||||
|
if ($offset !== null) {
|
||||||
|
$qb->setFirstResult($offset);
|
||||||
|
}
|
||||||
|
$entries = $this->findEntities($qb);
|
||||||
|
foreach ($entries as $entry) {
|
||||||
|
$entry->setShared(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// shared with user
|
||||||
|
$qb->resetQueryParts();
|
||||||
|
$qb->select('b.id', 'title', 'owner', 'color', 'archived', 'deleted_at', 'last_modified')
|
||||||
|
//->selectAlias('1', 'shared')
|
||||||
|
->from('deck_boards', 'b')
|
||||||
|
->innerJoin('b', 'deck_board_acl', 'acl', $qb->expr()->eq('b.id', 'acl.board_id'))
|
||||||
|
->where($qb->expr()->eq('acl.participant', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
|
||||||
|
->andWhere($qb->expr()->eq('acl.type', $qb->createNamedParameter(Acl::PERMISSION_TYPE_USER, IQueryBuilder::PARAM_INT)))
|
||||||
|
->andWhere($qb->expr()->neq('b.owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
|
||||||
if (!$includeArchived) {
|
if (!$includeArchived) {
|
||||||
$sql .= ' AND NOT archived AND deleted_at = 0';
|
$qb->andWhere($qb->expr()->eq('archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
|
||||||
|
->andWhere($qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
|
||||||
}
|
}
|
||||||
if ($since !== null) {
|
if ($since !== null) {
|
||||||
$sql .= ' AND last_modified > ?';
|
$qb->andWhere($qb->expr()->gt('last_modified', $qb->createNamedParameter($since, IQueryBuilder::PARAM_INT)));
|
||||||
$params[] = $since;
|
|
||||||
}
|
}
|
||||||
if ($before !== null) {
|
if ($before !== null) {
|
||||||
$sql .= ' AND last_modified < ?';
|
$qb->andWhere($qb->expr()->lt('last_modified', $qb->createNamedParameter($before, IQueryBuilder::PARAM_INT)));
|
||||||
$params[] = $before;
|
|
||||||
}
|
}
|
||||||
if ($term !== null) {
|
if ($term !== null) {
|
||||||
$sql .= ' AND lower(title) LIKE ?';
|
$qb->andWhere(
|
||||||
$params[] = '%' . $term . '%';
|
$qb->expr()->iLike(
|
||||||
|
'title',
|
||||||
|
$qb->createNamedParameter(
|
||||||
|
'%' . $this->db->escapeLikeParameter($term) . '%',
|
||||||
|
IQueryBuilder::PARAM_STR
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
$entries = $this->findEntities($sql, $params, $limit, $offset);
|
$qb->orderBy('b.id');
|
||||||
|
if ($limit !== null) {
|
||||||
|
$qb->setMaxResults($limit);
|
||||||
|
}
|
||||||
|
if ($offset !== null) {
|
||||||
|
$qb->setFirstResult($offset);
|
||||||
|
}
|
||||||
|
$sharedEntries = $this->findEntities($qb);
|
||||||
|
foreach ($sharedEntries as $entry) {
|
||||||
|
$entry->setShared(1);
|
||||||
|
}
|
||||||
|
$entries = array_merge($entries, $sharedEntries);
|
||||||
/* @var Board $entry */
|
/* @var Board $entry */
|
||||||
foreach ($entries as $entry) {
|
foreach ($entries as $entry) {
|
||||||
$acl = $this->aclMapper->findAll($entry->id);
|
$acl = $this->aclMapper->findAll($entry->id);
|
||||||
@@ -169,9 +230,19 @@ class BoardMapper extends DeckMapper implements IPermissionMapper {
|
|||||||
return $entries;
|
return $entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findAllByOwner(string $userId, int $limit = null, int $offset = null) {
|
public function findAllByOwner(string $userId, ?int $limit = null, ?int $offset = null) {
|
||||||
$sql = 'SELECT * FROM `*PREFIX*deck_boards` WHERE owner = ?';
|
$qb = $this->db->getQueryBuilder();
|
||||||
return $this->findEntities($sql, [$userId], $limit, $offset);
|
$qb->select('*')
|
||||||
|
->from('deck_boards')
|
||||||
|
->where($qb->expr()->eq('owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)))
|
||||||
|
->orderBy('id');
|
||||||
|
if ($limit !== null) {
|
||||||
|
$qb->setMaxResults($limit);
|
||||||
|
}
|
||||||
|
if ($offset !== null) {
|
||||||
|
$qb->setFirstResult($offset);
|
||||||
|
}
|
||||||
|
return $this->findEntities($qb);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -188,33 +259,52 @@ class BoardMapper extends DeckMapper implements IPermissionMapper {
|
|||||||
if (count($groups) <= 0) {
|
if (count($groups) <= 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
$sql = 'SELECT boards.id, title, owner, color, archived, deleted_at, 2 as shared, last_modified FROM `*PREFIX*deck_boards` as boards ' .
|
$qb = $this->db->getQueryBuilder();
|
||||||
'INNER JOIN `*PREFIX*deck_board_acl` as acl ON boards.id=acl.board_id WHERE owner != ? AND type=? AND (';
|
$qb->select('b.id', 'title', 'owner', 'color', 'archived', 'deleted_at', 'last_modified')
|
||||||
$params = [$userId, Acl::PERMISSION_TYPE_GROUP];
|
//->selectAlias('2', 'shared')
|
||||||
|
->from('deck_boards', 'b')
|
||||||
|
->innerJoin('b', 'deck_board_acl', 'acl', $qb->expr()->eq('b.id', 'acl.board_id'))
|
||||||
|
->where($qb->expr()->eq('acl.type', $qb->createNamedParameter(Acl::PERMISSION_TYPE_GROUP, IQueryBuilder::PARAM_INT)))
|
||||||
|
->andWhere($qb->expr()->neq('b.owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
|
||||||
|
$or = $qb->expr()->orx();
|
||||||
for ($i = 0, $iMax = count($groups); $i < $iMax; $i++) {
|
for ($i = 0, $iMax = count($groups); $i < $iMax; $i++) {
|
||||||
$sql .= 'acl.participant = ? ';
|
$or->add(
|
||||||
if (count($groups) > 1 && $i < count($groups) - 1) {
|
$qb->expr()->eq('acl.participant', $qb->createNamedParameter($groups[$i], IQueryBuilder::PARAM_STR))
|
||||||
$sql .= ' OR ';
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$sql .= ')';
|
$qb->andWhere($or);
|
||||||
array_push($params, ...$groups);
|
|
||||||
if (!$includeArchived) {
|
if (!$includeArchived) {
|
||||||
$sql .= ' AND NOT archived AND deleted_at = 0';
|
$qb->andWhere($qb->expr()->eq('archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
|
||||||
|
->andWhere($qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
|
||||||
}
|
}
|
||||||
if ($since !== null) {
|
if ($since !== null) {
|
||||||
$sql .= ' AND last_modified > ?';
|
$qb->andWhere($qb->expr()->gt('last_modified', $qb->createNamedParameter($since, IQueryBuilder::PARAM_INT)));
|
||||||
$params[] = $since;
|
|
||||||
}
|
}
|
||||||
if ($before !== null) {
|
if ($before !== null) {
|
||||||
$sql .= ' AND last_modified < ?';
|
$qb->andWhere($qb->expr()->lt('last_modified', $qb->createNamedParameter($before, IQueryBuilder::PARAM_INT)));
|
||||||
$params[] = $before;
|
|
||||||
}
|
}
|
||||||
if ($term !== null) {
|
if ($term !== null) {
|
||||||
$sql .= ' AND lower(title) LIKE ?';
|
$qb->andWhere(
|
||||||
$params[] = '%' . $term . '%';
|
$qb->expr()->iLike(
|
||||||
|
'title',
|
||||||
|
$qb->createNamedParameter(
|
||||||
|
'%' . $this->db->escapeLikeParameter($term) . '%',
|
||||||
|
IQueryBuilder::PARAM_STR
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$qb->orderBy('b.id');
|
||||||
|
if ($limit !== null) {
|
||||||
|
$qb->setMaxResults($limit);
|
||||||
|
}
|
||||||
|
if ($offset !== null) {
|
||||||
|
$qb->setFirstResult($offset);
|
||||||
|
}
|
||||||
|
$entries = $this->findEntities($qb);
|
||||||
|
foreach ($entries as $entry) {
|
||||||
|
$entry->setShared(2);
|
||||||
}
|
}
|
||||||
$entries = $this->findEntities($sql, $params, $limit, $offset);
|
|
||||||
/* @var Board $entry */
|
/* @var Board $entry */
|
||||||
foreach ($entries as $entry) {
|
foreach ($entries as $entry) {
|
||||||
$acl = $this->aclMapper->findAll($entry->id);
|
$acl = $this->aclMapper->findAll($entry->id);
|
||||||
@@ -235,33 +325,52 @@ class BoardMapper extends DeckMapper implements IPermissionMapper {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
$sql = 'SELECT boards.id, title, owner, color, archived, deleted_at, 2 as shared, last_modified FROM `*PREFIX*deck_boards` as boards ' .
|
$qb = $this->db->getQueryBuilder();
|
||||||
'INNER JOIN `*PREFIX*deck_board_acl` as acl ON boards.id=acl.board_id WHERE owner != ? AND type=? AND (';
|
$qb->select('b.id', 'title', 'owner', 'color', 'archived', 'deleted_at', 'last_modified')
|
||||||
$params = [$userId, Acl::PERMISSION_TYPE_CIRCLE];
|
//->selectAlias('2', 'shared')
|
||||||
|
->from('deck_boards', 'b')
|
||||||
|
->innerJoin('b', 'deck_board_acl', 'acl', $qb->expr()->eq('b.id', 'acl.board_id'))
|
||||||
|
->where($qb->expr()->eq('acl.type', $qb->createNamedParameter(Acl::PERMISSION_TYPE_CIRCLE, IQueryBuilder::PARAM_INT)))
|
||||||
|
->andWhere($qb->expr()->neq('b.owner', $qb->createNamedParameter($userId, IQueryBuilder::PARAM_STR)));
|
||||||
|
$or = $qb->expr()->orx();
|
||||||
for ($i = 0, $iMax = count($circles); $i < $iMax; $i++) {
|
for ($i = 0, $iMax = count($circles); $i < $iMax; $i++) {
|
||||||
$sql .= 'acl.participant = ? ';
|
$or->add(
|
||||||
if (count($circles) > 1 && $i < count($circles) - 1) {
|
$qb->expr()->eq('acl.participant', $qb->createNamedParameter($circles[$i], IQueryBuilder::PARAM_STR))
|
||||||
$sql .= ' OR ';
|
);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$sql .= ')';
|
$qb->andWhere($or);
|
||||||
array_push($params, ...$circles);
|
|
||||||
if (!$includeArchived) {
|
if (!$includeArchived) {
|
||||||
$sql .= ' AND NOT archived AND deleted_at = 0';
|
$qb->andWhere($qb->expr()->eq('archived', $qb->createNamedParameter(false, IQueryBuilder::PARAM_BOOL)))
|
||||||
|
->andWhere($qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
|
||||||
}
|
}
|
||||||
if ($since !== null) {
|
if ($since !== null) {
|
||||||
$sql .= ' AND last_modified > ?';
|
$qb->andWhere($qb->expr()->gt('last_modified', $qb->createNamedParameter($since, IQueryBuilder::PARAM_INT)));
|
||||||
$params[] = $since;
|
|
||||||
}
|
}
|
||||||
if ($before !== null) {
|
if ($before !== null) {
|
||||||
$sql .= ' AND last_modified < ?';
|
$qb->andWhere($qb->expr()->lt('last_modified', $qb->createNamedParameter($before, IQueryBuilder::PARAM_INT)));
|
||||||
$params[] = $before;
|
|
||||||
}
|
}
|
||||||
if ($term !== null) {
|
if ($term !== null) {
|
||||||
$sql .= ' AND lower(title) LIKE ?';
|
$qb->andWhere(
|
||||||
$params[] = '%' . $term . '%';
|
$qb->expr()->iLike(
|
||||||
|
'title',
|
||||||
|
$qb->createNamedParameter(
|
||||||
|
'%' . $this->db->escapeLikeParameter($term) . '%',
|
||||||
|
IQueryBuilder::PARAM_STR
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$qb->orderBy('b.id');
|
||||||
|
if ($limit !== null) {
|
||||||
|
$qb->setMaxResults($limit);
|
||||||
|
}
|
||||||
|
if ($offset !== null) {
|
||||||
|
$qb->setFirstResult($offset);
|
||||||
|
}
|
||||||
|
$entries = $this->findEntities($qb);
|
||||||
|
foreach ($entries as $entry) {
|
||||||
|
$entry->setShared(2);
|
||||||
}
|
}
|
||||||
$entries = $this->findEntities($sql, $params, $limit, $offset);
|
|
||||||
/* @var Board $entry */
|
/* @var Board $entry */
|
||||||
foreach ($entries as $entry) {
|
foreach ($entries as $entry) {
|
||||||
$acl = $this->aclMapper->findAll($entry->id);
|
$acl = $this->aclMapper->findAll($entry->id);
|
||||||
@@ -270,21 +379,26 @@ class BoardMapper extends DeckMapper implements IPermissionMapper {
|
|||||||
return $entries;
|
return $entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findAll() {
|
public function findAll(): array {
|
||||||
$sql = 'SELECT id from *PREFIX*deck_boards;';
|
$qb = $this->db->getQueryBuilder();
|
||||||
return $this->findEntities($sql);
|
$qb->select('id')
|
||||||
|
->from('deck_boards');
|
||||||
|
return $this->findEntities($qb);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function findToDelete() {
|
public function findToDelete() {
|
||||||
// add buffer of 5 min
|
// add buffer of 5 min
|
||||||
$timeLimit = time() - (60 * 5);
|
$timeLimit = time() - (60 * 5);
|
||||||
$sql = 'SELECT id, title, owner, color, archived, deleted_at, last_modified FROM `*PREFIX*deck_boards` ' .
|
$qb = $this->db->getQueryBuilder();
|
||||||
'WHERE `deleted_at` > 0 AND `deleted_at` < ?';
|
$qb->select('id', 'title', 'owner', 'color', 'archived', 'deleted_at', 'last_modified')
|
||||||
return $this->findEntities($sql, [$timeLimit]);
|
->from('deck_boards')
|
||||||
|
->where($qb->expr()->gt('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)))
|
||||||
|
->andWhere($qb->expr()->lt('deleted_at', $qb->createNamedParameter($timeLimit, IQueryBuilder::PARAM_INT)));
|
||||||
|
return $this->findEntities($qb);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function delete(/** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
|
public function delete(/** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
|
||||||
\OCP\AppFramework\Db\Entity $entity) {
|
\OCP\AppFramework\Db\Entity $entity): \OCP\AppFramework\Db\Entity {
|
||||||
// delete acl
|
// delete acl
|
||||||
$acl = $this->aclMapper->findAll($entity->getId());
|
$acl = $this->aclMapper->findAll($entity->getId());
|
||||||
foreach ($acl as $item) {
|
foreach ($acl as $item) {
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ use OCA\Deck\Search\Query\SearchQuery;
|
|||||||
use OCP\AppFramework\Db\Entity;
|
use OCP\AppFramework\Db\Entity;
|
||||||
use OCP\AppFramework\Db\QBMapper;
|
use OCP\AppFramework\Db\QBMapper;
|
||||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||||
|
use OCP\ICache;
|
||||||
|
use OCP\ICacheFactory;
|
||||||
use OCP\IDBConnection;
|
use OCP\IDBConnection;
|
||||||
use OCP\IGroupManager;
|
use OCP\IGroupManager;
|
||||||
use OCP\IUser;
|
use OCP\IUser;
|
||||||
@@ -46,6 +48,8 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
|||||||
private $groupManager;
|
private $groupManager;
|
||||||
/** @var IManager */
|
/** @var IManager */
|
||||||
private $notificationManager;
|
private $notificationManager;
|
||||||
|
/** @var ICache */
|
||||||
|
private $cache;
|
||||||
private $databaseType;
|
private $databaseType;
|
||||||
private $database4ByteSupport;
|
private $database4ByteSupport;
|
||||||
|
|
||||||
@@ -55,6 +59,7 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
|||||||
IUserManager $userManager,
|
IUserManager $userManager,
|
||||||
IGroupManager $groupManager,
|
IGroupManager $groupManager,
|
||||||
IManager $notificationManager,
|
IManager $notificationManager,
|
||||||
|
ICacheFactory $cacheFactory,
|
||||||
$databaseType = 'sqlite3',
|
$databaseType = 'sqlite3',
|
||||||
$database4ByteSupport = true
|
$database4ByteSupport = true
|
||||||
) {
|
) {
|
||||||
@@ -63,6 +68,7 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
|||||||
$this->userManager = $userManager;
|
$this->userManager = $userManager;
|
||||||
$this->groupManager = $groupManager;
|
$this->groupManager = $groupManager;
|
||||||
$this->notificationManager = $notificationManager;
|
$this->notificationManager = $notificationManager;
|
||||||
|
$this->cache = $cacheFactory->createDistributed('deck-cardMapper');
|
||||||
$this->databaseType = $databaseType;
|
$this->databaseType = $databaseType;
|
||||||
$this->database4ByteSupport = $database4ByteSupport;
|
$this->database4ByteSupport = $database4ByteSupport;
|
||||||
}
|
}
|
||||||
@@ -75,7 +81,9 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
|||||||
$description = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $entity->getDescription());
|
$description = preg_replace('/[\x{10000}-\x{10FFFF}]/u', "\xEF\xBF\xBD", $entity->getDescription());
|
||||||
$entity->setDescription($description);
|
$entity->setDescription($description);
|
||||||
}
|
}
|
||||||
return parent::insert($entity);
|
$entity = parent::insert($entity);
|
||||||
|
$this->cache->remove('findBoardId:' . $entity->getId());
|
||||||
|
return $entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function update(Entity $entity, $updateModified = true): Entity {
|
public function update(Entity $entity, $updateModified = true): Entity {
|
||||||
@@ -107,6 +115,10 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
|||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Invalidate cache when the card may be moved to a different board
|
||||||
|
if (isset($updatedFields['stackId'])) {
|
||||||
|
$this->cache->remove('findBoardId:' . $entity->getId());
|
||||||
|
}
|
||||||
return parent::update($entity);
|
return parent::update($entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -506,9 +518,8 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function delete(Entity $entity): Entity {
|
public function delete(Entity $entity): Entity {
|
||||||
// delete assigned labels
|
|
||||||
$this->labelMapper->deleteLabelAssignmentsForCard($entity->getId());
|
$this->labelMapper->deleteLabelAssignmentsForCard($entity->getId());
|
||||||
// delete card
|
$this->cache->remove('findBoardId:' . $entity->getId());
|
||||||
return parent::delete($entity);
|
return parent::delete($entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -547,11 +558,22 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function findBoardId($id): ?int {
|
public function findBoardId($id): ?int {
|
||||||
$sql = 'SELECT id FROM `*PREFIX*deck_boards` WHERE `id` IN (SELECT board_id FROM `*PREFIX*deck_stacks` WHERE id IN (SELECT stack_id FROM `*PREFIX*deck_cards` WHERE id = ?))';
|
$result = $this->cache->get('findBoardId:' . $id);
|
||||||
$stmt = $this->db->prepare($sql);
|
if ($result === null) {
|
||||||
$stmt->bindParam(1, $id, \PDO::PARAM_INT);
|
try {
|
||||||
$stmt->execute();
|
$qb = $this->db->getQueryBuilder();
|
||||||
return $stmt->fetchColumn() ?? null;
|
$qb->select('board_id')
|
||||||
|
->from('deck_stacks', 's')
|
||||||
|
->innerJoin('s', 'deck_cards', 'c', 'c.stack_id = s.id')
|
||||||
|
->where($qb->expr()->eq('c.id', $qb->createNamedParameter($id)));
|
||||||
|
$queryResult = $qb->executeQuery();
|
||||||
|
$result = $queryResult->fetchOne();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$result = false;
|
||||||
|
}
|
||||||
|
$this->cache->set('findBoardId:' . $id, $result);
|
||||||
|
}
|
||||||
|
return $result !== false ? $result : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function mapOwner(Card &$card) {
|
public function mapOwner(Card &$card) {
|
||||||
|
|||||||
@@ -139,6 +139,6 @@ class DeckProvider implements IProvider {
|
|||||||
$cardTimestamps = array_map(function (Card $card) {
|
$cardTimestamps = array_map(function (Card $card) {
|
||||||
return $card->getLastModified();
|
return $card->getLastModified();
|
||||||
}, $cards);
|
}, $cards);
|
||||||
return (min($boardTimestamps) ?: '') . '|' . (min($cardTimestamps) ?: '');
|
return (count($boardTimestamps) > 0 ? min($boardTimestamps) : '') . '|' . (count($cardTimestamps) > 0 ? min($cardTimestamps) : '');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -179,9 +179,11 @@ class BoardService {
|
|||||||
/** @var Board $board */
|
/** @var Board $board */
|
||||||
$board = $this->boardMapper->find($boardId, true, true);
|
$board = $this->boardMapper->find($boardId, true, true);
|
||||||
$this->boardMapper->mapOwner($board);
|
$this->boardMapper->mapOwner($board);
|
||||||
foreach ($board->getAcl() as &$acl) {
|
if ($board->getAcl() !== null) {
|
||||||
if ($acl !== null) {
|
foreach ($board->getAcl() as $acl) {
|
||||||
$this->boardMapper->mapAcl($acl);
|
if ($acl !== null) {
|
||||||
|
$this->boardMapper->mapAcl($acl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$permissions = $this->permissionService->matchPermissions($board);
|
$permissions = $this->permissionService->matchPermissions($board);
|
||||||
|
|||||||
@@ -117,7 +117,7 @@ class PermissionService {
|
|||||||
*/
|
*/
|
||||||
public function matchPermissions(Board $board) {
|
public function matchPermissions(Board $board) {
|
||||||
$owner = $this->userIsBoardOwner($board->getId());
|
$owner = $this->userIsBoardOwner($board->getId());
|
||||||
$acls = $board->getAcl();
|
$acls = $board->getAcl() ?? [];
|
||||||
return [
|
return [
|
||||||
Acl::PERMISSION_READ => $owner || $this->userCan($acls, Acl::PERMISSION_READ),
|
Acl::PERMISSION_READ => $owner || $this->userCan($acls, Acl::PERMISSION_READ),
|
||||||
Acl::PERMISSION_EDIT => $owner || $this->userCan($acls, Acl::PERMISSION_EDIT),
|
Acl::PERMISSION_EDIT => $owner || $this->userCan($acls, Acl::PERMISSION_EDIT),
|
||||||
@@ -155,7 +155,7 @@ class PermissionService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$acls = $this->getBoard($boardId)->getAcl();
|
$acls = $this->getBoard($boardId)->getAcl() ?? [];
|
||||||
$result = $this->userCan($acls, $permission, $userId);
|
$result = $this->userCan($acls, $permission, $userId);
|
||||||
if ($result) {
|
if ($result) {
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "deck",
|
"name": "deck",
|
||||||
"description": "",
|
"description": "",
|
||||||
"version": "1.6.0-beta1",
|
"version": "1.6.0",
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
{
|
||||||
"name": "Julius Härtl",
|
"name": "Julius Härtl",
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<AttachmentDragAndDrop :card-id="cardId" class="drop-upload--sidebar">
|
<AttachmentDragAndDrop :card-id="cardId" class="drop-upload--sidebar">
|
||||||
<div class="button-group" v-if="!isReadOnly">
|
<div v-if="!isReadOnly" class="button-group">
|
||||||
<button class="icon-upload" @click="uploadNewFile()">
|
<button class="icon-upload" @click="uploadNewFile()">
|
||||||
{{ t('deck', 'Upload new files') }}
|
{{ t('deck', 'Upload new files') }}
|
||||||
</button>
|
</button>
|
||||||
@@ -79,6 +79,12 @@
|
|||||||
<ActionLink v-if="attachment.extendedData.fileid" icon="icon-folder" :href="internalLink(attachment)">
|
<ActionLink v-if="attachment.extendedData.fileid" icon="icon-folder" :href="internalLink(attachment)">
|
||||||
{{ t('deck', 'Show in Files') }}
|
{{ t('deck', 'Show in Files') }}
|
||||||
</ActionLink>
|
</ActionLink>
|
||||||
|
<ActionLink v-if="attachment.extendedData.fileid"
|
||||||
|
icon="icon-download"
|
||||||
|
:href="downloadLink(attachment)"
|
||||||
|
download>
|
||||||
|
{{ t('deck', 'Download') }}
|
||||||
|
</ActionLink>
|
||||||
<ActionButton v-if="attachment.extendedData.fileid && !isReadOnly" icon="icon-delete" @click="unshareAttachment(attachment)">
|
<ActionButton v-if="attachment.extendedData.fileid && !isReadOnly" icon="icon-delete" @click="unshareAttachment(attachment)">
|
||||||
{{ t('deck', 'Remove attachment') }}
|
{{ t('deck', 'Remove attachment') }}
|
||||||
</ActionButton>
|
</ActionButton>
|
||||||
@@ -101,7 +107,8 @@ import { Actions, ActionButton, ActionLink } from '@nextcloud/vue'
|
|||||||
import AttachmentDragAndDrop from '../AttachmentDragAndDrop'
|
import AttachmentDragAndDrop from '../AttachmentDragAndDrop'
|
||||||
import relativeDate from '../../mixins/relativeDate'
|
import relativeDate from '../../mixins/relativeDate'
|
||||||
import { formatFileSize } from '@nextcloud/files'
|
import { formatFileSize } from '@nextcloud/files'
|
||||||
import { generateUrl, generateOcsUrl } from '@nextcloud/router'
|
import { getCurrentUser } from '@nextcloud/auth'
|
||||||
|
import { generateUrl, generateOcsUrl, generateRemoteUrl } from '@nextcloud/router'
|
||||||
import { mapState } from 'vuex'
|
import { mapState } from 'vuex'
|
||||||
import { loadState } from '@nextcloud/initial-state'
|
import { loadState } from '@nextcloud/initial-state'
|
||||||
import attachmentUpload from '../../mixins/attachmentUpload'
|
import attachmentUpload from '../../mixins/attachmentUpload'
|
||||||
@@ -174,6 +181,9 @@ export default {
|
|||||||
internalLink() {
|
internalLink() {
|
||||||
return (attachment) => generateUrl('/f/' + attachment.extendedData.fileid)
|
return (attachment) => generateUrl('/f/' + attachment.extendedData.fileid)
|
||||||
},
|
},
|
||||||
|
downloadLink() {
|
||||||
|
return (attachment) => generateRemoteUrl(`dav/files/${getCurrentUser().uid}/${attachment.extendedData.path}`)
|
||||||
|
},
|
||||||
formattedFileSize() {
|
formattedFileSize() {
|
||||||
return (filesize) => formatFileSize(filesize)
|
return (filesize) => formatFileSize(filesize)
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
ref="markdownEditor"
|
ref="markdownEditor"
|
||||||
v-model="description"
|
v-model="description"
|
||||||
:configs="mdeConfig"
|
:configs="mdeConfig"
|
||||||
@input="updateDescription"
|
@update:modelValue="updateDescription"
|
||||||
@blur="saveDescription" />
|
@blur="saveDescription" />
|
||||||
|
|
||||||
<Modal v-if="modalShow" :title="t('deck', 'Choose attachment')" @close="modalShow=false">
|
<Modal v-if="modalShow" :title="t('deck', 'Choose attachment')" @close="modalShow=false">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<files psalm-version="4.9.3@4c262932602b9bbab5020863d1eb22d49de0dbf4">
|
<files psalm-version="4.11.2@6fba5eb554f9507b72932f9c75533d8af593688d">
|
||||||
<file src="lib/Activity/ActivityManager.php">
|
<file src="lib/Activity/ActivityManager.php">
|
||||||
<TypeDoesNotContainType occurrences="1">
|
<TypeDoesNotContainType occurrences="1">
|
||||||
<code>$message !== null</code>
|
<code>$message !== null</code>
|
||||||
@@ -116,6 +116,14 @@
|
|||||||
<ParamNameMismatch occurrences="1">
|
<ParamNameMismatch occurrences="1">
|
||||||
<code>$boardId</code>
|
<code>$boardId</code>
|
||||||
</ParamNameMismatch>
|
</ParamNameMismatch>
|
||||||
|
<TypeDoesNotContainType occurrences="6">
|
||||||
|
<code>$limit !== null</code>
|
||||||
|
<code>$limit !== null</code>
|
||||||
|
<code>$limit !== null</code>
|
||||||
|
<code>$offset !== null</code>
|
||||||
|
<code>$offset !== null</code>
|
||||||
|
<code>$offset !== null</code>
|
||||||
|
</TypeDoesNotContainType>
|
||||||
<UndefinedClass occurrences="2">
|
<UndefinedClass occurrences="2">
|
||||||
<code>\OCA\Circles\Api\v1\Circles</code>
|
<code>\OCA\Circles\Api\v1\Circles</code>
|
||||||
<code>\OCA\Circles\Api\v1\Circles</code>
|
<code>\OCA\Circles\Api\v1\Circles</code>
|
||||||
|
|||||||
@@ -161,6 +161,7 @@ class BoardMapperTest extends MapperTestUtility {
|
|||||||
$actual = $this->boardMapper->find($this->boards[0]->getId(), true, false);
|
$actual = $this->boardMapper->find($this->boards[0]->getId(), true, false);
|
||||||
/** @var Board $expected */
|
/** @var Board $expected */
|
||||||
$expected = $this->boards[0];
|
$expected = $this->boards[0];
|
||||||
|
$expected->setLabels([]);
|
||||||
$this->assertEquals($expected->getLabels(), $actual->getLabels());
|
$this->assertEquals($expected->getLabels(), $actual->getLabels());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,29 @@ class BoardTest extends TestCase {
|
|||||||
], $board->jsonSerialize());
|
], $board->jsonSerialize());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testUnfetchedValues() {
|
||||||
|
$board = $this->createBoard();
|
||||||
|
$board->setUsers(['user1', 'user2']);
|
||||||
|
self::assertNull($board->getAcl());
|
||||||
|
self::assertNull($board->getLabels());
|
||||||
|
$this->assertEquals([
|
||||||
|
'id' => 1,
|
||||||
|
'title' => "My Board",
|
||||||
|
'owner' => "admin",
|
||||||
|
'color' => "000000",
|
||||||
|
'labels' => [],
|
||||||
|
'permissions' => [],
|
||||||
|
'stacks' => [],
|
||||||
|
'deletedAt' => 0,
|
||||||
|
'lastModified' => 0,
|
||||||
|
'acl' => [],
|
||||||
|
'archived' => false,
|
||||||
|
'users' => ['user1', 'user2'],
|
||||||
|
'settings' => [],
|
||||||
|
'ETag' => $board->getETag(),
|
||||||
|
], $board->jsonSerialize());
|
||||||
|
}
|
||||||
|
|
||||||
public function testSetLabels() {
|
public function testSetLabels() {
|
||||||
$board = $this->createBoard();
|
$board = $this->createBoard();
|
||||||
$board->setLabels(["foo", "bar"]);
|
$board->setLabels(["foo", "bar"]);
|
||||||
|
|||||||
Reference in New Issue
Block a user