Compare commits

..

19 Commits

Author SHA1 Message Date
Julius Härtl
ccffe1094e Bump version to 1.3.2
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-04-07 18:47:23 +02:00
Julius Härtl
b698c42a1d Merge pull request #2946 from nextcloud/backport/2793/stable1.3
[stable1.3] fix desc save bug
2021-04-07 18:41:22 +02:00
Jakob Röhrl
454a9115d8 fix desc save bug
Signed-off-by: Jakob Röhrl <jakob.roehrl@web.de>
2021-04-07 08:36:54 +02:00
Julius Härtl
e359c0582a Merge pull request #2924 from nextcloud/backport/2923/stable1.3
[stable1.3] Avoid reusing the existing route object to make navigation work properly
2021-03-29 14:13:24 +02:00
Julius Härtl
3d912f4af1 Avoid reusing the existing route object to make navigation work properly
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-03-29 12:00:28 +00:00
Julius Härtl
6e60c5ab4b Merge pull request #2880 from nextcloud/backport/2871/stable1.3
[stable1.3] Only extract additional attributes from query when doing a raw search
2021-03-11 15:39:26 +01:00
Julius Härtl
770d34814d Only extract additional attributes from query when not using the entity mapping for the result
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-03-11 14:20:01 +00:00
Julius Härtl
49569150cb Merge pull request #2869 from nextcloud/backport/2857/stable1.3
[stable1.3] Don't close tempfile as it is already done
2021-03-08 10:39:08 +01:00
Julius Härtl
1beed6be61 No need to close the temp file as the View is already taking care of it
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-03-08 09:25:51 +00:00
Julius Härtl
fc85a09f69 Bump version to 1.3.1
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-03-05 15:51:14 +01:00
Julius Härtl
8719f7a592 Merge pull request #2859 from nextcloud/backport/2823/stable1.3
[stable1.3] Properly pass the user to fetch circles when calling through occ
2021-03-05 15:49:27 +01:00
Julius Härtl
bf002b773f Properly pass the user to fetch circles when calling through occ
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-03-05 14:40:18 +00:00
Julius Härtl
4e2577ce53 Merge pull request #2849 from nextcloud/backport/2847/stable1.3
[stable1.3] Switch to Content-Disposition attachment and check for sane mimetypes
2021-03-04 11:18:00 +01:00
Julius Härtl
11e642af56 Switch to Content-Disposition attachment and check for sane mimetypes
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-03-04 07:25:55 +00:00
Julius Härtl
0956e6b2f6 Merge pull request #2844 from nextcloud/backport/2843/stable1.3 2021-03-03 09:19:57 +01:00
Julius Härtl
217c1b3104 Use proper debounce on the sharing input
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-03-03 08:07:32 +00:00
Julius Härtl
f5452a63be Search by mail on the board sharing input
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-03-03 08:07:32 +00:00
Julius Härtl
7162f1185d Merge pull request #2827 from nextcloud/backport/2822/stable1.3
[stable1.3] Also include /apps/spreed urls in the listener for loading the talk integration
2021-02-25 15:12:44 +01:00
Julius Härtl
9d5ebf8877 Also include /apps/spreed urls in the listener for loading the talk integration
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2021-02-24 18:14:14 +00:00
13 changed files with 69 additions and 117 deletions

View File

@@ -1,6 +1,25 @@
# Changelog
All notable changes to this project will be documented in this file.
## 1.3.2 - 2021-04-07
### Fixed
* [#2869](https://github.com/nextcloud/deck/pull/2869) Don't close tempfile as it is already done
* [#2880](https://github.com/nextcloud/deck/pull/2880) Only extract additional attributes from query when doing a raw search
* [#2924](https://github.com/nextcloud/deck/pull/2924) Avoid reusing the existing route object to make navigation work properly
* [#2946](https://github.com/nextcloud/deck/pull/2946) Fix bug when saving the description
## 1.3.1 - 2021-03-05
### Fixed
* [#2827](https://github.com/nextcloud/deck/pull/2827) Also include /apps/spreed urls in the listener for loading the talk integration
* [#2844](https://github.com/nextcloud/deck/pull/2844) Search by mail on the board sharing input
* [#2849](https://github.com/nextcloud/deck/pull/2849) Switch to Content-Disposition attachment and check for sane mimetypes
* [#2859](https://github.com/nextcloud/deck/pull/2859) Properly pass the user to fetch circles when calling through occ
## 1.3.0 - 2021-02-20
### Added

View File

@@ -17,7 +17,7 @@
- 🚀 Get your project organized
</description>
<version>1.3.0</version>
<version>1.3.2</version>
<licence>agpl</licence>
<author>Julius Härtl</author>
<namespace>Deck</namespace>

View File

@@ -169,7 +169,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

@@ -149,8 +149,7 @@ class CardMapper extends QBMapper implements IPermissionMapper {
public function queryCardsByBoards(array $boardIds): IQueryBuilder {
$qb = $this->db->getQueryBuilder();
$qb->select('c.*', 's.board_id')
->selectAlias('s.title', 'stack_title')
$qb->select('c.*')
->from('deck_cards', 'c')
->innerJoin('c', 'deck_stacks', 's', $qb->expr()->eq('s.id', 'c.stack_id'))
->andWhere($qb->expr()->in('s.board_id', $qb->createNamedParameter($boardIds, IQueryBuilder::PARAM_INT_ARRAY)));
@@ -281,7 +280,9 @@ class CardMapper extends QBMapper implements IPermissionMapper {
}
public function searchRaw($boardIds, $term, $limit = null, $offset = null) {
$qb = $this->queryCardsByBoards($boardIds);
$qb = $this->queryCardsByBoards($boardIds)
->select('s.board_id', 'board_id')
->selectAlias('s.title', 'stack_title');
$qb->andWhere($qb->expr()->eq('c.deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
$qb->andWhere(
$qb->expr()->orX(

View File

@@ -49,11 +49,12 @@ class BeforeTemplateRenderedListener implements IEventListener {
}
Util::addStyle('deck', 'deck');
if (strpos($this->request->getPathInfo(), '/apps/calendar') === 0) {
$pathInfo = $this->request->getPathInfo();
if (strpos($pathInfo, '/apps/calendar') === 0) {
Util::addScript('deck', 'calendar');
}
if (strpos($this->request->getPathInfo(), '/call/') === 0) {
if (strpos($pathInfo, '/call/') === 0 || strpos($pathInfo, '/apps/spreed') === 0) {
Util::addScript('deck', 'talk');
}
}

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

@@ -26,10 +26,9 @@ namespace OCA\Deck\Service;
use OCA\Deck\Db\Attachment;
use OCA\Deck\Sharing\DeckShareProvider;
use OCA\Deck\StatusException;
use OCP\AppFramework\Http\ContentSecurityPolicy;
use OCP\AppFramework\Http\FileDisplayResponse;
use OCP\AppFramework\Http\StreamResponse;
use OCP\Constants;
use OCP\Files\IMimeTypeDetector;
use OCP\Files\IRootFolder;
use OCP\Files\NotFoundException;
use OCP\IDBConnection;
@@ -50,6 +49,7 @@ class FilesAppService implements IAttachmentService, ICustomAttachmentService {
private $l10n;
private $preview;
private $permissionService;
private $mimeTypeDetector;
public function __construct(
IRequest $request,
@@ -60,6 +60,7 @@ class FilesAppService implements IAttachmentService, ICustomAttachmentService {
DeckShareProvider $shareProvider,
IPreview $preview,
PermissionService $permissionService,
IMimeTypeDetector $mimeTypeDetector,
string $userId = null
) {
$this->request = $request;
@@ -70,6 +71,7 @@ class FilesAppService implements IAttachmentService, ICustomAttachmentService {
$this->shareManager = $shareManager;
$this->userId = $userId;
$this->preview = $preview;
$this->mimeTypeDetector = $mimeTypeDetector;
}
public function listAttachments(int $cardId): array {
@@ -147,22 +149,10 @@ class FilesAppService implements IAttachmentService, ICustomAttachmentService {
if ($file === null || $share->getSharedWith() !== (string)$attachment->getCardId()) {
throw new NotFoundException('File not found');
}
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;
}
@@ -184,7 +174,6 @@ class FilesAppService implements IAttachmentService, ICustomAttachmentService {
throw new StatusException('Could not read file');
}
$target->putContent($content);
fclose($content);
$share = $this->shareManager->newShare();
$share->setNode($target);

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 = {

View File

@@ -117,7 +117,7 @@
type="deck-card" />
</div>
<Description :key="card.id" :card="card" />
<Description :key="card.id" :card="card" @change="descriptionChanged" />
</div>
</template>
@@ -234,6 +234,9 @@ export default {
this.initialize()
},
methods: {
descriptionChanged(newDesc) {
this.copiedCard.description = newDesc
},
async initialize() {
if (!this.card) {
return
@@ -253,9 +256,6 @@ export default {
}
},
setDue() {
this.$store.dispatch('updateCardDue', this.copiedCard)
},
removeDue() {
this.copiedCard.duedate = null
this.$store.dispatch('updateCardDue', this.copiedCard)

View File

@@ -218,6 +218,7 @@ export default {
await this.$store.dispatch('updateCardDesc', { ...this.card, description: this.description })
this.descriptionLastEdit = 0
this.descriptionSaving = false
this.$emit('change', this.description)
},
updateDescription() {
this.descriptionLastEdit = Date.now()

View File

@@ -235,9 +235,7 @@ export default {
try {
const newBoard = await this.$store.dispatch('cloneBoard', this.board)
this.loading = false
const route = this.routeTo
route.params.id = newBoard.id
this.$router.push(route)
this.$router.push({ name: 'board', params: { id: newBoard.id } })
} catch (e) {
OC.Notification.showTemporary(t('deck', 'An error occurred'))
console.error(e)
@@ -278,9 +276,7 @@ export default {
)
},
actionDetails() {
const route = this.routeTo
route.name = 'board.details'
this.$router.push(route)
this.$router.push({ name: 'board.details', params: { id: this.routeTo.id } })
},
applyEdit(e) {
this.editing = false
@@ -298,11 +294,6 @@ export default {
cancelEdit(e) {
this.editing = false
},
showSidebar() {
const route = this.routeTo
route.name = 'board.details'
this.$router.push(route)
},
async updateSetting(key, value) {
this.updateDueSetting = value
const setting = {}

View File

@@ -416,7 +416,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

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