Merge pull request #2638 from nextcloud/enh/files
This commit is contained in:
2
.github/workflows/integration.yml
vendored
2
.github/workflows/integration.yml
vendored
@@ -81,6 +81,8 @@ jobs:
|
||||
fi
|
||||
mkdir data
|
||||
./occ maintenance:install --verbose --database=${{ matrix.databases }} --database-name=nextcloud --database-host=127.0.0.1 --database-port=$DB_PORT --database-user=root --database-pass=rootpassword --admin-user admin --admin-pass admin
|
||||
cat config/config.php
|
||||
./occ user:list
|
||||
./occ app:enable --force ${{ env.APP_NAME }}
|
||||
php -S localhost:8080 &
|
||||
|
||||
|
||||
2
.github/workflows/static-analysis.yml
vendored
2
.github/workflows/static-analysis.yml
vendored
@@ -12,7 +12,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
ocp-version: [ 'dev-master', 'v20.0.1' ]
|
||||
ocp-version: [ 'dev-master' ]
|
||||
name: Nextcloud ${{ matrix.ocp-version }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
|
||||
@@ -13,6 +13,7 @@ $config
|
||||
->notPath('build')
|
||||
->notPath('l10n')
|
||||
->notPath('src')
|
||||
->notPath('node_modules')
|
||||
->notPath('vendor')
|
||||
->in(__DIR__);
|
||||
return $config;
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
- 🚀 Get your project organized
|
||||
|
||||
</description>
|
||||
<version>1.2.2</version>
|
||||
<version>1.3.0-beta1</version>
|
||||
<licence>agpl</licence>
|
||||
<author>Julius Härtl</author>
|
||||
<namespace>Deck</namespace>
|
||||
@@ -36,7 +36,7 @@
|
||||
<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="21" max-version="21" />
|
||||
</dependencies>
|
||||
<background-jobs>
|
||||
<job>OCA\Deck\Cron\DeleteCron</job>
|
||||
|
||||
@@ -80,59 +80,66 @@ return [
|
||||
['name' => 'label#delete', 'url' => '/labels/{labelId}', 'verb' => 'DELETE'],
|
||||
|
||||
// api
|
||||
['name' => 'board_api#index', 'url' => '/api/v1.0/boards', 'verb' => 'GET'],
|
||||
['name' => 'board_api#get', 'url' => '/api/v1.0/boards/{boardId}', 'verb' => 'GET'],
|
||||
['name' => 'board_api#create', 'url' => '/api/v1.0/boards', 'verb' => 'POST'],
|
||||
['name' => 'board_api#delete', 'url' => '/api/v1.0/boards/{boardId}', 'verb' => 'DELETE'],
|
||||
['name' => 'board_api#update', 'url' => '/api/v1.0/boards/{boardId}', 'verb' => 'PUT'],
|
||||
['name' => 'board_api#undo_delete', 'url' => '/api/v1.0/boards/{boardId}/undo_delete', 'verb' => 'POST'],
|
||||
['name' => 'board_api#addAcl', 'url' => '/api/v1.0/boards/{boardId}/acl', 'verb' => 'POST'],
|
||||
['name' => 'board_api#deleteAcl', 'url' => '/api/v1.0/boards/{boardId}/acl/{aclId}', 'verb' => 'DELETE'],
|
||||
['name' => 'board_api#updateAcl', 'url' => '/api/v1.0/boards/{boardId}/acl/{aclId}', 'verb' => 'PUT'],
|
||||
['name' => 'board_api#index', 'url' => '/api/v{apiVersion}/boards', 'verb' => 'GET'],
|
||||
['name' => 'board_api#get', 'url' => '/api/v{apiVersion}/boards/{boardId}', 'verb' => 'GET'],
|
||||
['name' => 'board_api#create', 'url' => '/api/v{apiVersion}/boards', 'verb' => 'POST'],
|
||||
['name' => 'board_api#delete', 'url' => '/api/v{apiVersion}/boards/{boardId}', 'verb' => 'DELETE'],
|
||||
['name' => 'board_api#update', 'url' => '/api/v{apiVersion}/boards/{boardId}', 'verb' => 'PUT'],
|
||||
['name' => 'board_api#undo_delete', 'url' => '/api/v{apiVersion}/boards/{boardId}/undo_delete', 'verb' => 'POST'],
|
||||
['name' => 'board_api#addAcl', 'url' => '/api/v{apiVersion}/boards/{boardId}/acl', 'verb' => 'POST'],
|
||||
['name' => 'board_api#deleteAcl', 'url' => '/api/v{apiVersion}/boards/{boardId}/acl/{aclId}', 'verb' => 'DELETE'],
|
||||
['name' => 'board_api#updateAcl', 'url' => '/api/v{apiVersion}/boards/{boardId}/acl/{aclId}', 'verb' => 'PUT'],
|
||||
|
||||
|
||||
['name' => 'stack_api#index', 'url' => '/api/v1.0/boards/{boardId}/stacks', 'verb' => 'GET'],
|
||||
['name' => 'stack_api#getArchived', 'url' => '/api/v1.0/boards/{boardId}/stacks/archived', 'verb' => 'GET'],
|
||||
['name' => 'stack_api#get', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}', 'verb' => 'GET'],
|
||||
['name' => 'stack_api#create', 'url' => '/api/v1.0/boards/{boardId}/stacks', 'verb' => 'POST'],
|
||||
['name' => 'stack_api#update', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}', 'verb' => 'PUT'],
|
||||
['name' => 'stack_api#delete', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}', 'verb' => 'DELETE'],
|
||||
['name' => 'stack_api#index', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks', 'verb' => 'GET'],
|
||||
['name' => 'stack_api#getArchived', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/archived', 'verb' => 'GET'],
|
||||
['name' => 'stack_api#get', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}', 'verb' => 'GET'],
|
||||
['name' => 'stack_api#create', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks', 'verb' => 'POST'],
|
||||
['name' => 'stack_api#update', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}', 'verb' => 'PUT'],
|
||||
['name' => 'stack_api#delete', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}', 'verb' => 'DELETE'],
|
||||
|
||||
['name' => 'card_api#get', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}', 'verb' => 'GET'],
|
||||
['name' => 'card_api#create', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards', 'verb' => 'POST'],
|
||||
['name' => 'card_api#update', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}', 'verb' => 'PUT'],
|
||||
['name' => 'card_api#assignLabel', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/assignLabel', 'verb' => 'PUT'],
|
||||
['name' => 'card_api#removeLabel', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/removeLabel', 'verb' => 'PUT'],
|
||||
['name' => 'card_api#assignUser', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/assignUser', 'verb' => 'PUT'],
|
||||
['name' => 'card_api#unassignUser', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/unassignUser', 'verb' => 'PUT'],
|
||||
['name' => 'card_api#reorder', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/reorder', 'verb' => 'PUT'],
|
||||
['name' => 'card_api#delete', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}', 'verb' => 'DELETE'],
|
||||
['name' => 'card_api#get', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}', 'verb' => 'GET'],
|
||||
['name' => 'card_api#create', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards', 'verb' => 'POST'],
|
||||
['name' => 'card_api#update', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}', 'verb' => 'PUT'],
|
||||
['name' => 'card_api#assignLabel', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/assignLabel', 'verb' => 'PUT'],
|
||||
['name' => 'card_api#removeLabel', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/removeLabel', 'verb' => 'PUT'],
|
||||
['name' => 'card_api#assignUser', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/assignUser', 'verb' => 'PUT'],
|
||||
['name' => 'card_api#unassignUser', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/unassignUser', 'verb' => 'PUT'],
|
||||
['name' => 'card_api#reorder', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/reorder', 'verb' => 'PUT'],
|
||||
['name' => 'card_api#delete', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}', 'verb' => 'DELETE'],
|
||||
|
||||
['name' => 'card_api#findAllWithDue', 'url' => '/api/v1.0/dashboard/due', 'verb' => 'GET'],
|
||||
['name' => 'card_api#findAllWithDue', 'url' => '/api/v{apiVersion}/dashboard/due', 'verb' => 'GET'],
|
||||
|
||||
['name' => 'label_api#get', 'url' => '/api/v1.0/boards/{boardId}/labels/{labelId}', 'verb' => 'GET'],
|
||||
['name' => 'label_api#create', 'url' => '/api/v1.0/boards/{boardId}/labels', 'verb' => 'POST'],
|
||||
['name' => 'label_api#update', 'url' => '/api/v1.0/boards/{boardId}/labels/{labelId}', 'verb' => 'PUT'],
|
||||
['name' => 'label_api#delete', 'url' => '/api/v1.0/boards/{boardId}/labels/{labelId}', 'verb' => 'DELETE'],
|
||||
['name' => 'label_api#get', 'url' => '/api/v{apiVersion}/boards/{boardId}/labels/{labelId}', 'verb' => 'GET'],
|
||||
['name' => 'label_api#create', 'url' => '/api/v{apiVersion}/boards/{boardId}/labels', 'verb' => 'POST'],
|
||||
['name' => 'label_api#update', 'url' => '/api/v{apiVersion}/boards/{boardId}/labels/{labelId}', 'verb' => 'PUT'],
|
||||
['name' => 'label_api#delete', 'url' => '/api/v{apiVersion}/boards/{boardId}/labels/{labelId}', 'verb' => 'DELETE'],
|
||||
|
||||
['name' => 'attachment_api#getAll', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments', 'verb' => 'GET'],
|
||||
['name' => 'attachment_api#display', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{attachmentId}', 'verb' => 'GET'],
|
||||
['name' => 'attachment_api#create', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments', 'verb' => 'POST'],
|
||||
['name' => 'attachment_api#update', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{attachmentId}', 'verb' => 'PUT'],
|
||||
['name' => 'attachment_api#delete', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{attachmentId}', 'verb' => 'DELETE'],
|
||||
['name' => 'attachment_api#restore', 'url' => '/api/v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{attachmentId}/restore', 'verb' => 'PUT'],
|
||||
['name' => 'attachment_api#getAll', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments', 'verb' => 'GET', 'requirements' => ['apiVersion' => '1.0']],
|
||||
['name' => 'attachment_api#display', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{attachmentId}', 'verb' => 'GET', 'requirements' => ['apiVersion' => '1.0']],
|
||||
['name' => 'attachment_api#create', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments', 'verb' => 'POST', 'requirements' => ['apiVersion' => '1.0']],
|
||||
['name' => 'attachment_api#update', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{attachmentId}', 'verb' => 'PUT', 'requirements' => ['apiVersion' => '1.0']],
|
||||
['name' => 'attachment_api#delete', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{attachmentId}', 'verb' => 'DELETE', 'requirements' => ['apiVersion' => '1.0']],
|
||||
['name' => 'attachment_api#restore', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{attachmentId}/restore', 'verb' => 'PUT', 'requirements' => ['apiVersion' => '1.0']],
|
||||
|
||||
['name' => 'board_api#preflighted_cors', 'url' => '/api/v1.0/{path}','verb' => 'OPTIONS', 'requirements' => ['path' => '.+']],
|
||||
['name' => 'attachment_api_v11#getAll', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments', 'verb' => 'GET', 'requirements' => ['apiVersion' => '1.1']],
|
||||
['name' => 'attachment_api_v11#display', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{type}/{attachmentId}', 'verb' => 'GET', 'requirements' => ['apiVersion' => '1.1']],
|
||||
['name' => 'attachment_api_v11#create', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments', 'verb' => 'POST', 'requirements' => ['apiVersion' => '1.1']],
|
||||
['name' => 'attachment_api_v11#update', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{type}/{attachmentId}', 'verb' => 'PUT', 'requirements' => ['apiVersion' => '1.1']],
|
||||
['name' => 'attachment_api_v11#delete', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{type}/{attachmentId}', 'verb' => 'DELETE', 'requirements' => ['apiVersion' => '1.1']],
|
||||
['name' => 'attachment_api_v11#restore', 'url' => '/api/v{apiVersion}/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{type}/{attachmentId}/restore', 'verb' => 'PUT', 'requirements' => ['apiVersion' => '1.1']],
|
||||
|
||||
['name' => 'board_api#preflighted_cors', 'url' => '/api/v{apiVersion}/{path}','verb' => 'OPTIONS', 'requirements' => ['path' => '.+']],
|
||||
],
|
||||
'ocs' => [
|
||||
['name' => 'Config#get', 'url' => '/api/v1.0/config', 'verb' => 'GET'],
|
||||
['name' => 'Config#setValue', 'url' => '/api/v1.0/config/{key}', 'verb' => 'POST'],
|
||||
['name' => 'Config#get', 'url' => '/api/v{apiVersion}/config', 'verb' => 'GET'],
|
||||
['name' => 'Config#setValue', 'url' => '/api/v{apiVersion}/config/{key}', 'verb' => 'POST'],
|
||||
|
||||
['name' => 'comments_api#list', 'url' => '/api/v1.0/cards/{cardId}/comments', 'verb' => 'GET'],
|
||||
['name' => 'comments_api#create', 'url' => '/api/v1.0/cards/{cardId}/comments', 'verb' => 'POST'],
|
||||
['name' => 'comments_api#update', 'url' => '/api/v1.0/cards/{cardId}/comments/{commentId}', 'verb' => 'PUT'],
|
||||
['name' => 'comments_api#delete', 'url' => '/api/v1.0/cards/{cardId}/comments/{commentId}', 'verb' => 'DELETE'],
|
||||
['name' => 'comments_api#list', 'url' => '/api/v{apiVersion}/cards/{cardId}/comments', 'verb' => 'GET'],
|
||||
['name' => 'comments_api#create', 'url' => '/api/v{apiVersion}/cards/{cardId}/comments', 'verb' => 'POST'],
|
||||
['name' => 'comments_api#update', 'url' => '/api/v{apiVersion}/cards/{cardId}/comments/{commentId}', 'verb' => 'PUT'],
|
||||
['name' => 'comments_api#delete', 'url' => '/api/v{apiVersion}/cards/{cardId}/comments/{commentId}', 'verb' => 'DELETE'],
|
||||
|
||||
['name' => 'overview_api#upcomingCards', 'url' => '/api/v1.0/overview/upcoming', 'verb' => 'GET'],
|
||||
['name' => 'overview_api#upcomingCards', 'url' => '/api/v{apiVersion}/overview/upcoming', 'verb' => 'GET'],
|
||||
]
|
||||
];
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"name": "nextcloud/deck",
|
||||
"type": "project",
|
||||
"license": "AGPLv3",
|
||||
"minimum-stability": "dev",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Julius Härtl",
|
||||
@@ -13,7 +14,7 @@
|
||||
},
|
||||
"require-dev": {
|
||||
"roave/security-advisories": "dev-master",
|
||||
"christophwurst/nextcloud": "^20",
|
||||
"christophwurst/nextcloud": "dev-master",
|
||||
"phpunit/phpunit": "^8",
|
||||
"nextcloud/coding-standard": "^0.4.0",
|
||||
"symfony/event-dispatcher": "^4.0",
|
||||
|
||||
24
composer.lock
generated
24
composer.lock
generated
@@ -4,7 +4,7 @@
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "4a3d65807490679a4de897a8643385bb",
|
||||
"content-hash": "3172d27bd19b2a125db3197c495deda9",
|
||||
"packages": [
|
||||
{
|
||||
"name": "cogpowered/finediff",
|
||||
@@ -225,25 +225,26 @@
|
||||
},
|
||||
{
|
||||
"name": "christophwurst/nextcloud",
|
||||
"version": "v20.0.4",
|
||||
"version": "dev-master",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ChristophWurst/nextcloud_composer.git",
|
||||
"reference": "a207b55848d1ac4c83a954eac90c07714bbdaaed"
|
||||
"reference": "0c78518f688ea2ceb1e23ff2931e0e1db1b75ddd"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/ChristophWurst/nextcloud_composer/zipball/a207b55848d1ac4c83a954eac90c07714bbdaaed",
|
||||
"reference": "a207b55848d1ac4c83a954eac90c07714bbdaaed",
|
||||
"url": "https://api.github.com/repos/ChristophWurst/nextcloud_composer/zipball/0c78518f688ea2ceb1e23ff2931e0e1db1b75ddd",
|
||||
"reference": "0c78518f688ea2ceb1e23ff2931e0e1db1b75ddd",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"php": "^7.2"
|
||||
"php": "^7.3 || ~8.0.0"
|
||||
},
|
||||
"default-branch": true,
|
||||
"type": "library",
|
||||
"extra": {
|
||||
"branch-alias": {
|
||||
"dev-master": "20.0.0-dev"
|
||||
"dev-master": "21.0.0-dev"
|
||||
}
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
@@ -259,9 +260,9 @@
|
||||
"description": "Composer package containing Nextcloud's public API (classes, interfaces)",
|
||||
"support": {
|
||||
"issues": "https://github.com/ChristophWurst/nextcloud_composer/issues",
|
||||
"source": "https://github.com/ChristophWurst/nextcloud_composer/tree/v20.0.4"
|
||||
"source": "https://github.com/ChristophWurst/nextcloud_composer/tree/master"
|
||||
},
|
||||
"time": "2020-12-23T12:42:07+00:00"
|
||||
"time": "2020-12-23T22:55:20+00:00"
|
||||
},
|
||||
{
|
||||
"name": "composer/package-versions-deprecated",
|
||||
@@ -4893,9 +4894,10 @@
|
||||
}
|
||||
],
|
||||
"aliases": [],
|
||||
"minimum-stability": "stable",
|
||||
"minimum-stability": "dev",
|
||||
"stability-flags": {
|
||||
"roave/security-advisories": 20
|
||||
"roave/security-advisories": 20,
|
||||
"christophwurst/nextcloud": 20
|
||||
},
|
||||
"prefer-stable": false,
|
||||
"prefer-lowest": false,
|
||||
|
||||
@@ -380,7 +380,7 @@ class ActivityManager {
|
||||
case self::SUBJECT_ATTACHMENT_UPDATE:
|
||||
case self::SUBJECT_ATTACHMENT_DELETE:
|
||||
case self::SUBJECT_ATTACHMENT_RESTORE:
|
||||
$subjectParams = $this->findDetailsForAttachment($entity->getId());
|
||||
$subjectParams = $this->findDetailsForAttachment($entity);
|
||||
break;
|
||||
case self::SUBJECT_BOARD_SHARE:
|
||||
case self::SUBJECT_BOARD_UNSHARE:
|
||||
@@ -527,8 +527,7 @@ class ActivityManager {
|
||||
];
|
||||
}
|
||||
|
||||
private function findDetailsForAttachment($attachmentId) {
|
||||
$attachment = $this->attachmentMapper->find($attachmentId);
|
||||
private function findDetailsForAttachment($attachment) {
|
||||
$data = $this->findDetailsForCard($attachment->getCardId());
|
||||
return array_merge($data, ['attachment' => $attachment]);
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ use OCA\Deck\Notification\Notifier;
|
||||
use OCA\Deck\Search\DeckProvider;
|
||||
use OCA\Deck\Service\FullTextSearchService;
|
||||
use OCA\Deck\Service\PermissionService;
|
||||
use OCA\Deck\Sharing\DeckShareProvider;
|
||||
use OCA\Deck\Sharing\Listener;
|
||||
use OCP\AppFramework\App;
|
||||
use OCP\AppFramework\Bootstrap\IBootContext;
|
||||
use OCP\AppFramework\Bootstrap\IBootstrap;
|
||||
@@ -62,6 +64,7 @@ use OCP\IServerContainer;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
use OCP\Notification\IManager as NotificationManager;
|
||||
use OCP\Share\IManager;
|
||||
use OCP\Util;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
@@ -92,6 +95,16 @@ class Application20 extends App implements IBootstrap {
|
||||
$context->injectFn(Closure::fromCallable([$this, 'registerNotifications']));
|
||||
$context->injectFn(Closure::fromCallable([$this, 'registerFullTextSearch']));
|
||||
$context->injectFn(Closure::fromCallable([$this, 'registerCollaborationResources']));
|
||||
|
||||
$context->injectFn(function (IManager $shareManager) {
|
||||
if (method_exists($shareManager, 'registerShareProvider')) {
|
||||
$shareManager->registerShareProvider(DeckShareProvider::class);
|
||||
}
|
||||
});
|
||||
|
||||
$context->injectFn(function (Listener $listener, IEventDispatcher $eventDispatcher) {
|
||||
$listener->register($eventDispatcher);
|
||||
});
|
||||
}
|
||||
|
||||
public function register(IRegistrationContext $context): void {
|
||||
|
||||
@@ -50,7 +50,11 @@ class Capabilities implements ICapability {
|
||||
return [
|
||||
'deck' => [
|
||||
'version' => $this->appManager->getAppVersion('deck'),
|
||||
'canCreateBoards' => $this->permissionService->canCreate()
|
||||
'canCreateBoards' => $this->permissionService->canCreate(),
|
||||
'apiVersions' => [
|
||||
'1.0',
|
||||
'1.1'
|
||||
]
|
||||
]
|
||||
];
|
||||
}
|
||||
|
||||
@@ -42,8 +42,13 @@ class AttachmentApiController extends ApiController {
|
||||
* @NoCSRFRequired
|
||||
*
|
||||
*/
|
||||
public function getAll() {
|
||||
public function getAll($apiVersion) {
|
||||
$attachment = $this->attachmentService->findAll($this->request->getParam('cardId'), true);
|
||||
if ($apiVersion === '1.0') {
|
||||
$attachment = array_filter($attachment, function ($attachment) {
|
||||
return $attachment->getType() === 'deck_file';
|
||||
});
|
||||
}
|
||||
return new DataResponse($attachment, HTTP::STATUS_OK);
|
||||
}
|
||||
|
||||
@@ -53,8 +58,8 @@ class AttachmentApiController extends ApiController {
|
||||
* @NoCSRFRequired
|
||||
*
|
||||
*/
|
||||
public function display() {
|
||||
return $this->attachmentService->display($this->request->getParam('attachmentId'));
|
||||
public function display($cardId, $attachmentId, $type = 'deck_file') {
|
||||
return $this->attachmentService->display($cardId, $attachmentId, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -63,8 +68,8 @@ class AttachmentApiController extends ApiController {
|
||||
* @NoCSRFRequired
|
||||
*
|
||||
*/
|
||||
public function create($type, $data) {
|
||||
$attachment = $this->attachmentService->create($this->request->getParam('cardId'), $type, $data);
|
||||
public function create($cardId, $type, $data) {
|
||||
$attachment = $this->attachmentService->create($cardId, $type, $data);
|
||||
return new DataResponse($attachment, HTTP::STATUS_OK);
|
||||
}
|
||||
|
||||
@@ -74,8 +79,8 @@ class AttachmentApiController extends ApiController {
|
||||
* @NoCSRFRequired
|
||||
*
|
||||
*/
|
||||
public function update($data) {
|
||||
$attachment = $this->attachmentService->update($this->request->getParam('attachmentId'), $data);
|
||||
public function update($cardId, $attachmentId, $data, $type = 'deck_file') {
|
||||
$attachment = $this->attachmentService->update($cardId, $attachmentId, $data, $type);
|
||||
return new DataResponse($attachment, HTTP::STATUS_OK);
|
||||
}
|
||||
|
||||
@@ -85,8 +90,8 @@ class AttachmentApiController extends ApiController {
|
||||
* @NoCSRFRequired
|
||||
*
|
||||
*/
|
||||
public function delete() {
|
||||
$attachment = $this->attachmentService->delete($this->request->getParam('attachmentId'));
|
||||
public function delete($cardId, $attachmentId, $type = 'deck_file') {
|
||||
$attachment = $this->attachmentService->delete($cardId, $attachmentId, $type);
|
||||
return new DataResponse($attachment, HTTP::STATUS_OK);
|
||||
}
|
||||
|
||||
@@ -96,8 +101,8 @@ class AttachmentApiController extends ApiController {
|
||||
* @NoCSRFRequired
|
||||
*
|
||||
*/
|
||||
public function restore() {
|
||||
$attachment = $this->attachmentService->restore($this->request->getParam('attachmentId'));
|
||||
public function restore($cardId, $attachmentId, $type = 'deck_file') {
|
||||
$attachment = $this->attachmentService->restore($cardId, $attachmentId, $type);
|
||||
return new DataResponse($attachment, HTTP::STATUS_OK);
|
||||
}
|
||||
}
|
||||
|
||||
26
lib/Controller/AttachmentApiV11Controller.php
Normal file
26
lib/Controller/AttachmentApiV11Controller.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2018 Ryan Fletcher <ryan.fletcher@codepassion.ca>
|
||||
*
|
||||
* @author Ryan Fletcher <ryan.fletcher@codepassion.ca>
|
||||
*
|
||||
* @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\Controller;
|
||||
|
||||
class AttachmentApiV11Controller extends AttachmentApiController {
|
||||
}
|
||||
@@ -52,8 +52,13 @@ class AttachmentController extends Controller {
|
||||
* @return \OCP\AppFramework\Http\Response
|
||||
* @throws \OCA\Deck\NotFoundException
|
||||
*/
|
||||
public function display($attachmentId) {
|
||||
return $this->attachmentService->display($attachmentId);
|
||||
public function display($cardId, $attachmentId) {
|
||||
if (strpos($attachmentId, ':') === false) {
|
||||
$type = 'deck_file';
|
||||
} else {
|
||||
[$type, $attachmentId] = explode(':', $attachmentId);
|
||||
}
|
||||
return $this->attachmentService->display($cardId, $attachmentId, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -70,21 +75,36 @@ class AttachmentController extends Controller {
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*/
|
||||
public function update($attachmentId) {
|
||||
return $this->attachmentService->update($attachmentId, $this->request->getParam('data'));
|
||||
public function update($cardId, $attachmentId) {
|
||||
if (strpos($attachmentId, ':') === false) {
|
||||
$type = 'deck_file';
|
||||
} else {
|
||||
[$type, $attachmentId] = explode(':', $attachmentId);
|
||||
}
|
||||
return $this->attachmentService->update($cardId, $attachmentId, $this->request->getParam('data'), $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*/
|
||||
public function delete($attachmentId) {
|
||||
return $this->attachmentService->delete($attachmentId);
|
||||
public function delete($cardId, $attachmentId) {
|
||||
if (strpos($attachmentId, ':') === false) {
|
||||
$type = 'deck_file';
|
||||
} else {
|
||||
[$type, $attachmentId] = explode(':', $attachmentId);
|
||||
}
|
||||
return $this->attachmentService->delete($cardId, $attachmentId, $type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @NoAdminRequired
|
||||
*/
|
||||
public function restore($attachmentId) {
|
||||
return $this->attachmentService->restore($attachmentId);
|
||||
public function restore($cardId, $attachmentId) {
|
||||
if (strpos($attachmentId, ':') === false) {
|
||||
$type = 'deck_file';
|
||||
} else {
|
||||
[$type, $attachmentId] = explode(':', $attachmentId);
|
||||
}
|
||||
return $this->attachmentService->restore($cardId, $attachmentId, $type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,10 @@ namespace OCA\Deck\Controller;
|
||||
use OCA\Deck\AppInfo\Application;
|
||||
use OCA\Deck\Service\ConfigService;
|
||||
use OCA\Deck\Service\PermissionService;
|
||||
use OCA\Files\Event\LoadSidebar;
|
||||
use OCA\Viewer\Event\LoadViewer;
|
||||
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\IInitialStateService;
|
||||
use OCP\IRequest;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
@@ -34,23 +37,24 @@ use OCP\AppFramework\Controller;
|
||||
|
||||
class PageController extends Controller {
|
||||
private $permissionService;
|
||||
private $userId;
|
||||
private $l10n;
|
||||
private $initialState;
|
||||
private $configService;
|
||||
private $eventDispatcher;
|
||||
|
||||
public function __construct(
|
||||
$AppName,
|
||||
IRequest $request,
|
||||
PermissionService $permissionService,
|
||||
IInitialStateService $initialStateService,
|
||||
ConfigService $configService
|
||||
ConfigService $configService,
|
||||
IEventDispatcher $eventDispatcher
|
||||
) {
|
||||
parent::__construct($AppName, $request);
|
||||
|
||||
$this->permissionService = $permissionService;
|
||||
$this->initialState = $initialStateService;
|
||||
$this->configService = $configService;
|
||||
$this->eventDispatcher = $eventDispatcher;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -65,6 +69,11 @@ class PageController extends Controller {
|
||||
$this->initialState->provideInitialState(Application::APP_ID, 'canCreate', $this->permissionService->canCreate());
|
||||
$this->initialState->provideInitialState(Application::APP_ID, 'config', $this->configService->getAll());
|
||||
|
||||
$this->eventDispatcher->dispatchTyped(new LoadSidebar());
|
||||
if (class_exists(LoadViewer::class)) {
|
||||
$this->eventDispatcher->dispatchTyped(new LoadViewer());
|
||||
}
|
||||
|
||||
$response = new TemplateResponse('deck', 'main');
|
||||
|
||||
if (\OC::$server->getConfig()->getSystemValueBool('debug', false)) {
|
||||
|
||||
@@ -85,6 +85,16 @@ class BoardMapper extends DeckMapper implements IPermissionMapper {
|
||||
return $board;
|
||||
}
|
||||
|
||||
public function findAllForUser(string $userId, int $since = -1, $includeArchived = true): array {
|
||||
$groups = $this->groupManager->getUserGroupIds(
|
||||
$this->userManager->get($userId)
|
||||
);
|
||||
$userBoards = $this->findAllByUser($userId, null, null, $since, $includeArchived);
|
||||
$groupBoards = $this->findAllByGroups($userId, $groups,null, null, $since, $includeArchived);
|
||||
$circleBoards = $this->findAllByCircles($userId, null, null, $since, $includeArchived);
|
||||
return array_unique(array_merge($userBoards, $groupBoards, $circleBoards));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all boards for a given user
|
||||
*
|
||||
|
||||
@@ -149,7 +149,8 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
|
||||
public function queryCardsByBoards(array $boardIds): IQueryBuilder {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('c.*')
|
||||
$qb->select('c.*', 's.board_id')
|
||||
->selectAlias('s.title', 'stack_title')
|
||||
->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)));
|
||||
@@ -279,6 +280,27 @@ class CardMapper extends QBMapper implements IPermissionMapper {
|
||||
return $this->findEntities($qb);
|
||||
}
|
||||
|
||||
public function searchRaw($boardIds, $term, $limit = null, $offset = null) {
|
||||
$qb = $this->queryCardsByBoards($boardIds);
|
||||
$qb->andWhere($qb->expr()->eq('c.deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
|
||||
$qb->andWhere(
|
||||
$qb->expr()->orX(
|
||||
$qb->expr()->iLike('c.title', $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($term) . '%')),
|
||||
$qb->expr()->iLike('c.description', $qb->createNamedParameter('%' . $this->db->escapeLikeParameter($term) . '%'))
|
||||
)
|
||||
);
|
||||
if ($limit !== null) {
|
||||
$qb->setMaxResults($limit);
|
||||
}
|
||||
if ($offset !== null) {
|
||||
$qb->setFirstResult($offset);
|
||||
}
|
||||
$result = $qb->execute();
|
||||
$all = $result->fetchAll();
|
||||
$result->closeCursor();
|
||||
return $all;
|
||||
}
|
||||
|
||||
public function delete(Entity $entity): Entity {
|
||||
// delete assigned labels
|
||||
$this->labelMapper->deleteLabelAssignmentsForCard($entity->getId());
|
||||
|
||||
@@ -58,18 +58,6 @@ class AttachmentService {
|
||||
/** @var ChangeHelper */
|
||||
private $changeHelper;
|
||||
|
||||
/**
|
||||
* AttachmentService constructor.
|
||||
*
|
||||
* @param AttachmentMapper $attachmentMapper
|
||||
* @param CardMapper $cardMapper
|
||||
* @param PermissionService $permissionService
|
||||
* @param Application $application
|
||||
* @param ICacheFactory $cacheFactory
|
||||
* @param $userId
|
||||
* @param IL10N $l10n
|
||||
* @throws \OCP\AppFramework\QueryException
|
||||
*/
|
||||
public function __construct(AttachmentMapper $attachmentMapper, CardMapper $cardMapper, ChangeHelper $changeHelper, PermissionService $permissionService, Application $application, ICacheFactory $cacheFactory, $userId, IL10N $l10n, ActivityManager $activityManager) {
|
||||
$this->attachmentMapper = $attachmentMapper;
|
||||
$this->cardMapper = $cardMapper;
|
||||
@@ -84,6 +72,7 @@ class AttachmentService {
|
||||
// Register shipped attachment services
|
||||
// TODO: move this to a plugin based approach once we have different types of attachments
|
||||
$this->registerAttachmentService('deck_file', FileService::class);
|
||||
$this->registerAttachmentService('file', FilesAppService::class);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -124,6 +113,15 @@ class AttachmentService {
|
||||
if ($withDeleted) {
|
||||
$attachments = array_merge($attachments, $this->attachmentMapper->findToDelete($cardId, false));
|
||||
}
|
||||
|
||||
foreach (array_keys($this->services) as $attachmentType) {
|
||||
/** @var IAttachmentService $service */
|
||||
$service = $this->getService($attachmentType);
|
||||
if ($service instanceof ICustomAttachmentService) {
|
||||
$attachments = array_merge($attachments, $service->listAttachments((int)$cardId));
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($attachments as &$attachment) {
|
||||
try {
|
||||
$service = $this->getService($attachment->getType());
|
||||
@@ -132,6 +130,7 @@ class AttachmentService {
|
||||
// Ingore invalid attachment types when extending the data
|
||||
}
|
||||
}
|
||||
|
||||
return $attachments;
|
||||
}
|
||||
|
||||
@@ -148,8 +147,17 @@ class AttachmentService {
|
||||
$count = $this->cache->get('card-' . $cardId);
|
||||
if (!$count) {
|
||||
$count = count($this->attachmentMapper->findAll($cardId));
|
||||
|
||||
foreach (array_keys($this->services) as $attachmentType) {
|
||||
$service = $this->getService($attachmentType);
|
||||
if ($service instanceof ICustomAttachmentService) {
|
||||
$count += $service->getAttachmentCount((int)$cardId);
|
||||
}
|
||||
}
|
||||
|
||||
$this->cache->set('card-' . $cardId, $count);
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
@@ -189,21 +197,20 @@ class AttachmentService {
|
||||
try {
|
||||
$service = $this->getService($attachment->getType());
|
||||
$service->create($attachment);
|
||||
} catch (InvalidAttachmentType $e) {
|
||||
// just store the data
|
||||
}
|
||||
if ($attachment->getData() === null) {
|
||||
throw new StatusException($this->l10n->t('No data was provided to create an attachment.'));
|
||||
}
|
||||
$attachment = $this->attachmentMapper->insert($attachment);
|
||||
|
||||
// extend data so the frontend can use it properly after creating
|
||||
try {
|
||||
$service = $this->getService($attachment->getType());
|
||||
if (!$service instanceof ICustomAttachmentService) {
|
||||
if ($attachment->getData() === null) {
|
||||
throw new StatusException($this->l10n->t('No data was provided to create an attachment.'));
|
||||
}
|
||||
|
||||
$attachment = $this->attachmentMapper->insert($attachment);
|
||||
}
|
||||
|
||||
$service->extendData($attachment);
|
||||
} catch (InvalidAttachmentType $e) {
|
||||
// just store the data
|
||||
}
|
||||
|
||||
$this->changeHelper->cardChanged($attachment->getCardId());
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $attachment, ActivityManager::SUBJECT_ATTACHMENT_CREATE);
|
||||
return $attachment;
|
||||
@@ -215,46 +222,69 @@ class AttachmentService {
|
||||
*
|
||||
* @param $attachmentId
|
||||
* @return Response
|
||||
* @throws BadRequestException
|
||||
* @throws NoPermissionException
|
||||
* @throws NotFoundException
|
||||
* @throws \OCP\AppFramework\Db\DoesNotExistException
|
||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||
*/
|
||||
public function display($attachmentId) {
|
||||
if (is_numeric($attachmentId) === false) {
|
||||
throw new BadRequestException('attachment id must be a number');
|
||||
}
|
||||
|
||||
public function display($cardId, $attachmentId, $type = 'deck_file') {
|
||||
try {
|
||||
$attachment = $this->attachmentMapper->find($attachmentId);
|
||||
} catch (\Exception $e) {
|
||||
throw new NoPermissionException('Permission denied');
|
||||
}
|
||||
$this->permissionService->checkPermission($this->cardMapper, $attachment->getCardId(), Acl::PERMISSION_READ);
|
||||
|
||||
try {
|
||||
$service = $this->getService($attachment->getType());
|
||||
return $service->display($attachment);
|
||||
$service = $this->getService($type);
|
||||
} catch (InvalidAttachmentType $e) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
if (!$service instanceof ICustomAttachmentService) {
|
||||
try {
|
||||
$attachment = $this->attachmentMapper->find($attachmentId);
|
||||
} catch (\Exception $e) {
|
||||
throw new NoPermissionException('Permission denied');
|
||||
}
|
||||
$this->permissionService->checkPermission($this->cardMapper, $attachment->getCardId(), Acl::PERMISSION_READ);
|
||||
|
||||
try {
|
||||
$service = $this->getService($attachment->getType());
|
||||
} catch (InvalidAttachmentType $e) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
} else {
|
||||
$attachment = new Attachment();
|
||||
$attachment->setId($attachmentId);
|
||||
$attachment->setType($type);
|
||||
$attachment->setCardId($cardId);
|
||||
$this->permissionService->checkPermission($this->cardMapper, $attachment->getCardId(), Acl::PERMISSION_READ);
|
||||
}
|
||||
|
||||
return $service->display($attachment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an attachment with custom data
|
||||
*
|
||||
* @param $attachmentId
|
||||
* @param $request
|
||||
* @param $data
|
||||
* @return mixed
|
||||
* @throws \OCA\Deck\NoPermissionException
|
||||
* @throws \OCP\AppFramework\Db\DoesNotExistException
|
||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||
* @throws BadRequestException
|
||||
* @throws NoPermissionException
|
||||
*/
|
||||
public function update($attachmentId, $data) {
|
||||
if (is_numeric($attachmentId) === false) {
|
||||
throw new BadRequestException('attachment id must be a number');
|
||||
public function update($cardId, $attachmentId, $data, $type = 'deck_file') {
|
||||
try {
|
||||
$service = $this->getService($type);
|
||||
} catch (InvalidAttachmentType $e) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
if ($service instanceof ICustomAttachmentService) {
|
||||
try {
|
||||
$attachment = new Attachment();
|
||||
$attachment->setId($attachmentId);
|
||||
$attachment->setType($type);
|
||||
$attachment->setData($data);
|
||||
$attachment->setCardId($cardId);
|
||||
$service->update($attachment);
|
||||
$this->changeHelper->cardChanged($attachment->getCardId());
|
||||
return $attachment;
|
||||
} catch (\Exception $e) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
}
|
||||
|
||||
if ($data === false || $data === null) {
|
||||
@@ -279,12 +309,8 @@ class AttachmentService {
|
||||
$attachment->setLastModified(time());
|
||||
$this->attachmentMapper->update($attachment);
|
||||
// extend data so the frontend can use it properly after creating
|
||||
try {
|
||||
$service = $this->getService($attachment->getType());
|
||||
$service->extendData($attachment);
|
||||
} catch (InvalidAttachmentType $e) {
|
||||
// just store the data
|
||||
}
|
||||
$service->extendData($attachment);
|
||||
|
||||
$this->changeHelper->cardChanged($attachment->getCardId());
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $attachment, ActivityManager::SUBJECT_ATTACHMENT_UPDATE);
|
||||
return $attachment;
|
||||
@@ -301,9 +327,23 @@ class AttachmentService {
|
||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||
* @throws BadRequestException
|
||||
*/
|
||||
public function delete($attachmentId) {
|
||||
if (is_numeric($attachmentId) === false) {
|
||||
throw new BadRequestException('attachment id must be a number');
|
||||
public function delete($cardId, $attachmentId, $type = 'deck_file') {
|
||||
try {
|
||||
$service = $this->getService($type);
|
||||
} catch (InvalidAttachmentType $e) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
|
||||
if ($service instanceof ICustomAttachmentService) {
|
||||
$attachment = new Attachment();
|
||||
$attachment->setId($attachmentId);
|
||||
$attachment->setType($type);
|
||||
$attachment->setCardId($cardId);
|
||||
$service->extendData($attachment);
|
||||
$service->delete($attachment);
|
||||
$this->changeHelper->cardChanged($attachment->getCardId());
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $attachment, ActivityManager::SUBJECT_ATTACHMENT_DELETE);
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -315,25 +355,21 @@ class AttachmentService {
|
||||
$this->permissionService->checkPermission($this->cardMapper, $attachment->getCardId(), Acl::PERMISSION_EDIT);
|
||||
$this->cache->clear('card-' . $attachment->getCardId());
|
||||
|
||||
try {
|
||||
$service = $this->getService($attachment->getType());
|
||||
if ($service->allowUndo()) {
|
||||
$service->markAsDeleted($attachment);
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $attachment, ActivityManager::SUBJECT_ATTACHMENT_DELETE);
|
||||
$this->changeHelper->cardChanged($attachment->getCardId());
|
||||
return $this->attachmentMapper->update($attachment);
|
||||
}
|
||||
$service->delete($attachment);
|
||||
} catch (InvalidAttachmentType $e) {
|
||||
// just delete without further action
|
||||
if ($service->allowUndo()) {
|
||||
$service->markAsDeleted($attachment);
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $attachment, ActivityManager::SUBJECT_ATTACHMENT_DELETE);
|
||||
$this->changeHelper->cardChanged($attachment->getCardId());
|
||||
return $this->attachmentMapper->update($attachment);
|
||||
}
|
||||
$service->delete($attachment);
|
||||
|
||||
$attachment = $this->attachmentMapper->delete($attachment);
|
||||
$this->changeHelper->cardChanged($attachment->getCardId());
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_CARD, $attachment, ActivityManager::SUBJECT_ATTACHMENT_DELETE);
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
public function restore($attachmentId) {
|
||||
public function restore($cardId, $attachmentId, $type = 'deck_file') {
|
||||
if (is_numeric($attachmentId) === false) {
|
||||
throw new BadRequestException('attachment id must be a number');
|
||||
}
|
||||
|
||||
@@ -113,13 +113,13 @@ class BoardService {
|
||||
$this->userId = $userId;
|
||||
}
|
||||
|
||||
public function getUserBoards(int $since = -1, $includeArchived = true): array {
|
||||
$userInfo = $this->getBoardPrerequisites();
|
||||
$userBoards = $this->boardMapper->findAllByUser($userInfo['user'], null, null, $since, $includeArchived);
|
||||
$groupBoards = $this->boardMapper->findAllByGroups($userInfo['user'], $userInfo['groups'],null, null, $since, $includeArchived);
|
||||
$circleBoards = $this->boardMapper->findAllByCircles($userInfo['user'], null, null, $since, $includeArchived);
|
||||
return array_unique(array_merge($userBoards, $groupBoards, $circleBoards));
|
||||
/**
|
||||
* Get all boards that are shared with a user, their groups or circles
|
||||
*/
|
||||
public function getUserBoards(int $since = -1, bool $includeArchived = true): array {
|
||||
return $this->boardMapper->findAllForUser($this->userId, $since, $includeArchived);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
@@ -324,7 +324,7 @@ class BoardService {
|
||||
'PERMISSION_MANAGE' => $permissions[Acl::PERMISSION_MANAGE] ?? false,
|
||||
'PERMISSION_SHARE' => $permissions[Acl::PERMISSION_SHARE] ?? false
|
||||
]);
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $new_board, ActivityManager::SUBJECT_BOARD_CREATE);
|
||||
$this->activityManager->triggerEvent(ActivityManager::DECK_OBJECT_BOARD, $new_board, ActivityManager::SUBJECT_BOARD_CREATE, [], $userId);
|
||||
$this->changeHelper->boardChanged($new_board->getId());
|
||||
|
||||
$this->eventDispatcher->dispatch(
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace OCA\Deck\Service;
|
||||
use OCA\Deck\Activity\ActivityManager;
|
||||
use OCA\Deck\Activity\ChangeSet;
|
||||
use OCA\Deck\Db\AssignmentMapper;
|
||||
use OCA\Deck\Db\Board;
|
||||
use OCA\Deck\Db\Card;
|
||||
use OCA\Deck\Db\CardMapper;
|
||||
use OCA\Deck\Db\Acl;
|
||||
@@ -116,9 +117,20 @@ class CardService {
|
||||
return $cards;
|
||||
}
|
||||
|
||||
public function search($boardIds, $term) {
|
||||
$cards = $this->cardMapper->search($boardIds, $term);
|
||||
return $cards;
|
||||
public function search(string $term, int $limit = null, int $offset = null): array {
|
||||
$boards = $this->boardService->getUserBoards();
|
||||
$boardIds = array_map(static function (Board $board) {
|
||||
return $board->getId();
|
||||
}, $boards);
|
||||
return $this->cardMapper->search($boardIds, $term, $limit, $offset);
|
||||
}
|
||||
|
||||
public function searchRaw(string $term, int $limit = null, int $offset = null): array {
|
||||
$boards = $this->boardService->getUserBoards();
|
||||
$boardIds = array_map(static function (Board $board) {
|
||||
return $board->getId();
|
||||
}, $boards);
|
||||
return $this->cardMapper->searchRaw($boardIds, $term, $limit, $offset);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,6 +150,11 @@ class CardService {
|
||||
$card = $this->cardMapper->find($cardId);
|
||||
$assignedUsers = $this->assignedUsersMapper->findAll($card->getId());
|
||||
$attachments = $this->attachmentService->findAll($cardId, true);
|
||||
if (\OC::$server->getRequest()->getParam('apiVersion') === '1.0') {
|
||||
$attachments = array_filter($attachments, function ($attachment) {
|
||||
return $attachment->getType() === 'deck_file';
|
||||
});
|
||||
}
|
||||
$card->setAssignedUsers($assignedUsers);
|
||||
$card->setAttachments($attachments);
|
||||
$this->enrich($card);
|
||||
|
||||
@@ -32,6 +32,7 @@ use OCA\Deck\NoPermissionException;
|
||||
use OCP\IConfig;
|
||||
use OCP\IGroup;
|
||||
use OCP\IGroupManager;
|
||||
use OCP\IUserSession;
|
||||
|
||||
class ConfigService {
|
||||
public const SETTING_BOARD_NOTIFICATION_DUE_OFF = 'off';
|
||||
@@ -46,9 +47,10 @@ class ConfigService {
|
||||
public function __construct(
|
||||
IConfig $config,
|
||||
IGroupManager $groupManager,
|
||||
$userId
|
||||
IUserSession $userSession
|
||||
) {
|
||||
$this->userId = $userId;
|
||||
// Session is required here in order to make the tests properly inject the userId later on
|
||||
$this->userId = $userSession->getUser() ? $userSession->getUser()->getUID() : null;
|
||||
$this->groupManager = $groupManager;
|
||||
$this->config = $config;
|
||||
}
|
||||
@@ -148,4 +150,8 @@ class ConfigService {
|
||||
}, $groups);
|
||||
return array_filter($groups);
|
||||
}
|
||||
|
||||
public function getAttachmentFolder(): string {
|
||||
return $this->config->getUserValue($this->userId, 'deck', 'attachment_folder', '/Deck');
|
||||
}
|
||||
}
|
||||
|
||||
269
lib/Service/FilesAppService.php
Normal file
269
lib/Service/FilesAppService.php
Normal file
@@ -0,0 +1,269 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\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\IRootFolder;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IL10N;
|
||||
use OCP\IPreview;
|
||||
use OCP\IRequest;
|
||||
use OCP\Share;
|
||||
use OCP\Share\IManager;
|
||||
use OCP\Share\IShare;
|
||||
|
||||
class FilesAppService implements IAttachmentService, ICustomAttachmentService {
|
||||
private $request;
|
||||
private $rootFolder;
|
||||
private $shareProvider;
|
||||
private $shareManager;
|
||||
private $userId;
|
||||
private $configService;
|
||||
private $l10n;
|
||||
private $preview;
|
||||
private $permissionService;
|
||||
|
||||
public function __construct(
|
||||
IRequest $request,
|
||||
IL10N $l10n,
|
||||
IRootFolder $rootFolder,
|
||||
IManager $shareManager,
|
||||
ConfigService $configService,
|
||||
DeckShareProvider $shareProvider,
|
||||
IPreview $preview,
|
||||
PermissionService $permissionService,
|
||||
string $userId = null
|
||||
) {
|
||||
$this->request = $request;
|
||||
$this->l10n = $l10n;
|
||||
$this->rootFolder = $rootFolder;
|
||||
$this->configService = $configService;
|
||||
$this->shareProvider = $shareProvider;
|
||||
$this->shareManager = $shareManager;
|
||||
$this->userId = $userId;
|
||||
$this->preview = $preview;
|
||||
}
|
||||
|
||||
public function listAttachments(int $cardId): array {
|
||||
$shares = $this->shareProvider->getSharedWithByType($cardId, IShare::TYPE_DECK, -1, 0);
|
||||
$shares = array_filter($shares, function ($share) {
|
||||
return $share->getPermissions() > 0;
|
||||
});
|
||||
return array_map(function (IShare $share) use ($cardId) {
|
||||
$file = $share->getNode();
|
||||
$attachment = new Attachment();
|
||||
$attachment->setType('file');
|
||||
$attachment->setId((int)$share->getId());
|
||||
$attachment->setCardId($cardId);
|
||||
$attachment->setCreatedBy($share->getSharedBy());
|
||||
$attachment->setData($file->getName());
|
||||
$attachment->setLastModified($file->getMTime());
|
||||
$attachment->setCreatedAt($share->getShareTime()->getTimestamp());
|
||||
$attachment->setDeletedAt(0);
|
||||
return $attachment;
|
||||
}, $shares);
|
||||
}
|
||||
|
||||
public function getAttachmentCount(int $cardId): int {
|
||||
/** @var IDBConnection $qb */
|
||||
$db = \OC::$server->getDatabaseConnection();
|
||||
$qb = $db->getQueryBuilder();
|
||||
$qb->select('s.id', 'f.fileid', 'f.path')
|
||||
->selectAlias('st.id', 'storage_string_id')
|
||||
->from('share', 's')
|
||||
->leftJoin('s', 'filecache', 'f', $qb->expr()->eq('s.file_source', 'f.fileid'))
|
||||
->leftJoin('f', 'storages', 'st', $qb->expr()->eq('f.storage', 'st.numeric_id'))
|
||||
->andWhere($qb->expr()->eq('s.share_type', $qb->createNamedParameter(IShare::TYPE_DECK)))
|
||||
->andWhere($qb->expr()->eq('s.share_with', $qb->createNamedParameter($cardId)))
|
||||
->andWhere($qb->expr()->isNull('s.parent'))
|
||||
->andWhere($qb->expr()->orX(
|
||||
$qb->expr()->eq('s.item_type', $qb->createNamedParameter('file')),
|
||||
$qb->expr()->eq('s.item_type', $qb->createNamedParameter('folder'))
|
||||
));
|
||||
|
||||
$count = 0;
|
||||
$cursor = $qb->execute();
|
||||
while ($data = $cursor->fetch()) {
|
||||
if ($this->shareProvider->isAccessibleResult($data)) {
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
$cursor->closeCursor();
|
||||
return $count;
|
||||
}
|
||||
|
||||
public function extendData(Attachment $attachment) {
|
||||
$userFolder = $this->rootFolder->getUserFolder($this->userId);
|
||||
$share = $this->shareProvider->getShareById($attachment->getId());
|
||||
$file = $share->getNode();
|
||||
$attachment->setExtendedData([
|
||||
'path' => $userFolder->getRelativePath($file->getPath()),
|
||||
'fileid' => $file->getId(),
|
||||
'data' => $file->getName(),
|
||||
'filesize' => $file->getSize(),
|
||||
'mimetype' => $file->getMimeType(),
|
||||
'info' => pathinfo($file->getName()),
|
||||
'hasPreview' => $this->preview->isAvailable($file),
|
||||
'permissions' => $share->getPermissions(),
|
||||
]);
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
public function display(Attachment $attachment) {
|
||||
try {
|
||||
$share = $this->shareProvider->getShareById($attachment->getId());
|
||||
} catch (Share\Exceptions\ShareNotFound $e) {
|
||||
throw new NotFoundException('File not found');
|
||||
}
|
||||
$file = $share->getNode();
|
||||
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());
|
||||
return $response;
|
||||
}
|
||||
|
||||
public function create(Attachment $attachment) {
|
||||
$file = $this->getUploadedFile();
|
||||
$fileName = $file['name'];
|
||||
|
||||
$userFolder = $this->rootFolder->getUserFolder($this->userId);
|
||||
try {
|
||||
$folder = $userFolder->get($this->configService->getAttachmentFolder());
|
||||
} catch (NotFoundException $e) {
|
||||
$folder = $userFolder->newFolder($this->configService->getAttachmentFolder());
|
||||
}
|
||||
|
||||
$fileName = $folder->getNonExistingName($fileName);
|
||||
$target = $folder->newFile($fileName);
|
||||
$content = fopen($file['tmp_name'], 'rb');
|
||||
if ($content === false) {
|
||||
throw new StatusException('Could not read file');
|
||||
}
|
||||
$target->putContent($content);
|
||||
fclose($content);
|
||||
|
||||
$share = $this->shareManager->newShare();
|
||||
$share->setNode($target);
|
||||
$share->setShareType(ISHARE::TYPE_DECK);
|
||||
$share->setSharedWith((string)$attachment->getCardId());
|
||||
$share->setPermissions(Constants::PERMISSION_READ);
|
||||
$share->setSharedBy($this->userId);
|
||||
$share = $this->shareManager->createShare($share);
|
||||
$attachment->setId((int)$share->getId());
|
||||
$attachment->setData($target->getName());
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
* @throws StatusException
|
||||
*/
|
||||
private function getUploadedFile() {
|
||||
$file = $this->request->getUploadedFile('file');
|
||||
$error = null;
|
||||
$phpFileUploadErrors = [
|
||||
UPLOAD_ERR_OK => $this->l10n->t('The file was uploaded'),
|
||||
UPLOAD_ERR_INI_SIZE => $this->l10n->t('The uploaded file exceeds the upload_max_filesize directive in php.ini'),
|
||||
UPLOAD_ERR_FORM_SIZE => $this->l10n->t('The uploaded file exceeds the MAX_FILE_SIZE directive that was specified in the HTML form'),
|
||||
UPLOAD_ERR_PARTIAL => $this->l10n->t('The file was only partially uploaded'),
|
||||
UPLOAD_ERR_NO_FILE => $this->l10n->t('No file was uploaded'),
|
||||
UPLOAD_ERR_NO_TMP_DIR => $this->l10n->t('Missing a temporary folder'),
|
||||
UPLOAD_ERR_CANT_WRITE => $this->l10n->t('Could not write file to disk'),
|
||||
UPLOAD_ERR_EXTENSION => $this->l10n->t('A PHP extension stopped the file upload'),
|
||||
];
|
||||
|
||||
if (empty($file)) {
|
||||
$error = $this->l10n->t('No file uploaded or file size exceeds maximum of %s', [\OCP\Util::humanFileSize(\OCP\Util::uploadLimit())]);
|
||||
}
|
||||
if (!empty($file) && array_key_exists('error', $file) && $file['error'] !== UPLOAD_ERR_OK) {
|
||||
$error = $phpFileUploadErrors[$file['error']];
|
||||
}
|
||||
if ($error !== null) {
|
||||
throw new StatusException($error);
|
||||
}
|
||||
return $file;
|
||||
}
|
||||
|
||||
public function update(Attachment $attachment) {
|
||||
$share = $this->shareProvider->getShareById($attachment->getId());
|
||||
$target = $share->getNode();
|
||||
$file = $this->getUploadedFile();
|
||||
$fileName = $file['name'];
|
||||
$attachment->setData($fileName);
|
||||
|
||||
$content = fopen($file['tmp_name'], 'rb');
|
||||
if ($content === false) {
|
||||
throw new StatusException('Could not read file');
|
||||
}
|
||||
$target->putContent($content);
|
||||
fclose($content);
|
||||
|
||||
$attachment->setLastModified(time());
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
public function delete(Attachment $attachment) {
|
||||
$share = $this->shareProvider->getShareById($attachment->getId());
|
||||
$file = $share->getNode();
|
||||
$attachment->setData($file->getName());
|
||||
|
||||
if ($file->getOwner() !== null && $file->getOwner()->getUID() === $this->userId) {
|
||||
$file->delete();
|
||||
return;
|
||||
}
|
||||
|
||||
$this->shareManager->deleteFromSelf($share, $this->userId);
|
||||
}
|
||||
|
||||
public function allowUndo() {
|
||||
return false;
|
||||
}
|
||||
|
||||
public function markAsDeleted(Attachment $attachment) {
|
||||
throw new \Exception('Not implemented');
|
||||
}
|
||||
}
|
||||
39
lib/Service/ICustomAttachmentService.php
Normal file
39
lib/Service/ICustomAttachmentService.php
Normal file
@@ -0,0 +1,39 @@
|
||||
<?php
|
||||
/*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace OCA\Deck\Service;
|
||||
|
||||
/**
|
||||
* Interface to implement in case attachments are handled by a different backend than
|
||||
* then oc_deck_attachments table, e.g. for file sharing. When this interface is used
|
||||
* for implementing an attachment handler no backlink will be stored in the deck attachments
|
||||
* table and it is up to the implementation to track attachment to card relation.
|
||||
*/
|
||||
interface ICustomAttachmentService {
|
||||
public function listAttachments(int $cardId): array;
|
||||
|
||||
public function getAttachmentCount(int $cardId): int;
|
||||
}
|
||||
@@ -142,7 +142,7 @@ class PermissionService {
|
||||
}
|
||||
|
||||
if ($permission === Acl::PERMISSION_SHARE && $this->shareManager->sharingDisabledForUser($this->userId)) {
|
||||
return false;
|
||||
throw new NoPermissionException('Permission denied');
|
||||
}
|
||||
|
||||
if ($this->userIsBoardOwner($boardId, $userId)) {
|
||||
|
||||
1048
lib/Sharing/DeckShareProvider.php
Normal file
1048
lib/Sharing/DeckShareProvider.php
Normal file
File diff suppressed because it is too large
Load Diff
112
lib/Sharing/Listener.php
Normal file
112
lib/Sharing/Listener.php
Normal file
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
/*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace OCA\Deck\Sharing;
|
||||
|
||||
use OC\Files\Filesystem;
|
||||
use OCA\Deck\Service\ConfigService;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\Share\Events\VerifyMountPointEvent;
|
||||
use OCP\Share\IShare;
|
||||
use Symfony\Component\EventDispatcher\GenericEvent;
|
||||
|
||||
class Listener {
|
||||
|
||||
/** @var ConfigService */
|
||||
private $configService;
|
||||
|
||||
public function __construct(ConfigService $configService) {
|
||||
$this->configService = $configService;
|
||||
}
|
||||
|
||||
public function register(IEventDispatcher $dispatcher): void {
|
||||
/**
|
||||
* @psalm-suppress UndefinedClass
|
||||
*/
|
||||
$dispatcher->addListener('OCP\Share::preShare', [self::class, 'listenPreShare'], 1000);
|
||||
$dispatcher->addListener(VerifyMountPointEvent::class, [self::class, 'listenVerifyMountPointEvent'], 1000);
|
||||
}
|
||||
|
||||
public static function listenPreShare(GenericEvent $event): void {
|
||||
/** @var self $listener */
|
||||
$listener = \OC::$server->query(self::class);
|
||||
$listener->overwriteShareTarget($event);
|
||||
}
|
||||
|
||||
public static function listenVerifyMountPointEvent(VerifyMountPointEvent $event): void {
|
||||
/** @var self $listener */
|
||||
$listener = \OC::$server->query(self::class);
|
||||
$listener->overwriteMountPoint($event);
|
||||
}
|
||||
|
||||
public function overwriteShareTarget(GenericEvent $event): void {
|
||||
/** @var IShare $share */
|
||||
$share = $event->getSubject();
|
||||
|
||||
if ($share->getShareType() !== IShare::TYPE_DECK
|
||||
&& $share->getShareType() !== DeckShareProvider::SHARE_TYPE_DECK_USER) {
|
||||
return;
|
||||
}
|
||||
|
||||
$target = DeckShareProvider::DECK_FOLDER_PLACEHOLDER . '/' . $share->getNode()->getName();
|
||||
$target = Filesystem::normalizePath($target);
|
||||
$share->setTarget($target);
|
||||
}
|
||||
|
||||
public function overwriteMountPoint(VerifyMountPointEvent $event): void {
|
||||
$share = $event->getShare();
|
||||
$view = $event->getView();
|
||||
|
||||
if ($share->getShareType() !== IShare::TYPE_DECK
|
||||
&& $share->getShareType() !== DeckShareProvider::SHARE_TYPE_DECK_USER) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($event->getParent() === DeckShareProvider::DECK_FOLDER_PLACEHOLDER) {
|
||||
try {
|
||||
$userId = $view->getOwner('/');
|
||||
} catch (\Exception $e) {
|
||||
// If we fail to get the owner of the view from the cache,
|
||||
// e.g. because the user never logged in but a cron job runs
|
||||
// We fallback to calculating the owner from the root of the view:
|
||||
if (substr_count($view->getRoot(), '/') >= 2) {
|
||||
// /37c09aa0-1b92-4cf6-8c66-86d8cac8c1d0/files
|
||||
[, $userId, ] = explode('/', $view->getRoot(), 3);
|
||||
} else {
|
||||
// Something weird is going on, we can't fallback more
|
||||
// so for now we don't overwrite the share path ¯\_(ツ)_/¯
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$parent = $this->configService->getAttachmentFolder();
|
||||
$event->setParent($parent);
|
||||
if (!$event->getView()->is_dir($parent)) {
|
||||
$event->getView()->mkdir($parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
118
lib/Sharing/ShareAPIHelper.php
Normal file
118
lib/Sharing/ShareAPIHelper.php
Normal file
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
/*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
|
||||
namespace OCA\Deck\Sharing;
|
||||
|
||||
use OCA\Deck\Db\Acl;
|
||||
use OCA\Deck\Db\CardMapper;
|
||||
use OCA\Deck\NoPermissionException;
|
||||
use OCA\Deck\Service\PermissionService;
|
||||
use OCP\AppFramework\OCS\OCSNotFoundException;
|
||||
use OCP\AppFramework\Utility\ITimeFactory;
|
||||
use OCP\IL10N;
|
||||
use OCP\IURLGenerator;
|
||||
use OCP\Share\IShare;
|
||||
|
||||
class ShareAPIHelper {
|
||||
private $urlGenerator;
|
||||
private $timeFactory;
|
||||
private $cardMapper;
|
||||
private $permissionService;
|
||||
private $l10n;
|
||||
|
||||
public function __construct(IURLGenerator $urlGenerator, ITimeFactory $timeFactory, CardMapper $cardMapper, PermissionService $permissionService, IL10N $l10n) {
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
$this->timeFactory = $timeFactory;
|
||||
$this->cardMapper = $cardMapper;
|
||||
$this->permissionService = $permissionService;
|
||||
$this->l10n = $l10n;
|
||||
}
|
||||
|
||||
public function formatShare(IShare $share): array {
|
||||
$result = [];
|
||||
$card = $this->cardMapper->find($share->getSharedWith());
|
||||
$boardId = $this->cardMapper->findBoardId($card->getId());
|
||||
$result['share_with'] = $share->getSharedWith();
|
||||
$result['share_with_displayname'] = $card->getTitle();
|
||||
$result['share_with_link'] = $this->urlGenerator->linkToRouteAbsolute('deck.page.index') . '#/board/' . $boardId . '/card/' . $card->getId();
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function createShare(IShare $share, string $shareWith, int $permissions, $expireDate) {
|
||||
$share->setSharedWith($shareWith);
|
||||
$share->setPermissions($permissions);
|
||||
|
||||
if ($expireDate !== '') {
|
||||
try {
|
||||
$expireDate = $this->parseDate($expireDate);
|
||||
$share->setExpirationDate($expireDate);
|
||||
} catch (\Exception $e) {
|
||||
throw new OCSNotFoundException($this->l10n->t('Invalid date, date format must be YYYY-MM-DD'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make sure that the passed date is valid ISO 8601
|
||||
* So YYYY-MM-DD
|
||||
* If not throw an exception
|
||||
*
|
||||
* Copied from \OCA\Files_Sharing\Controller\ShareAPIController::parseDate.
|
||||
*
|
||||
* @param string $expireDate
|
||||
* @return \DateTime
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function parseDate(string $expireDate): \DateTime {
|
||||
try {
|
||||
$date = $this->timeFactory->getDateTime($expireDate);
|
||||
} catch (\Exception $e) {
|
||||
throw new \Exception('Invalid date. Format must be YYYY-MM-DD');
|
||||
}
|
||||
|
||||
$date->setTime(0, 0, 0);
|
||||
|
||||
return $date;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given user can access the given room share or not.
|
||||
*
|
||||
* A user can access a room share only if she is a participant of the room.
|
||||
*
|
||||
* @param IShare $share
|
||||
* @param string $user
|
||||
* @return bool
|
||||
*/
|
||||
public function canAccessShare(IShare $share, string $user): bool {
|
||||
try {
|
||||
$this->permissionService->checkPermission($this->cardMapper, $share->getSharedWith(), Acl::PERMISSION_READ, $user);
|
||||
} catch (NoPermissionException $e) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -21,21 +21,37 @@
|
||||
-->
|
||||
|
||||
<template>
|
||||
<Modal :title="t('deck', 'Select the card to link to a project')" @close="close">
|
||||
<Modal class="card-selector" @close="close">
|
||||
<div id="modal-inner" :class="{ 'icon-loading': loading }">
|
||||
<h3>{{ title }}</h3>
|
||||
<Multiselect v-model="selectedBoard"
|
||||
:placeholder="t('deck', 'Select a board')"
|
||||
:options="boards"
|
||||
:disabled="loading"
|
||||
label="title"
|
||||
@select="fetchCardsFromBoard" />
|
||||
@select="fetchCardsFromBoard">
|
||||
<template slot="singleLabel" slot-scope="props">
|
||||
<span>
|
||||
<span :style="{ 'backgroundColor': '#' + props.option.color }" class="board-bullet" />
|
||||
<span>{{ props.option.title }}</span>
|
||||
</span>
|
||||
</template>
|
||||
<template slot="option" slot-scope="props">
|
||||
<span>
|
||||
<span :style="{ 'backgroundColor': '#' + props.option.color }" class="board-bullet" />
|
||||
<span>{{ props.option.title }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</Multiselect>
|
||||
|
||||
<Multiselect v-model="selectedCard"
|
||||
:placeholder="t('deck', 'Select a card')"
|
||||
:options="cardsFromBoard"
|
||||
:disabled="loading || selectedBoard === ''"
|
||||
label="title" />
|
||||
|
||||
<button :disabled="!isBoardAndStackChoosen" class="primary" @click="select">
|
||||
{{ t('deck', 'Link to card') }}
|
||||
{{ action }}
|
||||
</button>
|
||||
<button @click="close">
|
||||
{{ t('deck', 'Cancel') }}
|
||||
@@ -56,6 +72,16 @@ export default {
|
||||
Modal,
|
||||
Multiselect,
|
||||
},
|
||||
props: {
|
||||
title: {
|
||||
type: String,
|
||||
default: t('deck', 'Select the card to link to a project'),
|
||||
},
|
||||
action: {
|
||||
type: String,
|
||||
default: t('deck', 'Link to card'),
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
boards: [],
|
||||
@@ -67,10 +93,7 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
isBoardAndStackChoosen() {
|
||||
if (this.selectedBoard === '' || this.selectedCard === '') {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
return !(this.selectedBoard === '' || this.selectedCard === '')
|
||||
},
|
||||
},
|
||||
beforeMount() {
|
||||
@@ -113,7 +136,12 @@ export default {
|
||||
width: 90vw;
|
||||
max-width: 400px;
|
||||
padding: 20px;
|
||||
height: 500px;
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.multiselect {
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
ul {
|
||||
@@ -129,10 +157,6 @@ export default {
|
||||
background-color: var(--color-background-dark);
|
||||
}
|
||||
|
||||
li.selected {
|
||||
border: 1px solid var(--color-primary);
|
||||
}
|
||||
|
||||
.board-bullet {
|
||||
display: inline-block;
|
||||
width: 12px;
|
||||
@@ -142,12 +166,11 @@ export default {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
li > span,
|
||||
.avatar {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
button {
|
||||
float: right;
|
||||
}
|
||||
|
||||
.card-selector::v-deep .modal-container {
|
||||
overflow: visible !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -22,17 +22,22 @@
|
||||
|
||||
<template>
|
||||
<AttachmentDragAndDrop :card-id="cardId" class="drop-upload--sidebar">
|
||||
<button class="icon-upload" @click="clickAddNewAttachmment()">
|
||||
{{ t('deck', 'Upload attachment') }}
|
||||
</button>
|
||||
<input ref="localAttachments"
|
||||
<div class="button-group">
|
||||
<button class="icon-upload" @click="uploadNewFile()">
|
||||
{{ t('deck', 'Upload new files') }}
|
||||
</button>
|
||||
<button class="icon-folder" @click="shareFromFiles()">
|
||||
{{ t('deck', 'Share from Files') }}
|
||||
</button>
|
||||
</div>
|
||||
<input ref="filesAttachment"
|
||||
type="file"
|
||||
style="display: none;"
|
||||
multiple
|
||||
@change="handleUploadFile">
|
||||
<ul class="attachment-list">
|
||||
<li v-for="attachment in uploadQueue" :key="attachment.name" class="attachment">
|
||||
<a class="fileicon" :style="mimetypeForAttachment('none')" />
|
||||
<a class="fileicon" :style="mimetypeForAttachment()" />
|
||||
<div class="details">
|
||||
<a>
|
||||
<div class="filename">
|
||||
@@ -45,9 +50,11 @@
|
||||
<li v-for="attachment in attachments"
|
||||
:key="attachment.id"
|
||||
class="attachment">
|
||||
<a class="fileicon" :style="mimetypeForAttachment(attachment.extendedData.mimetype)" :href="attachmentUrl(attachment)" />
|
||||
<a class="fileicon"
|
||||
:style="mimetypeForAttachment(attachment)"
|
||||
@click.prevent="showViewer(attachment)" />
|
||||
<div class="details">
|
||||
<a :href="attachmentUrl(attachment)" target="_blank">
|
||||
<a @click.prevent="showViewer(attachment)">
|
||||
<div class="filename">
|
||||
<span class="basename">{{ attachment.data }}</span>
|
||||
</div>
|
||||
@@ -61,12 +68,18 @@
|
||||
{{ t('deck', 'Add this attachment') }}
|
||||
</ActionButton>
|
||||
</Actions>
|
||||
<Actions v-if="removable">
|
||||
<ActionButton v-if="attachment.deletedAt === 0" icon="icon-delete" @click="$emit('deleteAttachment', attachment)">
|
||||
{{ t('deck', 'Delete Attachment') }}
|
||||
<Actions v-if="removable" :force-menu="true">
|
||||
<ActionLink v-if="attachment.extendedData.fileid" icon="icon-folder" :href="internalLink(attachment)">
|
||||
{{ t('deck', 'Show in files') }}
|
||||
</ActionLink>
|
||||
<ActionButton v-if="attachment.extendedData.fileid" icon="icon-delete" @click="unshareAttachment(attachment)">
|
||||
{{ t('deck', 'Unshare file') }}
|
||||
</ActionButton>
|
||||
|
||||
<ActionButton v-else icon="icon-history" @click="$emit('restoreAttachment', attachment)">
|
||||
<ActionButton v-if="!attachment.extendedData.fileid && attachment.deletedAt === 0" icon="icon-delete" @click="$emit('deleteAttachment', attachment)">
|
||||
{{ t('deck', 'Delete Attachment') }}
|
||||
</ActionButton>
|
||||
<ActionButton v-else-if="!attachment.extendedData.fileid" icon="icon-history" @click="$emit('restoreAttachment', attachment)">
|
||||
{{ t('deck', 'Restore Attachment') }}
|
||||
</ActionButton>
|
||||
</Actions>
|
||||
@@ -76,21 +89,31 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { Actions, ActionButton } from '@nextcloud/vue'
|
||||
import axios from '@nextcloud/axios'
|
||||
import { Actions, ActionButton, ActionLink } from '@nextcloud/vue'
|
||||
import AttachmentDragAndDrop from '../AttachmentDragAndDrop'
|
||||
import relativeDate from '../../mixins/relativeDate'
|
||||
import { formatFileSize } from '@nextcloud/files'
|
||||
import { generateUrl } from '@nextcloud/router'
|
||||
import { generateUrl, generateOcsUrl } from '@nextcloud/router'
|
||||
import { mapState } from 'vuex'
|
||||
import { loadState } from '@nextcloud/initial-state'
|
||||
import attachmentUpload from '../../mixins/attachmentUpload'
|
||||
import { getFilePickerBuilder } from '@nextcloud/dialogs'
|
||||
const maxUploadSizeState = loadState('deck', 'maxUploadSize')
|
||||
|
||||
const picker = getFilePickerBuilder(t('deck', 'File to share'))
|
||||
.setMultiSelect(false)
|
||||
.setModal(true)
|
||||
.setType(1)
|
||||
.allowDirectories()
|
||||
.build()
|
||||
|
||||
export default {
|
||||
name: 'AttachmentList',
|
||||
components: {
|
||||
Actions,
|
||||
ActionButton,
|
||||
ActionLink,
|
||||
AttachmentDragAndDrop,
|
||||
},
|
||||
mixins: [relativeDate, attachmentUpload],
|
||||
@@ -120,20 +143,29 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
attachments() {
|
||||
return [...this.$store.getters.attachmentsByCard(this.cardId)].sort((a, b) => b.id - a.id)
|
||||
return [...this.$store.getters.attachmentsByCard(this.cardId)].filter(attachment => attachment.deletedAt >= 0).sort((a, b) => b.id - a.id)
|
||||
},
|
||||
mimetypeForAttachment() {
|
||||
return (mimetype) => {
|
||||
const url = OC.MimeType.getIconUrl(mimetype)
|
||||
return (attachment) => {
|
||||
if (!attachment) {
|
||||
return {}
|
||||
}
|
||||
const url = attachment.extendedData.hasPreview ? this.attachmentPreview(attachment) : OC.MimeType.getIconUrl(attachment.extendedData.mimetype)
|
||||
const styles = {
|
||||
'background-image': `url("${url}")`,
|
||||
}
|
||||
return styles
|
||||
}
|
||||
},
|
||||
attachmentPreview() {
|
||||
return (attachment) => (attachment.extendedData.fileid ? generateUrl(`/core/preview?fileId=${attachment.extendedData.fileid}&x=64&y=64&a=true`) : null)
|
||||
},
|
||||
attachmentUrl() {
|
||||
return (attachment) => generateUrl(`/apps/deck/cards/${attachment.cardId}/attachment/${attachment.id}`)
|
||||
},
|
||||
internalLink() {
|
||||
return (attachment) => generateUrl('/f/' + attachment.extendedData.fileid)
|
||||
},
|
||||
formattedFileSize() {
|
||||
return (filesize) => formatFileSize(filesize)
|
||||
},
|
||||
@@ -151,29 +183,78 @@ export default {
|
||||
}
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.$store.dispatch('fetchAttachments', this.cardId)
|
||||
watch: {
|
||||
cardId: {
|
||||
immediate: true,
|
||||
handler() {
|
||||
this.$store.dispatch('fetchAttachments', this.cardId)
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
handleUploadFile(event) {
|
||||
const files = event.target.files ?? []
|
||||
for (const file of files) {
|
||||
this.onLocalAttachmentSelected(file)
|
||||
this.onLocalAttachmentSelected(file, 'file')
|
||||
}
|
||||
event.target.value = ''
|
||||
},
|
||||
uploadNewFile() {
|
||||
this.$refs.filesAttachment.click()
|
||||
},
|
||||
shareFromFiles() {
|
||||
picker.pick()
|
||||
.then(async(path) => {
|
||||
console.debug(`path ${path} selected for sharing`)
|
||||
if (!path.startsWith('/')) {
|
||||
throw new Error(t('files', 'Invalid path selected'))
|
||||
}
|
||||
|
||||
axios.post(generateOcsUrl('apps/files_sharing/api/v1', 2) + 'shares', {
|
||||
path,
|
||||
shareType: 12,
|
||||
shareWith: '' + this.cardId,
|
||||
}).then(() => {
|
||||
this.$store.dispatch('fetchAttachments', this.cardId)
|
||||
})
|
||||
})
|
||||
},
|
||||
unshareAttachment(attachment) {
|
||||
this.$store.dispatch('unshareAttachment', attachment)
|
||||
},
|
||||
clickAddNewAttachmment() {
|
||||
this.$refs.localAttachments.click()
|
||||
},
|
||||
showViewer(attachment) {
|
||||
if (attachment.extendedData.fileid && window.OCA.Viewer.availableHandlers.map(handler => handler.mimes).flat().includes(attachment.extendedData.mimetype)) {
|
||||
window.OCA.Viewer.open(attachment.extendedData.path)
|
||||
return
|
||||
}
|
||||
|
||||
if (attachment.extendedData.fileid) {
|
||||
window.location = generateUrl('/f/' + attachment.extendedData.fileid)
|
||||
return
|
||||
}
|
||||
|
||||
window.location = generateUrl(`/apps/deck/cards/${attachment.cardId}/attachment/${attachment.id}`)
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.icon-upload {
|
||||
padding-left: 35px;
|
||||
background-position: 10px center;
|
||||
.button-group {
|
||||
display: flex;
|
||||
|
||||
.icon-upload, .icon-folder {
|
||||
padding-left: 44px;
|
||||
background-position: 16px center;
|
||||
flex-grow: 1;
|
||||
height: 44px;
|
||||
margin-bottom: 12px;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.attachment-list {
|
||||
|
||||
@@ -140,7 +140,17 @@ export default {
|
||||
}
|
||||
},
|
||||
attachmentUrl() {
|
||||
return (attachment) => generateUrl(`/apps/deck/cards/${attachment.cardId}/attachment/${attachment.id}`)
|
||||
return (attachment) => {
|
||||
if (attachment.extendedData.fileid) {
|
||||
return generateUrl('/f/' + attachment.extendedData.fileid)
|
||||
}
|
||||
return generateUrl(`/apps/deck/cards/${attachment.cardId}/attachment/${attachment.id}`)
|
||||
}
|
||||
},
|
||||
attachmentPreview() {
|
||||
return (attachment) => (attachment.extendedData.fileid
|
||||
? generateUrl(`/core/preview?fileId=${attachment.extendedData.fileid}&x=600&y=600&a=true`)
|
||||
: generateUrl(`/apps/deck/cards/${attachment.cardId}/attachment/${attachment.id}`))
|
||||
},
|
||||
formattedFileSize() {
|
||||
return (filesize) => formatFileSize(filesize)
|
||||
@@ -169,12 +179,15 @@ export default {
|
||||
addAttachment(attachment) {
|
||||
const descString = this.$refs.markdownEditor.easymde.value()
|
||||
let embed = ''
|
||||
if (attachment.extendedData.mimetype.includes('image')) {
|
||||
if ((attachment.type === 'file' && attachment.extendedData.hasPreview) || attachment.extendedData.mimetype.includes('image')) {
|
||||
embed = '!'
|
||||
}
|
||||
const attachmentString = embed + '[📎 ' + attachment.data + '](' + this.attachmentUrl(attachment) + ')'
|
||||
this.$refs.markdownEditor.easymde.value(descString + '\n' + attachmentString)
|
||||
const attachmentString = embed + '[📎 ' + attachment.data + '](' + this.attachmentPreview(attachment) + ')'
|
||||
const newContent = descString + '\n' + attachmentString
|
||||
this.$refs.markdownEditor.easymde.value(newContent)
|
||||
this.description = newContent
|
||||
this.modalShow = false
|
||||
this.updateDescription()
|
||||
},
|
||||
clickedPreview(e) {
|
||||
if (e.target.getAttribute('type') === 'checkbox') {
|
||||
|
||||
@@ -24,9 +24,8 @@ import Vue from 'vue'
|
||||
|
||||
import BoardSelector from './BoardSelector'
|
||||
import CardSelector from './CardSelector'
|
||||
|
||||
import './../css/collections.css'
|
||||
|
||||
import FileSharingPicker from './views/FileSharingPicker'
|
||||
// eslint-disable-next-line
|
||||
__webpack_nonce__ = btoa(OC.requestToken);
|
||||
// eslint-disable-next-line
|
||||
@@ -34,7 +33,15 @@ __webpack_public_path__ = OC.linkTo('deck', 'js/');
|
||||
|
||||
Vue.prototype.t = t
|
||||
Vue.prototype.n = n
|
||||
Vue.prototype.OC = OC;
|
||||
Vue.prototype.OC = OC
|
||||
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
if (OCA.Sharing && OCA.Sharing.ShareSearch) {
|
||||
OCA.Sharing.ShareSearch.addNewResult(FileSharingPicker)
|
||||
} else {
|
||||
console.error('OCA.Sharing.ShareSearch not ready')
|
||||
}
|
||||
});
|
||||
|
||||
((function(OCP) {
|
||||
|
||||
|
||||
@@ -32,7 +32,7 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
async onLocalAttachmentSelected(file) {
|
||||
async onLocalAttachmentSelected(file, type) {
|
||||
if (this.maxUploadSize > 0 && file.size > this.maxUploadSize) {
|
||||
showError(
|
||||
t('deck', 'Failed to upload {name}', { name: file.name }) + ' - '
|
||||
@@ -45,7 +45,7 @@ export default {
|
||||
this.$set(this.uploadQueue, file.name, { name: file.name, progress: 0 })
|
||||
const bodyFormData = new FormData()
|
||||
bodyFormData.append('cardId', this.cardId)
|
||||
bodyFormData.append('type', 'deck_file')
|
||||
bodyFormData.append('type', type)
|
||||
bodyFormData.append('file', file)
|
||||
await queue.add(async() => {
|
||||
try {
|
||||
@@ -63,7 +63,7 @@ export default {
|
||||
this.overwriteAttachment = err.response.data.data
|
||||
this.modalShow = true
|
||||
} else {
|
||||
showError(err.response.data.message)
|
||||
showError(err.response.data ? err.response.data.message : 'Failed to upload file')
|
||||
}
|
||||
}
|
||||
this.$delete(this.uploadQueue, file.name)
|
||||
@@ -78,7 +78,7 @@ export default {
|
||||
bodyFormData.append('file', this.file)
|
||||
this.$store.dispatch('updateAttachment', {
|
||||
cardId: this.cardId,
|
||||
attachmentId: this.overwriteAttachment.id,
|
||||
attachment: this.overwriteAttachment,
|
||||
formData: bodyFormData,
|
||||
})
|
||||
|
||||
|
||||
@@ -47,10 +47,10 @@ export class AttachmentApi {
|
||||
return response.data
|
||||
}
|
||||
|
||||
async updateAttachment({ cardId, attachmentId, formData }) {
|
||||
async updateAttachment({ cardId, attachment, formData }) {
|
||||
const response = await axios({
|
||||
method: 'POST',
|
||||
url: this.url(`/cards/${cardId}/attachment/${attachmentId}`),
|
||||
url: this.url(`/cards/${cardId}/attachment/${attachment.type}:${attachment.id}`),
|
||||
data: formData,
|
||||
})
|
||||
return response.data
|
||||
@@ -59,14 +59,14 @@ export class AttachmentApi {
|
||||
async deleteAttachment(attachment) {
|
||||
await axios({
|
||||
method: 'DELETE',
|
||||
url: this.url(`/cards/${attachment.cardId}/attachment/${attachment.id}`),
|
||||
url: this.url(`/cards/${attachment.cardId}/attachment/${attachment.type}:${attachment.id}`),
|
||||
})
|
||||
}
|
||||
|
||||
async restoreAttachment(attachment) {
|
||||
const response = await axios({
|
||||
method: 'GET',
|
||||
url: this.url(`/cards/${attachment.cardId}/attachment/${attachment.id}/restore`),
|
||||
url: this.url(`/cards/${attachment.cardId}/attachment/${attachment.type}:${attachment.id}/restore`),
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
@@ -74,7 +74,7 @@ export class AttachmentApi {
|
||||
async displayAttachment(attachment) {
|
||||
const response = await axios({
|
||||
method: 'GET',
|
||||
url: this.url(`/cards/${attachment.cardId}/attachment/${attachment.id}`),
|
||||
url: this.url(`/cards/${attachment.cardId}/attachment/${attachment.type}:${attachment.id}`),
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
43
src/services/SharingApi.js
Normal file
43
src/services/SharingApi.js
Normal file
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* @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 axios from '@nextcloud/axios'
|
||||
import { generateOcsUrl } from '@nextcloud/router'
|
||||
|
||||
const shareUrl = generateOcsUrl('apps/files_sharing/api/v1', 2) + 'shares'
|
||||
|
||||
const createShare = async function({ path, permissions, shareType, shareWith, publicUpload, password, sendPasswordByTalk, expireDate, label }) {
|
||||
try {
|
||||
const request = await axios.post(shareUrl, { path, permissions, shareType, shareWith, publicUpload, password, sendPasswordByTalk, expireDate, label })
|
||||
if (!request?.data?.ocs) {
|
||||
throw request
|
||||
}
|
||||
return request
|
||||
} catch (error) {
|
||||
console.error('Error while creating share', error)
|
||||
OC.Notification.showTemporary(t('files_sharing', 'Error creating the share'), { type: 'error' })
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
createShare,
|
||||
}
|
||||
@@ -52,21 +52,28 @@ export default {
|
||||
},
|
||||
|
||||
updateAttachment(state, { cardId, attachment }) {
|
||||
const existingIndex = state.attachments[attachment.cardId].findIndex(a => a.id === attachment.id)
|
||||
const existingIndex = state.attachments[attachment.cardId].findIndex(a => a.id === attachment.id && a.type === attachment.type)
|
||||
if (existingIndex !== -1) {
|
||||
Vue.set(state.attachments[cardId], existingIndex, attachment)
|
||||
}
|
||||
},
|
||||
|
||||
deleteAttachment(state, deletedAttachment) {
|
||||
const existingIndex = state.attachments[deletedAttachment.cardId].findIndex(a => a.id === deletedAttachment.id)
|
||||
const existingIndex = state.attachments[deletedAttachment.cardId].findIndex(a => a.id === deletedAttachment.id && a.type === deletedAttachment.type)
|
||||
if (existingIndex !== -1) {
|
||||
state.attachments[deletedAttachment.cardId][existingIndex].deletedAt = Date.now() / 1000 | 0
|
||||
}
|
||||
},
|
||||
|
||||
unshareAttachment(state, deletedAttachment) {
|
||||
const existingIndex = state.attachments[deletedAttachment.cardId].findIndex(a => a.id === deletedAttachment.id && a.type === deletedAttachment.type)
|
||||
if (existingIndex !== -1) {
|
||||
state.attachments[deletedAttachment.cardId][existingIndex].deletedAt = -1
|
||||
}
|
||||
},
|
||||
|
||||
restoreAttachment(state, restoredAttachment) {
|
||||
const existingIndex = state.attachments[restoredAttachment.cardId].findIndex(a => a.id === restoredAttachment.id)
|
||||
const existingIndex = state.attachments[restoredAttachment.cardId].findIndex(a => a.id === restoredAttachment.id && a.type === restoredAttachment.type)
|
||||
if (existingIndex !== -1) {
|
||||
state.attachments[restoredAttachment.cardId][existingIndex].deletedAt = 0
|
||||
}
|
||||
@@ -85,9 +92,9 @@ export default {
|
||||
commit('cardIncreaseAttachmentCount', cardId)
|
||||
},
|
||||
|
||||
async updateAttachment({ commit }, { cardId, attachmentId, formData }) {
|
||||
const attachment = await apiClient.updateAttachment({ cardId, attachmentId, formData })
|
||||
commit('updateAttachment', { cardId, attachment })
|
||||
async updateAttachment({ commit }, { cardId, attachment, formData }) {
|
||||
const result = await apiClient.updateAttachment({ cardId, attachment, formData })
|
||||
commit('updateAttachment', { cardId, attachment: result })
|
||||
},
|
||||
|
||||
async deleteAttachment({ commit }, attachment) {
|
||||
@@ -96,6 +103,12 @@ export default {
|
||||
commit('cardDecreaseAttachmentCount', attachment.cardId)
|
||||
},
|
||||
|
||||
async unshareAttachment({ commit }, attachment) {
|
||||
await apiClient.deleteAttachment(attachment)
|
||||
commit('unshareAttachment', attachment)
|
||||
commit('cardDecreaseAttachmentCount', attachment.cardId)
|
||||
},
|
||||
|
||||
async restoreAttachment({ commit }, attachment) {
|
||||
const restoredAttachment = await apiClient.restoreAttachment(attachment)
|
||||
commit('restoreAttachment', restoredAttachment)
|
||||
|
||||
64
src/views/FileSharingPicker.js
Normal file
64
src/views/FileSharingPicker.js
Normal file
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* @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 Vue from 'vue'
|
||||
import CardSelector from '../CardSelector'
|
||||
import { createShare } from '../services/SharingApi'
|
||||
|
||||
export default {
|
||||
icon: 'icon-deck',
|
||||
displayName: t('deck', 'Share with a Deck card'),
|
||||
handler: async self => {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const container = document.createElement('div')
|
||||
container.id = 'deck-board-select'
|
||||
const body = document.getElementById('body-user')
|
||||
body.append(container)
|
||||
const SelectorView = Vue.extend(CardSelector)
|
||||
const ComponentVM = new SelectorView({
|
||||
propsData: {
|
||||
title: t('deck', 'Share {file} with a Deck card', { file: decodeURIComponent(self.fileInfo.name) }),
|
||||
action: t('deck', 'Share'),
|
||||
},
|
||||
})
|
||||
ComponentVM.$mount(container)
|
||||
ComponentVM.$root.$on('close', () => {
|
||||
ComponentVM.$el.remove()
|
||||
ComponentVM.$destroy()
|
||||
reject(new Error('Canceled'))
|
||||
})
|
||||
ComponentVM.$root.$on('select', async(id) => {
|
||||
const result = await createShare({
|
||||
path: self.fileInfo.path + '/' + self.fileInfo.name,
|
||||
shareType: 12,
|
||||
shareWith: '' + id,
|
||||
})
|
||||
ComponentVM.$el.remove()
|
||||
ComponentVM.$destroy()
|
||||
resolve(result.data.ocs.data)
|
||||
})
|
||||
|
||||
})
|
||||
},
|
||||
condition: self => true,
|
||||
}
|
||||
1
tests/data/test.txt
Normal file
1
tests/data/test.txt
Normal file
@@ -0,0 +1 @@
|
||||
Hello world
|
||||
@@ -4,9 +4,11 @@ default:
|
||||
paths:
|
||||
- '%paths.base%/../features/'
|
||||
contexts:
|
||||
- FeatureContext:
|
||||
- ServerContext:
|
||||
baseUrl: http://localhost:8080/index.php/ocs/
|
||||
admin:
|
||||
- admin
|
||||
- admin
|
||||
regular_user_password: 123456
|
||||
regular_user_password: 123456
|
||||
- BoardContext:
|
||||
baseUrl: http://localhost:8080/
|
||||
|
||||
@@ -10,27 +10,83 @@ Feature: acl
|
||||
And group "group1" exists
|
||||
Given user "user1" belongs to group "group1"
|
||||
|
||||
Scenario: Request the main frontend page
|
||||
Given Logging in using web as "user0"
|
||||
When Sending a "GET" to "/index.php/apps/deck" without requesttoken
|
||||
Then the HTTP status code should be "200"
|
||||
|
||||
Scenario: Fetch the board list
|
||||
Given Logging in using web as "user0"
|
||||
When Sending a "GET" to "/index.php/apps/deck/boards" with requesttoken
|
||||
Then the HTTP status code should be "200"
|
||||
And the Content-Type should be "application/json; charset=utf-8"
|
||||
When fetching the board list
|
||||
Then the response should have a status code "200"
|
||||
And the response Content-Type should be "application/json; charset=utf-8"
|
||||
|
||||
Scenario: Fetch board details of owned board
|
||||
Given Logging in using web as "admin"
|
||||
And creates a board named "MyPrivateAdminBoard" with color "fafafa"
|
||||
When "admin" fetches the board named "MyPrivateAdminBoard"
|
||||
Then the HTTP status code should be "200"
|
||||
And the Content-Type should be "application/json; charset=utf-8"
|
||||
When fetches the board named "MyPrivateAdminBoard"
|
||||
Then the response should have a status code "200"
|
||||
And the response Content-Type should be "application/json; charset=utf-8"
|
||||
|
||||
Scenario: Fetch board details of an other users board
|
||||
Given Logging in using web as "admin"
|
||||
And creates a board named "MyPrivateAdminBoard" with color "fafafa"
|
||||
When "user0" fetches the board named "MyPrivateAdminBoard"
|
||||
Then the HTTP status code should be "403"
|
||||
And the Content-Type should be "application/json; charset=utf-8"
|
||||
And creates a board named "MyPrivateAdminBoard" with color "ff0000"
|
||||
Given Logging in using web as "user0"
|
||||
When fetches the board named "MyPrivateAdminBoard"
|
||||
Then the response should have a status code "403"
|
||||
And the response Content-Type should be "application/json; charset=utf-8"
|
||||
|
||||
Scenario: Share a board
|
||||
Given Logging in using web as "user0"
|
||||
And creates a board named "Shared board" with color "ff0000"
|
||||
And shares the board with user "user1"
|
||||
| permissionEdit | 0 |
|
||||
| permissionShare | 0 |
|
||||
| permissionManage | 0 |
|
||||
And the response should have a status code 200
|
||||
And shares the board with user "user2"
|
||||
| permissionEdit | 1 |
|
||||
| permissionShare | 1 |
|
||||
| permissionManage | 1 |
|
||||
And the response should have a status code 200
|
||||
|
||||
Given Logging in using web as "user2"
|
||||
When fetches the board named "Shared board"
|
||||
Then the current user should have "read" permissions on the board
|
||||
And the current user should have "edit" permissions on the board
|
||||
And the current user should have "share" permissions on the board
|
||||
And the current user should have "manage" permissions on the board
|
||||
And create a stack named "Stack"
|
||||
And the response should have a status code 200
|
||||
And create a card named "Test"
|
||||
And the response should have a status code 200
|
||||
|
||||
|
||||
Given Logging in using web as "user1"
|
||||
When fetches the board named "Shared board"
|
||||
And create a card named "Test"
|
||||
And the response should have a status code 403
|
||||
Then the current user should have "read" permissions on the board
|
||||
And the current user should not have "edit" permissions on the board
|
||||
And the current user should not have "share" permissions on the board
|
||||
And the current user should not have "manage" permissions on the board
|
||||
And create a stack named "Stack"
|
||||
And the response should have a status code 403
|
||||
|
||||
|
||||
Scenario: Reshare a board
|
||||
Given Logging in using web as "user0"
|
||||
And creates a board named "Reshared board" with color "ff0000"
|
||||
And shares the board with user "user1"
|
||||
| permissionEdit | 0 |
|
||||
| permissionShare | 1 |
|
||||
| permissionManage | 0 |
|
||||
And the response should have a status code 200
|
||||
Given Logging in using web as "user1"
|
||||
When fetches the board named "Shared board"
|
||||
And shares the board with user "user2"
|
||||
| permissionEdit | 1 |
|
||||
| permissionShare | 1 |
|
||||
| permissionManage | 1 |
|
||||
And the response should have a status code 200
|
||||
Given Logging in using web as "user2"
|
||||
When fetches the board named "Shared board"
|
||||
Then the current user should have "read" permissions on the board
|
||||
And the current user should not have "edit" permissions on the board
|
||||
And the current user should have "share" permissions on the board
|
||||
And the current user should not have "manage" permissions on the board
|
||||
|
||||
154
tests/integration/features/bootstrap/BoardContext.php
Normal file
154
tests/integration/features/bootstrap/BoardContext.php
Normal file
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Behat\Gherkin\Node\TableNode;
|
||||
use PHPUnit\Framework\Assert;
|
||||
|
||||
require_once __DIR__ . '/../../vendor/autoload.php';
|
||||
|
||||
class BoardContext implements Context {
|
||||
use RequestTrait;
|
||||
|
||||
/** @var array Last board response */
|
||||
private $board = null;
|
||||
/** @var array last stack response */
|
||||
private $stack = null;
|
||||
/** @var array last card response */
|
||||
private $card = null;
|
||||
|
||||
/**
|
||||
* @Given /^creates a board named "([^"]*)" with color "([^"]*)"$/
|
||||
*/
|
||||
public function createsABoardNamedWithColor($title, $color) {
|
||||
$this->sendJSONrequest('POST', '/index.php/apps/deck/boards', [
|
||||
'title' => $title,
|
||||
'color' => $color
|
||||
]);
|
||||
$this->response->getBody()->seek(0);
|
||||
$this->board = json_decode((string)$this->response->getBody(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^fetches the board named "([^"]*)"$/
|
||||
*/
|
||||
public function fetchesTheBoardNamed($boardName) {
|
||||
$this->sendJSONrequest('GET', '/index.php/apps/deck/boards/' . $this->board['id'], []);
|
||||
$this->response->getBody()->seek(0);
|
||||
$this->board = json_decode((string)$this->response->getBody(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When shares the board with user :user
|
||||
*/
|
||||
public function sharesTheBoardWithUser($user, TableNode $permissions = null) {
|
||||
$defaults = [
|
||||
'permissionEdit' => '0',
|
||||
'permissionShare' => '0',
|
||||
'permissionManage' => '0'
|
||||
];
|
||||
$tableRows = isset($permissions) ? $permissions->getRowsHash() : [];
|
||||
$result = array_merge($defaults, $tableRows);
|
||||
$this->sendJSONrequest('POST', '/index.php/apps/deck/boards/' . $this->board['id'] . '/acl', [
|
||||
'type' => 0,
|
||||
'participant' => $user,
|
||||
'permissionEdit' => $result['permissionEdit'] === '1',
|
||||
'permissionShare' => $result['permissionShare'] === '1',
|
||||
'permissionManage' => $result['permissionManage'] === '1',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @When shares the board with group :group
|
||||
*/
|
||||
public function sharesTheBoardWithGroup($group, TableNode $permissions = null) {
|
||||
$defaults = [
|
||||
'permissionEdit' => '0',
|
||||
'permissionShare' => '0',
|
||||
'permissionManage' => '0'
|
||||
];
|
||||
$tableRows = isset($permissions) ? $permissions->getRowsHash() : [];
|
||||
$result = array_merge($defaults, $tableRows);
|
||||
$this->sendJSONrequest('POST', '/index.php/apps/deck/boards/' . $this->board['id'] . '/acl', [
|
||||
'type' => 1,
|
||||
'participant' => $group,
|
||||
'permissionEdit' => $result['permissionEdit'] === '1',
|
||||
'permissionShare' => $result['permissionShare'] === '1',
|
||||
'permissionManage' => $result['permissionManage'] === '1',
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @When /^fetching the board list$/
|
||||
*/
|
||||
public function fetchingTheBoardList() {
|
||||
$this->sendJSONrequest('GET', '/index.php/apps/deck/boards');
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^fetching the board with id "([^"]*)"$/
|
||||
*/
|
||||
public function fetchingTheBoardWithId($id) {
|
||||
$this->sendJSONrequest('GET', '/index.php/apps/deck/boards/' . $id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^create a stack named "([^"]*)"$/
|
||||
*/
|
||||
public function createAStackNamed($name) {
|
||||
$this->sendJSONrequest('POST', '/index.php/apps/deck/stacks', [
|
||||
'title' => $name,
|
||||
'boardId' => $this->board['id']
|
||||
]);
|
||||
$this->response->getBody()->seek(0);
|
||||
$this->stack = json_decode((string)$this->response->getBody(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^create a card named "([^"]*)"$/
|
||||
*/
|
||||
public function createACardNamed($name) {
|
||||
$this->sendJSONrequest('POST', '/index.php/apps/deck/cards', [
|
||||
'title' => $name,
|
||||
'stackId' => $this->stack['id']
|
||||
]);
|
||||
$this->response->getBody()->seek(0);
|
||||
$this->card = json_decode((string)$this->response->getBody(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^the current user should have "(read|edit|share|manage)" permissions on the board$/
|
||||
*/
|
||||
public function theCurrentUserShouldHavePermissionsOnTheBoard($permission) {
|
||||
Assert::assertTrue($this->getPermissionsValue($permission));
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^the current user should not have "(read|edit|share|manage)" permissions on the board$/
|
||||
*/
|
||||
public function theCurrentUserShouldNotHavePermissionsOnTheBoard($permission) {
|
||||
Assert::assertFalse($this->getPermissionsValue($permission));
|
||||
}
|
||||
|
||||
private function getPermissionsValue($permission) {
|
||||
$mapping = [
|
||||
'read' => 'PERMISSION_READ',
|
||||
'edit' => 'PERMISSION_EDIT',
|
||||
'share' => 'PERMISSION_SHARE',
|
||||
'manage' => 'PERMISSION_MANAGE',
|
||||
];
|
||||
return $this->board['permissions'][$mapping[$permission]];
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^share the file "([^"]*)" with the card$/
|
||||
*/
|
||||
public function shareWithTheCard($file) {
|
||||
$table = new TableNode([
|
||||
['path', $file],
|
||||
['shareType', 12],
|
||||
['shareWith', (string)$this->card['id']],
|
||||
]);
|
||||
$this->serverContext->creatingShare($table);
|
||||
}
|
||||
}
|
||||
@@ -1,174 +0,0 @@
|
||||
<?php
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use Behat\Gherkin\Node\TableNode;
|
||||
use GuzzleHttp\Client;
|
||||
use Behat\Gherkin\Node\PyStringNode;
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
|
||||
require_once __DIR__ . '/../../vendor/autoload.php';
|
||||
|
||||
class FeatureContext implements Context {
|
||||
use WebDav;
|
||||
|
||||
/** @var string */
|
||||
private $mappedUserId;
|
||||
|
||||
private $lastInsertIds = [];
|
||||
|
||||
/**
|
||||
* @BeforeSuite
|
||||
*/
|
||||
public static function addFilesToSkeleton() {
|
||||
}
|
||||
/**
|
||||
* @When :user requests the deck list
|
||||
*/
|
||||
/**
|
||||
* @When Sending a :method to :url with JSON
|
||||
*/
|
||||
public function sendingAToWithJSON($method, $url, \Behat\Gherkin\Node\PyStringNode $data) {
|
||||
$this->sendJSONrequest($method, $url, json_decode($data));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @When :user creates a new deck with name :name
|
||||
*/
|
||||
public function createsANewDeckWithName($user, $content) {
|
||||
$client = new GuzzleHttp\Client();
|
||||
$this->response = $client->post(
|
||||
'http://localhost:8080/index.php/apps/deck/boards',
|
||||
[
|
||||
'form_params' => [
|
||||
'name' => $name,
|
||||
],
|
||||
'auth' => [
|
||||
$this->mappedUserId,
|
||||
'test',
|
||||
],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the response should have a status code :code
|
||||
* @param string $code
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function theResponseShouldHaveAStatusCode($code) {
|
||||
$currentCode = $this->response->getStatusCode();
|
||||
if ($currentCode !== (int)$code) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'Expected %s as code got %s',
|
||||
$code,
|
||||
$currentCode
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the response should be a JSON array with the following mandatory values
|
||||
* @param TableNode $table
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function theResponseShouldBeAJsonArrayWithTheFollowingMandatoryValues(TableNode $table) {
|
||||
$expectedValues = $table->getColumnsHash();
|
||||
$realResponseArray = json_decode($this->response->getBody()->getContents(), true);
|
||||
|
||||
foreach ($expectedValues as $value) {
|
||||
if ((string)$realResponseArray[$value['key']] !== (string)$value['value']) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'Expected %s for key %s got %s',
|
||||
(string)$value['value'],
|
||||
$value['key'],
|
||||
(string)$realResponseArray[$value['key']]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the response should be a JSON array with a length of :length
|
||||
* @param int $length
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function theResponseShouldBeAJsonArrayWithALengthOf($length) {
|
||||
$realResponseArray = json_decode($this->response->getBody()->getContents(), true);
|
||||
PHPUnit_Framework_Assert::assertEquals($realResponseArray, "foo");
|
||||
if ((int)count($realResponseArray) !== (int)$length) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'Expected %d as length got %d',
|
||||
$length,
|
||||
count($realResponseArray)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^I should get:$/
|
||||
*
|
||||
* @param PyStringNode $string
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function iShouldGet(PyStringNode $string) {
|
||||
if ((string) $string !== trim($this->cliOutput)) {
|
||||
throw new Exception(sprintf(
|
||||
'Expected "%s" but received "%s".',
|
||||
$string,
|
||||
$this->cliOutput
|
||||
));
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
private function sendJSONrequest($method, $url, $data) {
|
||||
$baseUrl = substr($this->baseUrl, 0, -5);
|
||||
|
||||
$client = new Client;
|
||||
try {
|
||||
$this->response = $client->request(
|
||||
$method,
|
||||
$baseUrl . $url,
|
||||
[
|
||||
'cookies' => $this->cookieJar,
|
||||
'json' => $data,
|
||||
'headers' => [
|
||||
'requesttoken' => $this->requestToken
|
||||
]
|
||||
]
|
||||
);
|
||||
} catch (ClientException $e) {
|
||||
$this->response = $e->getResponse();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^creates a board named "([^"]*)" with color "([^"]*)"$/
|
||||
*/
|
||||
public function createsABoardNamedWithColor($title, $color) {
|
||||
$this->sendJSONrequest('POST', '/index.php/apps/deck/boards', [
|
||||
'title' => $title,
|
||||
'color' => $color
|
||||
]
|
||||
);
|
||||
$response = json_decode($this->response->getBody()->getContents(), true);
|
||||
$this->lastInsertIds[$title] = $response['id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* @When /^"([^"]*)" fetches the board named "([^"]*)"$/
|
||||
*/
|
||||
public function fetchesTheBoardNamed($user, $boardName) {
|
||||
$this->loggingInUsingWebAs($user);
|
||||
$id = $this->lastInsertIds[$boardName];
|
||||
$this->sendJSONrequest('GET', '/index.php/apps/deck/boards/'.$id, []);
|
||||
}
|
||||
}
|
||||
121
tests/integration/features/bootstrap/RequestTrait.php
Normal file
121
tests/integration/features/bootstrap/RequestTrait.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
use Behat\Behat\Hook\Scope\BeforeScenarioScope;
|
||||
use Behat\Gherkin\Node\TableNode;
|
||||
use GuzzleHttp\Client;
|
||||
use GuzzleHttp\Exception\ClientException;
|
||||
use PHPUnit\Framework\Assert;
|
||||
|
||||
require_once __DIR__ . '/../../vendor/autoload.php';
|
||||
|
||||
trait RequestTrait {
|
||||
private $baseUrl;
|
||||
private $adminUser;
|
||||
private $regularUser;
|
||||
private $cookieJar;
|
||||
|
||||
private $response;
|
||||
|
||||
public function __construct($baseUrl, $admin = 'admin', $regular_user_password = '123456') {
|
||||
$this->baseUrl = $baseUrl;
|
||||
$this->adminUser = $admin === 'admin' ? ['admin', 'admin'] : $admin;
|
||||
$this->regularUser = $regular_user_password;
|
||||
}
|
||||
|
||||
/** @var ServerContext */
|
||||
private $serverContext;
|
||||
|
||||
/** @BeforeScenario */
|
||||
public function gatherContexts(BeforeScenarioScope $scope) {
|
||||
$environment = $scope->getEnvironment();
|
||||
|
||||
$this->serverContext = $environment->getContext('ServerContext');
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the response should have a status code :code
|
||||
* @param string $code
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function theResponseShouldHaveStatusCode($code) {
|
||||
$currentCode = $this->response->getStatusCode();
|
||||
if ($currentCode !== (int)$code) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'Expected %s as code got %s',
|
||||
$code,
|
||||
$currentCode
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then /^the response Content-Type should be "([^"]*)"$/
|
||||
* @param string $contentType
|
||||
*/
|
||||
public function theResponseContentTypeShouldbe($contentType) {
|
||||
Assert::assertEquals($contentType, $this->response->getHeader('Content-Type')[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the response should be a JSON array with the following mandatory values
|
||||
* @param TableNode $table
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function theResponseShouldBeAJsonArrayWithTheFollowingMandatoryValues(TableNode $table) {
|
||||
$this->response->getBody()->seek(0);
|
||||
$expectedValues = $table->getColumnsHash();
|
||||
$realResponseArray = json_decode($this->response->getBody()->getContents(), true);
|
||||
foreach ($expectedValues as $value) {
|
||||
if ((string)$realResponseArray[$value['key']] !== (string)$value['value']) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'Expected %s for key %s got %s',
|
||||
(string)$value['value'],
|
||||
$value['key'],
|
||||
(string)$realResponseArray[$value['key']]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @Then the response should be a JSON array with a length of :length
|
||||
* @param int $length
|
||||
* @throws InvalidArgumentException
|
||||
*/
|
||||
public function theResponseShouldBeAJsonArrayWithALengthOf($length) {
|
||||
$this->response->getBody()->seek(0);
|
||||
$realResponseArray = json_decode($this->response->getBody()->getContents(), true);
|
||||
if ((int)count($realResponseArray) !== (int)$length) {
|
||||
throw new InvalidArgumentException(
|
||||
sprintf(
|
||||
'Expected %d as length got %d',
|
||||
$length,
|
||||
count($realResponseArray)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function sendJSONrequest($method, $url, $data = []) {
|
||||
$client = new Client;
|
||||
try {
|
||||
$this->response = $client->request(
|
||||
$method,
|
||||
$this->baseUrl . $url,
|
||||
[
|
||||
'cookies' => $this->serverContext->getCookieJar(),
|
||||
'json' => $data,
|
||||
'headers' => [
|
||||
'requesttoken' => $this->serverContext->getReqestToken()
|
||||
]
|
||||
]
|
||||
);
|
||||
} catch (ClientException $e) {
|
||||
$this->response = $e->getResponse();
|
||||
}
|
||||
}
|
||||
}
|
||||
38
tests/integration/features/bootstrap/ServerContext.php
Normal file
38
tests/integration/features/bootstrap/ServerContext.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
use Behat\Behat\Context\Context;
|
||||
use GuzzleHttp\Cookie\CookieJar;
|
||||
|
||||
require_once __DIR__ . '/../../vendor/autoload.php';
|
||||
|
||||
class ServerContext implements Context {
|
||||
use WebDav;
|
||||
|
||||
/** @var string */
|
||||
private $mappedUserId;
|
||||
|
||||
private $lastInsertIds = [];
|
||||
|
||||
/**
|
||||
* @BeforeSuite
|
||||
*/
|
||||
public static function addFilesToSkeleton() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @Given /^acting as user "([^"]*)"$/
|
||||
*/
|
||||
public function actingAsUser($user) {
|
||||
$this->cookieJar = new CookieJar();
|
||||
$this->loggingInUsingWebAs($user);
|
||||
$this->asAn($user);
|
||||
}
|
||||
|
||||
public function getCookieJar(): CookieJar {
|
||||
return $this->cookieJar;
|
||||
}
|
||||
|
||||
public function getReqestToken(): string {
|
||||
return $this->requestToken;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
Feature: decks
|
||||
|
||||
Background:
|
||||
Given user "admin" exists
|
||||
Given user "user0" exists
|
||||
|
||||
Scenario: Request the main frontend page
|
||||
@@ -10,27 +11,21 @@ Feature: decks
|
||||
|
||||
Scenario: Fetch the board list
|
||||
Given Logging in using web as "admin"
|
||||
When Sending a "GET" to "/index.php/apps/deck/boards" with requesttoken
|
||||
Then the HTTP status code should be "200"
|
||||
And the Content-Type should be "application/json; charset=utf-8"
|
||||
When fetching the board list
|
||||
Then the response should have a status code "200"
|
||||
And the response Content-Type should be "application/json; charset=utf-8"
|
||||
|
||||
Scenario: Fetch board details of a nonexisting board
|
||||
Given Logging in using web as "admin"
|
||||
When Sending a "GET" to "/index.php/apps/deck/boards/13379" with requesttoken
|
||||
Then the HTTP status code should be "403"
|
||||
And the Content-Type should be "application/json; charset=utf-8"
|
||||
When fetching the board with id "99999999"
|
||||
Then the response should have a status code "403"
|
||||
And the response Content-Type should be "application/json; charset=utf-8"
|
||||
|
||||
Scenario: Create a new board
|
||||
Given Logging in using web as "admin"
|
||||
When Sending a "POST" to "/index.php/apps/deck/boards" with JSON
|
||||
"""
|
||||
{
|
||||
"title": "MyBoard",
|
||||
"color": "000000"
|
||||
}
|
||||
"""
|
||||
Then the HTTP status code should be "200"
|
||||
And the Content-Type should be "application/json; charset=utf-8"
|
||||
When creates a board named "MyBoard" with color "000000"
|
||||
Then the response should have a status code "200"
|
||||
And the response Content-Type should be "application/json; charset=utf-8"
|
||||
And the response should be a JSON array with the following mandatory values
|
||||
|key|value|
|
||||
|title|MyBoard|
|
||||
|
||||
137
tests/integration/features/sharing.feature
Normal file
137
tests/integration/features/sharing.feature
Normal file
@@ -0,0 +1,137 @@
|
||||
Feature: File sharing
|
||||
|
||||
Background:
|
||||
Given user "admin" exists
|
||||
And user "user0" exists
|
||||
And user "user1" exists
|
||||
And user "user2" exists
|
||||
And user "user3" exists
|
||||
Given group "group0" exists
|
||||
And group "group1" exists
|
||||
Given user "user2" belongs to group "group1"
|
||||
Given user "user3" belongs to group "group1"
|
||||
|
||||
Scenario: Share a file with a card by the board owner
|
||||
Given acting as user "user0"
|
||||
And creates a board named "Shared board" with color "fafafa"
|
||||
And create a stack named "Stack"
|
||||
And create a card named "Test"
|
||||
And shares the board with user "user1"
|
||||
Then the HTTP status code should be "200"
|
||||
|
||||
Given using new dav path
|
||||
When User "user0" uploads file "../data/test.txt" to "/user0-file.txt"
|
||||
Then the HTTP status code should be "201"
|
||||
Given acting as user "user0"
|
||||
When share the file "/user0-file.txt" with the card
|
||||
Then the OCS status code should be "100"
|
||||
And the HTTP status code should be "200"
|
||||
|
||||
And as "user1" the file "/Deck/user0-file.txt" exists
|
||||
|
||||
Scenario: Share a file with a card by another user
|
||||
Given acting as user "user0"
|
||||
And creates a board named "Shared board" with color "fafafa"
|
||||
And create a stack named "Stack"
|
||||
And create a card named "Test"
|
||||
And shares the board with user "user1"
|
||||
| permissionEdit | 1 |
|
||||
| permissionShare | 1 |
|
||||
| permissionManage | 1 |
|
||||
Then the HTTP status code should be "200"
|
||||
|
||||
Given using new dav path
|
||||
When User "user1" uploads file "../data/test.txt" to "/user1-file.txt"
|
||||
Then the HTTP status code should be "201"
|
||||
Given acting as user "user1"
|
||||
And share the file "/user1-file.txt" with the card
|
||||
Then the OCS status code should be "100"
|
||||
And the HTTP status code should be "200"
|
||||
|
||||
And as "user0" the file "/Deck/user1-file.txt" exists
|
||||
And as "user1" the file "/Deck/user1-file.txt" does not exist
|
||||
|
||||
Scenario: Share a file with a card by another user fails without edit permission
|
||||
Given acting as user "user0"
|
||||
And creates a board named "Shared board" with color "fafafa"
|
||||
And create a stack named "Stack"
|
||||
And create a card named "Test"
|
||||
And shares the board with user "user1"
|
||||
Then the HTTP status code should be "200"
|
||||
|
||||
Given using new dav path
|
||||
When User "user1" uploads file "../data/test.txt" to "/user1-file.txt"
|
||||
Then the HTTP status code should be "201"
|
||||
Given acting as user "user1"
|
||||
And share the file "/user1-file.txt" with the card
|
||||
Then the OCS status code should be "404"
|
||||
And the HTTP status code should be "200"
|
||||
And as "user0" the file "/Deck/user1-file.txt" does not exist
|
||||
|
||||
Scenario: Share a file with a card by another user through a group
|
||||
Given acting as user "user0"
|
||||
And creates a board named "Shared board" with color "fafafa"
|
||||
And create a stack named "Stack"
|
||||
And create a card named "Test"
|
||||
And shares the board with group "group1"
|
||||
Then the HTTP status code should be "200"
|
||||
|
||||
Given using new dav path
|
||||
When User "user0" uploads file "../data/test.txt" to "/user0-file2.txt"
|
||||
Then the HTTP status code should be "201"
|
||||
Given acting as user "user0"
|
||||
When share the file "/user0-file2.txt" with the card
|
||||
Then the OCS status code should be "100"
|
||||
And the HTTP status code should be "200"
|
||||
|
||||
And as "user2" the file "/Deck/user0-file2.txt" exists
|
||||
And as "user0" the file "/Deck/user0-file2.txt" does not exist
|
||||
|
||||
Scenario: Remove incoming group share as a user
|
||||
Given acting as user "user0"
|
||||
And creates a board named "Shared board" with color "fafafa"
|
||||
And create a stack named "Stack"
|
||||
And create a card named "Test"
|
||||
And shares the board with group "group1"
|
||||
Then the HTTP status code should be "200"
|
||||
|
||||
Given using new dav path
|
||||
When User "user0" uploads file "../data/test.txt" to "/user0-file2.txt"
|
||||
Then the HTTP status code should be "201"
|
||||
Given acting as user "user0"
|
||||
When share the file "/user0-file2.txt" with the card
|
||||
Then the OCS status code should be "100"
|
||||
And the HTTP status code should be "200"
|
||||
|
||||
And as "user2" the file "/Deck/user0-file2.txt" exists
|
||||
And as "user3" the file "/Deck/user0-file2.txt" exists
|
||||
And as "user0" the file "/Deck/user0-file2.txt" does not exist
|
||||
|
||||
Given User "user2" deletes file "/Deck/user0-file2.txt"
|
||||
And as "user2" the file "/Deck/user0-file2.txt" does not exist
|
||||
And as "user3" the file "/Deck/user0-file2.txt" exists
|
||||
|
||||
Scenario: Remove a share as the owner
|
||||
Given acting as user "user0"
|
||||
And creates a board named "Shared board" with color "fafafa"
|
||||
And create a stack named "Stack"
|
||||
And create a card named "Test"
|
||||
And shares the board with group "group1"
|
||||
Then the HTTP status code should be "200"
|
||||
|
||||
Given using new dav path
|
||||
When User "user0" uploads file "../data/test.txt" to "/user0-file2.txt"
|
||||
Then the HTTP status code should be "201"
|
||||
Given acting as user "user0"
|
||||
When share the file "/user0-file2.txt" with the card
|
||||
Then the OCS status code should be "100"
|
||||
And the HTTP status code should be "200"
|
||||
|
||||
And as "user2" the file "/Deck/user0-file2.txt" exists
|
||||
And as "user3" the file "/Deck/user0-file2.txt" exists
|
||||
And as "user0" the file "/Deck/user0-file2.txt" does not exist
|
||||
|
||||
Given acting as user "user0"
|
||||
When Deleting last share
|
||||
And as "user2" the file "/Deck/user0-file2.txt" does not exist
|
||||
And as "user3" the file "/Deck/user0-file2.txt" does not exist
|
||||
@@ -15,10 +15,8 @@ INSTALLED=$($OCC status | grep installed: | cut -d " " -f 5)
|
||||
if [ "$INSTALLED" == "true" ]; then
|
||||
$OCC app:enable deck
|
||||
else
|
||||
if [ "$SCENARIO_TO_RUN" != "setup_features/setup.feature" ]; then
|
||||
echo "Nextcloud instance needs to be installed" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Nextcloud instance needs to be installed" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
composer install
|
||||
@@ -36,7 +34,7 @@ echo $PHPPID
|
||||
|
||||
export TEST_SERVER_URL="http://localhost:$PORT/ocs/"
|
||||
|
||||
vendor/bin/behat
|
||||
vendor/bin/behat $SCENARIO_TO_RUN
|
||||
RESULT=$?
|
||||
|
||||
kill $PHPPID
|
||||
|
||||
@@ -4,18 +4,6 @@
|
||||
<TypeDoesNotContainType occurrences="1">
|
||||
<code>$message !== null</code>
|
||||
</TypeDoesNotContainType>
|
||||
<UndefinedMagicMethod occurrences="9">
|
||||
<code>getArchived</code>
|
||||
<code>getBoardId</code>
|
||||
<code>getBoardId</code>
|
||||
<code>getBoardId</code>
|
||||
<code>getBoardId</code>
|
||||
<code>getCardId</code>
|
||||
<code>getCardId</code>
|
||||
<code>getStackId</code>
|
||||
<code>getTitle</code>
|
||||
<code>getTitle</code>
|
||||
</UndefinedMagicMethod>
|
||||
</file>
|
||||
<file src="lib/Activity/DeckProvider.php">
|
||||
<InvalidScalarArgument occurrences="1">
|
||||
@@ -28,23 +16,9 @@
|
||||
</DuplicateClass>
|
||||
</file>
|
||||
<file src="lib/AppInfo/Application20.php">
|
||||
<UndefinedClass occurrences="1">
|
||||
<code>IBootstrap</code>
|
||||
</UndefinedClass>
|
||||
</file>
|
||||
<file src="lib/AppInfo/ApplicationLegacy.php">
|
||||
<UndefinedInterfaceMethod occurrences="2">
|
||||
<code>listen</code>
|
||||
<code>listen</code>
|
||||
</UndefinedInterfaceMethod>
|
||||
</file>
|
||||
<file src="lib/Collaboration/Resources/ResourceProviderCard.php">
|
||||
<UndefinedMagicMethod occurrences="4">
|
||||
<code>getAcl</code>
|
||||
<code>getOwner</code>
|
||||
<code>getTitle</code>
|
||||
<code>getTitle</code>
|
||||
</UndefinedMagicMethod>
|
||||
<RedundantCondition occurrences="1">
|
||||
<code>method_exists($shareManager, 'registerShareProvider')</code>
|
||||
</RedundantCondition>
|
||||
</file>
|
||||
<file src="lib/Command/UserExport.php">
|
||||
<ImplementedReturnTypeMismatch occurrences="1">
|
||||
@@ -90,11 +64,9 @@
|
||||
</InvalidScalarArgument>
|
||||
</file>
|
||||
<file src="lib/Controller/PageController.php">
|
||||
<MissingDependency occurrences="3">
|
||||
<code>Application</code>
|
||||
<code>Application</code>
|
||||
<code>Application</code>
|
||||
</MissingDependency>
|
||||
<UndefinedClass occurrences="1">
|
||||
<code>LoadSidebar</code>
|
||||
</UndefinedClass>
|
||||
</file>
|
||||
<file src="lib/Controller/StackApiController.php">
|
||||
<RedundantCondition occurrences="1">
|
||||
@@ -104,21 +76,6 @@
|
||||
<code>Util</code>
|
||||
</UndefinedClass>
|
||||
</file>
|
||||
<file src="lib/Cron/CardDescriptionActivity.php">
|
||||
<UndefinedClass occurrences="1">
|
||||
<code>Job</code>
|
||||
</UndefinedClass>
|
||||
</file>
|
||||
<file src="lib/Cron/DeleteCron.php">
|
||||
<UndefinedClass occurrences="1">
|
||||
<code>Job</code>
|
||||
</UndefinedClass>
|
||||
</file>
|
||||
<file src="lib/Cron/ScheduledNotifications.php">
|
||||
<UndefinedClass occurrences="1">
|
||||
<code>Job</code>
|
||||
</UndefinedClass>
|
||||
</file>
|
||||
<file src="lib/DAV/Calendar.php">
|
||||
<UndefinedClass occurrences="1">
|
||||
<code>ExternalCalendar</code>
|
||||
@@ -140,18 +97,6 @@
|
||||
<code>NotFound</code>
|
||||
</UndefinedClass>
|
||||
</file>
|
||||
<file src="lib/Dashboard/DeckWidget.php">
|
||||
<UndefinedClass occurrences="1">
|
||||
<code>IWidget</code>
|
||||
</UndefinedClass>
|
||||
</file>
|
||||
<file src="lib/Db/Acl.php">
|
||||
<UndefinedMagicMethod occurrences="3">
|
||||
<code>getPermissionEdit</code>
|
||||
<code>getPermissionManage</code>
|
||||
<code>getPermissionShare</code>
|
||||
</UndefinedMagicMethod>
|
||||
</file>
|
||||
<file src="lib/Db/AclMapper.php">
|
||||
<ParamNameMismatch occurrences="1">
|
||||
<code>$aclId</code>
|
||||
@@ -161,32 +106,12 @@
|
||||
<ParamNameMismatch occurrences="1">
|
||||
<code>$cardId</code>
|
||||
</ParamNameMismatch>
|
||||
<UndefinedMagicMethod occurrences="9">
|
||||
<code>getParticipant</code>
|
||||
<code>getParticipant</code>
|
||||
<code>getParticipant</code>
|
||||
<code>getParticipant</code>
|
||||
<code>getParticipant</code>
|
||||
<code>getParticipant</code>
|
||||
<code>getType</code>
|
||||
<code>getType</code>
|
||||
<code>getType</code>
|
||||
</UndefinedMagicMethod>
|
||||
</file>
|
||||
<file src="lib/Db/AttachmentMapper.php">
|
||||
<UndefinedMagicMethod occurrences="2">
|
||||
<code>getCardId</code>
|
||||
<code>getCardId</code>
|
||||
</UndefinedMagicMethod>
|
||||
<UndefinedVariable occurrences="1">
|
||||
<code>$query</code>
|
||||
</UndefinedVariable>
|
||||
</file>
|
||||
<file src="lib/Db/Board.php">
|
||||
<UndefinedMagicMethod occurrences="1">
|
||||
<code>getLastModified</code>
|
||||
</UndefinedMagicMethod>
|
||||
</file>
|
||||
<file src="lib/Db/BoardMapper.php">
|
||||
<ParamNameMismatch occurrences="1">
|
||||
<code>$boardId</code>
|
||||
@@ -194,53 +119,20 @@
|
||||
<UndefinedClass occurrences="1">
|
||||
<code>\OCA\Circles\Api\v1\Circles</code>
|
||||
</UndefinedClass>
|
||||
<UndefinedMagicMethod occurrences="2">
|
||||
<code>setAcl</code>
|
||||
<code>setLabels</code>
|
||||
</UndefinedMagicMethod>
|
||||
</file>
|
||||
<file src="lib/Db/Card.php">
|
||||
<UndefinedClass occurrences="2">
|
||||
<code>VCalendar</code>
|
||||
<code>VCalendar</code>
|
||||
</UndefinedClass>
|
||||
<UndefinedMagicMethod occurrences="9">
|
||||
<code>getArchived</code>
|
||||
<code>getArchived</code>
|
||||
<code>getDescription</code>
|
||||
<code>getLabels</code>
|
||||
<code>getLabels</code>
|
||||
<code>getLastModified</code>
|
||||
<code>getLastModified</code>
|
||||
<code>getStackId</code>
|
||||
<code>getTitle</code>
|
||||
</UndefinedMagicMethod>
|
||||
</file>
|
||||
<file src="lib/Db/CardMapper.php">
|
||||
<ImplicitToStringCast occurrences="1">
|
||||
<code>$qb->createNamedParameter($boardIds, IQueryBuilder::PARAM_INT_ARRAY)</code>
|
||||
</ImplicitToStringCast>
|
||||
<InvalidScalarArgument occurrences="1">
|
||||
<code>$entity->getId()</code>
|
||||
</InvalidScalarArgument>
|
||||
<ParamNameMismatch occurrences="1">
|
||||
<code>$cardId</code>
|
||||
</ParamNameMismatch>
|
||||
<UndefinedMagicMethod occurrences="13">
|
||||
<code>getDescription</code>
|
||||
<code>getDescription</code>
|
||||
<code>getDuedate</code>
|
||||
<code>setCreatedAt</code>
|
||||
<code>setDatabaseType</code>
|
||||
<code>setDatabaseType</code>
|
||||
<code>setDescription</code>
|
||||
<code>setDescription</code>
|
||||
<code>setLabels</code>
|
||||
<code>setLastModified</code>
|
||||
<code>setLastModified</code>
|
||||
<code>setNotified</code>
|
||||
<code>setNotified</code>
|
||||
</UndefinedMagicMethod>
|
||||
</file>
|
||||
<file src="lib/Db/ChangeHelper.php">
|
||||
<UndefinedThisPropertyAssignment occurrences="3">
|
||||
@@ -269,69 +161,26 @@
|
||||
<code>\OCA\Circles\Model\Circle</code>
|
||||
</UndefinedDocblockClass>
|
||||
</file>
|
||||
<file src="lib/Db/Label.php">
|
||||
<UndefinedMagicMethod occurrences="1">
|
||||
<code>getLastModified</code>
|
||||
</UndefinedMagicMethod>
|
||||
</file>
|
||||
<file src="lib/Db/LabelMapper.php">
|
||||
<ParamNameMismatch occurrences="1">
|
||||
<code>$labelId</code>
|
||||
</ParamNameMismatch>
|
||||
<UndefinedMagicMethod occurrences="2">
|
||||
<code>setLastModified</code>
|
||||
<code>setLastModified</code>
|
||||
</UndefinedMagicMethod>
|
||||
</file>
|
||||
<file src="lib/Db/RelationalEntity.php">
|
||||
<UndefinedMagicMethod occurrences="1">
|
||||
<code>getETag</code>
|
||||
</UndefinedMagicMethod>
|
||||
</file>
|
||||
<file src="lib/Db/Stack.php">
|
||||
<UndefinedClass occurrences="2">
|
||||
<code>VCalendar</code>
|
||||
<code>VCalendar</code>
|
||||
</UndefinedClass>
|
||||
<UndefinedMagicMethod occurrences="2">
|
||||
<code>getLastModified</code>
|
||||
<code>getTitle</code>
|
||||
</UndefinedMagicMethod>
|
||||
</file>
|
||||
<file src="lib/Db/StackMapper.php">
|
||||
<ParamNameMismatch occurrences="1">
|
||||
<code>$stackId</code>
|
||||
</ParamNameMismatch>
|
||||
<UndefinedMagicMethod occurrences="1">
|
||||
<code>getBoardId</code>
|
||||
</UndefinedMagicMethod>
|
||||
</file>
|
||||
<file src="lib/Listeners/BeforeTemplateRenderedListener.php">
|
||||
<UndefinedClass occurrences="1">
|
||||
<code>BeforeTemplateRenderedEvent</code>
|
||||
</UndefinedClass>
|
||||
</file>
|
||||
<file src="lib/Migration/UnknownUsers.php">
|
||||
<UndefinedMagicMethod occurrences="4">
|
||||
<code>getParticipant</code>
|
||||
<code>getParticipant</code>
|
||||
<code>getType</code>
|
||||
<code>getType</code>
|
||||
</UndefinedMagicMethod>
|
||||
</file>
|
||||
<file src="lib/Notification/NotificationHelper.php">
|
||||
<InvalidScalarArgument occurrences="1">
|
||||
<code>$board->getId()</code>
|
||||
</InvalidScalarArgument>
|
||||
<MissingDependency occurrences="1">
|
||||
<code>Application</code>
|
||||
</MissingDependency>
|
||||
<UndefinedMagicMethod occurrences="3">
|
||||
<code>getTitle</code>
|
||||
<code>getTitle</code>
|
||||
<code>getTitle</code>
|
||||
<code>getTitle</code>
|
||||
</UndefinedMagicMethod>
|
||||
</file>
|
||||
<file src="lib/Notification/Notifier.php">
|
||||
<RedundantCast occurrences="7">
|
||||
@@ -348,42 +197,12 @@
|
||||
<InvalidPropertyAssignmentValue occurrences="1">
|
||||
<code>[]</code>
|
||||
</InvalidPropertyAssignmentValue>
|
||||
<UndefinedClass occurrences="2">
|
||||
<code>IndexDocument</code>
|
||||
<code>SearchTemplate</code>
|
||||
</UndefinedClass>
|
||||
</file>
|
||||
<file src="lib/Search/BoardSearchResultEntry.php">
|
||||
<UndefinedClass occurrences="1">
|
||||
<code>SearchResultEntry</code>
|
||||
</UndefinedClass>
|
||||
</file>
|
||||
<file src="lib/Search/CardSearchResultEntry.php">
|
||||
<UndefinedClass occurrences="1">
|
||||
<code>SearchResultEntry</code>
|
||||
</UndefinedClass>
|
||||
</file>
|
||||
<file src="lib/Search/DeckProvider.php">
|
||||
<UndefinedClass occurrences="1">
|
||||
<code>IProvider</code>
|
||||
</UndefinedClass>
|
||||
</file>
|
||||
<file src="lib/Service/AssignmentService.php">
|
||||
<InvalidScalarArgument occurrences="2">
|
||||
<code>$cardId</code>
|
||||
<code>$cardId</code>
|
||||
</InvalidScalarArgument>
|
||||
<UndefinedMagicMethod occurrences="9">
|
||||
<code>getParticipant</code>
|
||||
<code>getParticipant</code>
|
||||
<code>getParticipant</code>
|
||||
<code>getType</code>
|
||||
<code>getType</code>
|
||||
<code>getType</code>
|
||||
<code>setCardId</code>
|
||||
<code>setParticipant</code>
|
||||
<code>setType</code>
|
||||
</UndefinedMagicMethod>
|
||||
<UndefinedThisPropertyAssignment occurrences="1">
|
||||
<code>$this->currentUser</code>
|
||||
</UndefinedThisPropertyAssignment>
|
||||
@@ -391,86 +210,11 @@
|
||||
<code>$this->currentUser</code>
|
||||
</UndefinedThisPropertyFetch>
|
||||
</file>
|
||||
<file src="lib/Service/AttachmentService.php">
|
||||
<MissingDependency occurrences="1">
|
||||
<code>Application</code>
|
||||
</MissingDependency>
|
||||
<UndefinedMagicMethod occurrences="13">
|
||||
<code>getCardId</code>
|
||||
<code>getCardId</code>
|
||||
<code>getCardId</code>
|
||||
<code>getData</code>
|
||||
<code>getType</code>
|
||||
<code>getType</code>
|
||||
<code>setCardId</code>
|
||||
<code>setCreatedAt</code>
|
||||
<code>setCreatedBy</code>
|
||||
<code>setData</code>
|
||||
<code>setLastModified</code>
|
||||
<code>setLastModified</code>
|
||||
<code>setType</code>
|
||||
</UndefinedMagicMethod>
|
||||
</file>
|
||||
<file src="lib/Service/BoardService.php">
|
||||
<InvalidArgument occurrences="6">
|
||||
<code>'\OCA\Deck\Board::onCreate'</code>
|
||||
<code>'\OCA\Deck\Board::onDelete'</code>
|
||||
<code>'\OCA\Deck\Board::onDelete'</code>
|
||||
<code>'\OCA\Deck\Board::onShareEdit'</code>
|
||||
<code>'\OCA\Deck\Board::onUpdate'</code>
|
||||
<code>'\OCA\Deck\Board::onUpdate'</code>
|
||||
</InvalidArgument>
|
||||
<MissingDependency occurrences="3">
|
||||
<code>Application</code>
|
||||
<code>Application</code>
|
||||
<code>Application</code>
|
||||
</MissingDependency>
|
||||
<TooManyArguments occurrences="2">
|
||||
<code>findAll</code>
|
||||
<code>findAll</code>
|
||||
</TooManyArguments>
|
||||
<UndefinedMagicMethod occurrences="40">
|
||||
<code>getAcl</code>
|
||||
<code>getAcl</code>
|
||||
<code>getAcl</code>
|
||||
<code>getBoardId</code>
|
||||
<code>getBoardId</code>
|
||||
<code>getBoardId</code>
|
||||
<code>getBoardId</code>
|
||||
<code>getParticipant</code>
|
||||
<code>getType</code>
|
||||
<code>setBoardId</code>
|
||||
<code>setBoardId</code>
|
||||
<code>setBoardId</code>
|
||||
<code>setBoardId</code>
|
||||
<code>setColor</code>
|
||||
<code>setColor</code>
|
||||
<code>setColor</code>
|
||||
<code>setColor</code>
|
||||
<code>setColor</code>
|
||||
<code>setLabels</code>
|
||||
<code>setOwner</code>
|
||||
<code>setOwner</code>
|
||||
<code>setParticipant</code>
|
||||
<code>setPermissionEdit</code>
|
||||
<code>setPermissionEdit</code>
|
||||
<code>setPermissionManage</code>
|
||||
<code>setPermissionManage</code>
|
||||
<code>setPermissionShare</code>
|
||||
<code>setPermissionShare</code>
|
||||
<code>setPermissions</code>
|
||||
<code>setPermissions</code>
|
||||
<code>setPermissions</code>
|
||||
<code>setPermissions</code>
|
||||
<code>setSettings</code>
|
||||
<code>setTitle</code>
|
||||
<code>setTitle</code>
|
||||
<code>setTitle</code>
|
||||
<code>setTitle</code>
|
||||
<code>setTitle</code>
|
||||
<code>setTitle</code>
|
||||
<code>setType</code>
|
||||
</UndefinedMagicMethod>
|
||||
</file>
|
||||
<file src="lib/Service/CardService.php">
|
||||
<TooFewArguments occurrences="1">
|
||||
@@ -479,52 +223,6 @@
|
||||
<UndefinedDocblockClass occurrences="1">
|
||||
<code>\OCP\AppFramework\Db\</code>
|
||||
</UndefinedDocblockClass>
|
||||
<UndefinedMagicMethod occurrences="44">
|
||||
<code>getArchived</code>
|
||||
<code>getArchived</code>
|
||||
<code>getArchived</code>
|
||||
<code>getArchived</code>
|
||||
<code>getArchived</code>
|
||||
<code>getDescription</code>
|
||||
<code>getDescription</code>
|
||||
<code>getDescription</code>
|
||||
<code>getDescriptionPrev</code>
|
||||
<code>getDescriptionPrev</code>
|
||||
<code>getLastEditor</code>
|
||||
<code>getLastEditor</code>
|
||||
<code>getLastEditor</code>
|
||||
<code>getOrder</code>
|
||||
<code>setArchived</code>
|
||||
<code>setArchived</code>
|
||||
<code>setArchived</code>
|
||||
<code>setAssignedUsers</code>
|
||||
<code>setAssignedUsers</code>
|
||||
<code>setAttachmentCount</code>
|
||||
<code>setAttachments</code>
|
||||
<code>setCommentsUnread</code>
|
||||
<code>setDeletedAt</code>
|
||||
<code>setDeletedAt</code>
|
||||
<code>setDescription</code>
|
||||
<code>setDescription</code>
|
||||
<code>setDescriptionPrev</code>
|
||||
<code>setDescriptionPrev</code>
|
||||
<code>setDuedate</code>
|
||||
<code>setDuedate</code>
|
||||
<code>setLabels</code>
|
||||
<code>setLastEditor</code>
|
||||
<code>setOrder</code>
|
||||
<code>setOrder</code>
|
||||
<code>setOwner</code>
|
||||
<code>setOwner</code>
|
||||
<code>setStackId</code>
|
||||
<code>setStackId</code>
|
||||
<code>setStackId</code>
|
||||
<code>setTitle</code>
|
||||
<code>setTitle</code>
|
||||
<code>setTitle</code>
|
||||
<code>setType</code>
|
||||
<code>setType</code>
|
||||
</UndefinedMagicMethod>
|
||||
</file>
|
||||
<file src="lib/Service/CirclesService.php">
|
||||
<UndefinedClass occurrences="2">
|
||||
@@ -533,13 +231,6 @@
|
||||
</UndefinedClass>
|
||||
</file>
|
||||
<file src="lib/Service/CommentService.php">
|
||||
<MissingDependency occurrences="5">
|
||||
<code>Application</code>
|
||||
<code>Application</code>
|
||||
<code>Application</code>
|
||||
<code>Application</code>
|
||||
<code>Application</code>
|
||||
</MissingDependency>
|
||||
<UndefinedThisPropertyAssignment occurrences="2">
|
||||
<code>$this->cardMapper</code>
|
||||
<code>$this->permissionService</code>
|
||||
@@ -555,25 +246,7 @@
|
||||
<code>$this->permissionService</code>
|
||||
</UndefinedThisPropertyFetch>
|
||||
</file>
|
||||
<file src="lib/Service/ConfigService.php">
|
||||
<InvalidScalarArgument occurrences="1">
|
||||
<code>(int)$value</code>
|
||||
</InvalidScalarArgument>
|
||||
<MissingDependency occurrences="7">
|
||||
<code>Application</code>
|
||||
<code>Application</code>
|
||||
<code>Application</code>
|
||||
<code>Application</code>
|
||||
<code>Application</code>
|
||||
<code>Application</code>
|
||||
<code>Application</code>
|
||||
</MissingDependency>
|
||||
</file>
|
||||
<file src="lib/Service/DefaultBoardService.php">
|
||||
<MissingDependency occurrences="2">
|
||||
<code>Application</code>
|
||||
<code>Application</code>
|
||||
</MissingDependency>
|
||||
<TypeDoesNotContainNull occurrences="6">
|
||||
<code>$color === false || $color === null</code>
|
||||
<code>$color === null</code>
|
||||
@@ -597,94 +270,46 @@
|
||||
<code>is_resource($content)</code>
|
||||
<code>is_resource($content)</code>
|
||||
</RedundantCondition>
|
||||
<UndefinedMagicMethod occurrences="10">
|
||||
<code>getCardId</code>
|
||||
<code>getCardId</code>
|
||||
<code>getCardId</code>
|
||||
<code>getData</code>
|
||||
<code>getData</code>
|
||||
<code>setData</code>
|
||||
<code>setData</code>
|
||||
<code>setDeletedAt</code>
|
||||
<code>setExtendedData</code>
|
||||
<code>setLastModified</code>
|
||||
</UndefinedMagicMethod>
|
||||
</file>
|
||||
<file src="lib/Service/FullTextSearchService.php">
|
||||
<UndefinedClass occurrences="2">
|
||||
<code>DocumentAccess</code>
|
||||
<code>IndexDocument</code>
|
||||
</UndefinedClass>
|
||||
<UndefinedMagicMethod occurrences="4">
|
||||
<code>getDescription</code>
|
||||
<code>getDescription</code>
|
||||
<code>getTitle</code>
|
||||
<code>getTitle</code>
|
||||
</UndefinedMagicMethod>
|
||||
</file>
|
||||
<file src="lib/Service/LabelService.php">
|
||||
<UndefinedMagicMethod occurrences="4">
|
||||
<code>getBoardId</code>
|
||||
<code>getBoardId</code>
|
||||
<code>getBoardId</code>
|
||||
<code>setBoardId</code>
|
||||
<code>setColor</code>
|
||||
<code>setColor</code>
|
||||
<code>setTitle</code>
|
||||
<code>setTitle</code>
|
||||
</UndefinedMagicMethod>
|
||||
</file>
|
||||
<file src="lib/Service/OverviewService.php">
|
||||
<UndefinedMagicMethod occurrences="4">
|
||||
<code>setAssignedUsers</code>
|
||||
<code>setAttachmentCount</code>
|
||||
<code>setCommentsUnread</code>
|
||||
<code>setLabels</code>
|
||||
</UndefinedMagicMethod>
|
||||
<file src="lib/Service/FilesAppService.php">
|
||||
<MissingDependency occurrences="4">
|
||||
<code>$this->rootFolder</code>
|
||||
<code>$this->rootFolder</code>
|
||||
<code>IRootFolder</code>
|
||||
<code>Share\Exceptions\ShareNotFound</code>
|
||||
</MissingDependency>
|
||||
</file>
|
||||
<file src="lib/Service/PermissionService.php">
|
||||
<UndefinedClass occurrences="2">
|
||||
<code>\OCA\Circles\Api\v1\Circles</code>
|
||||
<code>\OCA\Circles\Api\v1\Circles</code>
|
||||
</UndefinedClass>
|
||||
<UndefinedMagicMethod occurrences="6">
|
||||
<code>getAcl</code>
|
||||
<code>getParticipant</code>
|
||||
<code>getParticipant</code>
|
||||
<code>getParticipant</code>
|
||||
<code>getParticipant</code>
|
||||
<code>getType</code>
|
||||
<code>getType</code>
|
||||
<code>getType</code>
|
||||
<code>getType</code>
|
||||
<code>getType</code>
|
||||
<code>getType</code>
|
||||
</UndefinedMagicMethod>
|
||||
</file>
|
||||
<file src="lib/Service/StackService.php">
|
||||
<InvalidArgument occurrences="3">
|
||||
<code>'\OCA\Deck\Stack::onCreate'</code>
|
||||
<code>'\OCA\Deck\Stack::onDelete'</code>
|
||||
<code>'\OCA\Deck\Stack::onUpdate'</code>
|
||||
</InvalidArgument>
|
||||
<UndefinedClass occurrences="1">
|
||||
<code>BadRquestException</code>
|
||||
</UndefinedClass>
|
||||
<UndefinedMagicMethod occurrences="14">
|
||||
<code>getBoardId</code>
|
||||
<code>getBoardId</code>
|
||||
<code>getBoardId</code>
|
||||
<code>getBoardId</code>
|
||||
<code>getOrder</code>
|
||||
<code>setBoardId</code>
|
||||
<code>setBoardId</code>
|
||||
<code>setCards</code>
|
||||
<code>setDeletedAt</code>
|
||||
<code>setDeletedAt</code>
|
||||
<code>setOrder</code>
|
||||
<code>setOrder</code>
|
||||
<code>setTitle</code>
|
||||
<code>setTitle</code>
|
||||
</UndefinedMagicMethod>
|
||||
</file>
|
||||
<file src="lib/Sharing/DeckShareProvider.php">
|
||||
<InvalidReturnStatement occurrences="1">
|
||||
<code>$shares</code>
|
||||
</InvalidReturnStatement>
|
||||
<InvalidReturnType occurrences="1">
|
||||
<code>getSharesInFolder</code>
|
||||
</InvalidReturnType>
|
||||
<MissingDependency occurrences="7">
|
||||
<code>GenericShareException</code>
|
||||
<code>GenericShareException</code>
|
||||
<code>ShareNotFound</code>
|
||||
<code>ShareNotFound</code>
|
||||
<code>ShareNotFound</code>
|
||||
<code>ShareNotFound</code>
|
||||
<code>ShareNotFound</code>
|
||||
</MissingDependency>
|
||||
</file>
|
||||
<file src="lib/Sharing/Listener.php">
|
||||
<InvalidArgument occurrences="1">
|
||||
<code>[self::class, 'listenPreShare']</code>
|
||||
</InvalidArgument>
|
||||
</file>
|
||||
</files>
|
||||
|
||||
@@ -316,10 +316,6 @@ class ActivityManagerTest extends TestCase {
|
||||
$stack->setBoardId(999);
|
||||
$board = new Board();
|
||||
$board->setId(999);
|
||||
$this->attachmentMapper->expects($this->once())
|
||||
->method('find')
|
||||
->with(777)
|
||||
->willReturn($attachment);
|
||||
$this->cardMapper->expects($this->once())
|
||||
->method('find')
|
||||
->with(555)
|
||||
@@ -340,7 +336,7 @@ class ActivityManagerTest extends TestCase {
|
||||
'archived' => $card->getArchived()
|
||||
],
|
||||
'attachment' => $attachment
|
||||
], $this->invokePrivate($this->activityManager, 'findDetailsForAttachment', [777]));
|
||||
], $this->invokePrivate($this->activityManager, 'findDetailsForAttachment', [$attachment]));
|
||||
}
|
||||
|
||||
public function invokePrivate(&$object, $methodName, array $parameters = []) {
|
||||
|
||||
@@ -42,7 +42,7 @@ use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Test\TestCase;
|
||||
|
||||
/** @internal Just for testing the service registration */
|
||||
class MyAttachmentService {
|
||||
class MyAttachmentService implements IAttachmentService {
|
||||
public function extendData(Attachment $attachment) {
|
||||
}
|
||||
public function display(Attachment $attachment) {
|
||||
@@ -84,6 +84,10 @@ class AttachmentServiceTest extends TestCase {
|
||||
private $l10n;
|
||||
/** @var ChangeHelper */
|
||||
private $changeHelper;
|
||||
/**
|
||||
* @var IAttachmentService|MockObject
|
||||
*/
|
||||
private $filesAppServiceImpl;
|
||||
|
||||
/**
|
||||
* @throws \OCP\AppFramework\QueryException
|
||||
@@ -92,6 +96,8 @@ class AttachmentServiceTest extends TestCase {
|
||||
parent::setUp();
|
||||
|
||||
$this->attachmentServiceImpl = $this->createMock(IAttachmentService::class);
|
||||
$this->filesAppServiceImpl = $this->createMock(IAttachmentService::class);
|
||||
|
||||
$this->appContainer = $this->createMock(IAppContainer::class);
|
||||
|
||||
$this->attachmentMapper = $this->createMock(AttachmentMapper::class);
|
||||
@@ -105,6 +111,8 @@ class AttachmentServiceTest extends TestCase {
|
||||
$this->cacheFactory->expects($this->any())->method('createDistributed')->willReturn($this->cache);
|
||||
|
||||
$this->appContainer->expects($this->at(0))->method('query')->with(FileService::class)->willReturn($this->attachmentServiceImpl);
|
||||
$this->appContainer->expects($this->at(1))->method('query')->with(FilesAppService::class)->willReturn($this->filesAppServiceImpl);
|
||||
|
||||
$this->application->expects($this->any())
|
||||
->method('getContainer')
|
||||
->willReturn($this->appContainer);
|
||||
@@ -119,8 +127,12 @@ class AttachmentServiceTest extends TestCase {
|
||||
$application = $this->createMock(Application::class);
|
||||
$appContainer = $this->createMock(IAppContainer::class);
|
||||
$fileServiceMock = $this->createMock(FileService::class);
|
||||
$appContainer->expects($this->at(1))->method('query')->with(MyAttachmentService::class)->willReturn(new MyAttachmentService());
|
||||
$fileAppServiceMock = $this->createMock(FilesAppService::class);
|
||||
|
||||
$appContainer->expects($this->at(0))->method('query')->with(FileService::class)->willReturn($fileServiceMock);
|
||||
$appContainer->expects($this->at(1))->method('query')->with(FilesAppService::class)->willReturn($fileAppServiceMock);
|
||||
$appContainer->expects($this->at(2))->method('query')->with(MyAttachmentService::class)->willReturn(new MyAttachmentService());
|
||||
|
||||
$application->expects($this->any())
|
||||
->method('getContainer')
|
||||
->willReturn($appContainer);
|
||||
@@ -135,8 +147,10 @@ class AttachmentServiceTest extends TestCase {
|
||||
$application = $this->createMock(Application::class);
|
||||
$appContainer = $this->createMock(IAppContainer::class);
|
||||
$fileServiceMock = $this->createMock(FileService::class);
|
||||
$fileAppServiceMock = $this->createMock(FilesAppService::class);
|
||||
$appContainer->expects($this->at(0))->method('query')->with(FileService::class)->willReturn($fileServiceMock);
|
||||
$appContainer->expects($this->at(1))->method('query')->with(MyAttachmentService::class)->willReturn(new MyAttachmentService());
|
||||
$appContainer->expects($this->at(1))->method('query')->with(FilesAppService::class)->willReturn($fileAppServiceMock);
|
||||
$appContainer->expects($this->at(2))->method('query')->with(MyAttachmentService::class)->willReturn(new MyAttachmentService());
|
||||
$application->expects($this->any())
|
||||
->method('getContainer')
|
||||
->willReturn($appContainer);
|
||||
@@ -256,7 +270,7 @@ class AttachmentServiceTest extends TestCase {
|
||||
->method('display')
|
||||
->with($attachment)
|
||||
->willReturn($response);
|
||||
$actual = $this->attachmentService->display(1);
|
||||
$actual = $this->attachmentService->display(1, 1);
|
||||
$this->assertEquals($response, $actual);
|
||||
}
|
||||
|
||||
@@ -272,8 +286,8 @@ class AttachmentServiceTest extends TestCase {
|
||||
$this->attachmentServiceImpl->expects($this->once())
|
||||
->method('display')
|
||||
->with($attachment)
|
||||
->will($this->throwException(new InvalidAttachmentType('deck_file')));
|
||||
$this->attachmentService->display(1);
|
||||
->will($this->throwException(new NotFoundException('deck_file')));
|
||||
$this->attachmentService->display(1, 1);
|
||||
}
|
||||
public function testUpdate() {
|
||||
$attachment = $this->createAttachment('deck_file', 'file_name.jpg');
|
||||
@@ -295,7 +309,7 @@ class AttachmentServiceTest extends TestCase {
|
||||
$a->setExtendedData(['mime' => 'image/jpeg']);
|
||||
});
|
||||
|
||||
$actual = $this->attachmentService->update(1, 'file_name.jpg');
|
||||
$actual = $this->attachmentService->update(1, 1, 'file_name.jpg');
|
||||
|
||||
$expected->setExtendedData(['mime' => 'image/jpeg']);
|
||||
$expected->setLastModified($attachment->getLastModified());
|
||||
@@ -319,7 +333,7 @@ class AttachmentServiceTest extends TestCase {
|
||||
$this->attachmentMapper->expects($this->once())
|
||||
->method('delete')
|
||||
->willReturn($attachment);
|
||||
$actual = $this->attachmentService->delete(1);
|
||||
$actual = $this->attachmentService->delete(1, 1);
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
@@ -344,7 +358,7 @@ class AttachmentServiceTest extends TestCase {
|
||||
->method('update')
|
||||
->willReturn($attachment);
|
||||
$expected->setDeletedAt(23);
|
||||
$actual = $this->attachmentService->delete(1);
|
||||
$actual = $this->attachmentService->delete(1, 1);
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
@@ -364,7 +378,7 @@ class AttachmentServiceTest extends TestCase {
|
||||
->method('update')
|
||||
->willReturn($attachment);
|
||||
$expected->setDeletedAt(0);
|
||||
$actual = $this->attachmentService->restore(1);
|
||||
$actual = $this->attachmentService->restore(1, 1);
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
@@ -381,6 +395,6 @@ class AttachmentServiceTest extends TestCase {
|
||||
$this->attachmentServiceImpl->expects($this->once())
|
||||
->method('allowUndo')
|
||||
->willReturn(false);
|
||||
$actual = $this->attachmentService->restore(1);
|
||||
$actual = $this->attachmentService->restore(1, 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,20 +123,9 @@ class BoardServiceTest extends TestCase {
|
||||
$b3 = new Board();
|
||||
$b3->setId(3);
|
||||
$this->boardMapper->expects($this->once())
|
||||
->method('findAllByUser')
|
||||
->method('findAllForUser')
|
||||
->with('admin')
|
||||
->willReturn([$b1, $b2]);
|
||||
$this->stackMapper->expects($this->any())
|
||||
->method('findAll')
|
||||
->willReturn([]);
|
||||
$this->boardMapper->expects($this->once())
|
||||
->method('findAllByGroups')
|
||||
->with('admin', ['a', 'b', 'c'])
|
||||
->willReturn([$b2, $b3]);
|
||||
$this->boardMapper->expects($this->once())
|
||||
->method('findAllByCircles')
|
||||
->with('admin')
|
||||
->willReturn([]);
|
||||
->willReturn([$b1, $b2, $b3]);
|
||||
$user = $this->createMock(IUser::class);
|
||||
$this->groupManager->method('getUserGroupIds')
|
||||
->willReturn(['a', 'b', 'c']);
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2018 Ryan Fletcher <ryan.fletcher@codepassion.ca>
|
||||
*
|
||||
* @author Ryan Fletcher <ryan.fletcher@codepassion.ca>
|
||||
*
|
||||
* @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\Controller;
|
||||
|
||||
use OCP\AppFramework\Http;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
use OCP\IRequest;
|
||||
|
||||
use OCA\Deck\Service\AttachmentService;
|
||||
use OCA\Deck\Db\Attachment;
|
||||
|
||||
class AttachmentApiControllerTest extends \Test\TestCase {
|
||||
private $appName = 'deck';
|
||||
private $controller;
|
||||
private $request;
|
||||
private $attachmentExample;
|
||||
private $cardId;
|
||||
private $attachmentService;
|
||||
|
||||
public function setUp(): void {
|
||||
parent::setUp();
|
||||
$this->attachmentExample = new Attachment();
|
||||
$this->attachmentExample->setId(1);
|
||||
$this->cardId = 1;
|
||||
$this->request = $this->createMock(IRequest::class);
|
||||
$this->attachmentService = $this->createMock(AttachmentService::class);
|
||||
$this->controller = new AttachmentApiController(
|
||||
$this->appName,
|
||||
$this->request,
|
||||
$this->attachmentService
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetAll() {
|
||||
$allAttachments = [$this->attachmentExample];
|
||||
|
||||
$this->attachmentService->expects($this->once())
|
||||
->method('findAll')
|
||||
->willReturn($allAttachments);
|
||||
|
||||
$this->request->expects($this->once())
|
||||
->method('getParam')
|
||||
->with('cardId')
|
||||
->willReturn($allAttachments);
|
||||
|
||||
$expected = new DataResponse($allAttachments, HTTP::STATUS_OK);
|
||||
$actual = $this->controller->getAll();
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
public function testDisplay() {
|
||||
$this->attachmentService->expects($this->once())
|
||||
->method('display')
|
||||
->willReturn($this->attachmentExample);
|
||||
|
||||
$this->request->expects($this->once())
|
||||
->method('getParam')
|
||||
->willReturn($this->attachmentExample->getId());
|
||||
|
||||
$expected = $this->attachmentExample;
|
||||
$actual = $this->controller->display();
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
public function testCreate() {
|
||||
$type = 'not null';
|
||||
$data = ['not null'];
|
||||
|
||||
$this->attachmentService->expects($this->once())
|
||||
->method('create')
|
||||
->willReturn($this->attachmentExample);
|
||||
|
||||
$this->request->expects($this->once())
|
||||
->method('getParam')
|
||||
->with('cardId')
|
||||
->willReturn($this->cardId);
|
||||
|
||||
$expected = new DataResponse($this->attachmentExample, HTTP::STATUS_OK);
|
||||
$actual = $this->controller->create($type, $data);
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
public function testUpdate() {
|
||||
|
||||
// FIXME: what is data supposed to be in this context?
|
||||
$data = ['not empty data'];
|
||||
|
||||
$this->attachmentService->expects($this->once())
|
||||
->method('update')
|
||||
->willReturn($this->attachmentExample);
|
||||
|
||||
$this->request->expects($this->once())
|
||||
->method('getParam')
|
||||
->willReturn($this->attachmentExample->getId());
|
||||
|
||||
$expected = new DataResponse($this->attachmentExample, HTTP::STATUS_OK);
|
||||
$actual = $this->controller->update($data);
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
public function testDelete() {
|
||||
$this->attachmentService->expects($this->once())
|
||||
->method('delete')
|
||||
->willReturn($this->attachmentExample);
|
||||
|
||||
$this->request->expects($this->once())
|
||||
->method('getParam')
|
||||
->willReturn($this->attachmentExample->getId());
|
||||
|
||||
$expected = new DataResponse($this->attachmentExample, HTTP::STATUS_OK);
|
||||
$actual = $this->controller->delete();
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
public function testRestore() {
|
||||
$this->attachmentService->expects($this->once())
|
||||
->method('restore')
|
||||
->willReturn($this->attachmentExample);
|
||||
|
||||
$this->request->expects($this->once())
|
||||
->method('getParam')
|
||||
->willReturn($this->attachmentExample->getId());
|
||||
|
||||
$expected = new DataResponse($this->attachmentExample, HTTP::STATUS_OK);
|
||||
$actual = $this->controller->restore();
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (c) 2018 Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @author Julius Härtl <jus@bitgrid.net>
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Controller;
|
||||
|
||||
use OCA\Deck\Service\AttachmentService;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\IRequest;
|
||||
|
||||
class AttachmentControllerTest extends \Test\TestCase {
|
||||
|
||||
/** @var Controller|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $controller;
|
||||
/** @var IRequest|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $request;
|
||||
/** @var AttachmentService|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $attachmentService;
|
||||
/** @var string */
|
||||
private $userId = 'user';
|
||||
|
||||
public function setUp(): void {
|
||||
$this->request = $this->createMock(IRequest::class);
|
||||
$this->attachmentService = $this->createMock(AttachmentService::class);
|
||||
$this->controller = new AttachmentController(
|
||||
'deck',
|
||||
$this->request,
|
||||
$this->attachmentService
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetAll() {
|
||||
$this->attachmentService->expects($this->once())->method('findAll')->with(1);
|
||||
$this->controller->getAll(1);
|
||||
}
|
||||
|
||||
public function testDisplay() {
|
||||
$this->attachmentService->expects($this->once())->method('display')->with(2);
|
||||
$this->controller->display(2);
|
||||
}
|
||||
|
||||
public function testCreate() {
|
||||
$this->request->expects($this->exactly(2))
|
||||
->method('getParam')
|
||||
->will($this->onConsecutiveCalls('type', 'data'));
|
||||
$this->attachmentService->expects($this->once())
|
||||
->method('create')
|
||||
->with(1, 'type', 'data')
|
||||
->willReturn(1);
|
||||
$this->assertEquals(1, $this->controller->create(1));
|
||||
}
|
||||
|
||||
public function testUpdate() {
|
||||
$this->request->expects($this->exactly(1))
|
||||
->method('getParam')
|
||||
->will($this->onConsecutiveCalls('data'));
|
||||
$this->attachmentService->expects($this->once())
|
||||
->method('update')
|
||||
->with(2, 'data')
|
||||
->willReturn(1);
|
||||
$this->assertEquals(1, $this->controller->update(2));
|
||||
}
|
||||
|
||||
|
||||
public function testDelete() {
|
||||
$this->attachmentService->expects($this->once())
|
||||
->method('delete')
|
||||
->with(234)
|
||||
->willReturn(1);
|
||||
$this->assertEquals(1, $this->controller->delete(234));
|
||||
}
|
||||
|
||||
public function testRestore() {
|
||||
$this->attachmentService->expects($this->once())
|
||||
->method('restore')
|
||||
->with(234)
|
||||
->willReturn(1);
|
||||
$this->assertEquals(1, $this->controller->restore(234));
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ namespace OCA\Deck\Controller;
|
||||
|
||||
use OCA\Deck\Service\ConfigService;
|
||||
use OCA\Deck\Service\PermissionService;
|
||||
use OCP\EventDispatcher\IEventDispatcher;
|
||||
use OCP\IInitialStateService;
|
||||
use OCP\IL10N;
|
||||
use OCP\IRequest;
|
||||
@@ -37,6 +38,7 @@ class PageControllerTest extends \Test\TestCase {
|
||||
private $permissionService;
|
||||
private $initialState;
|
||||
private $configService;
|
||||
private $eventDispatcher;
|
||||
|
||||
public function setUp(): void {
|
||||
$this->l10n = $this->createMock(IL10N::class);
|
||||
@@ -44,9 +46,10 @@ class PageControllerTest extends \Test\TestCase {
|
||||
$this->permissionService = $this->createMock(PermissionService::class);
|
||||
$this->configService = $this->createMock(ConfigService::class);
|
||||
$this->initialState = $this->createMock(IInitialStateService::class);
|
||||
$this->eventDispatcher = $this->createMock(IEventDispatcher::class);
|
||||
|
||||
$this->controller = new PageController(
|
||||
'deck', $this->request, $this->permissionService, $this->initialState, $this->configService
|
||||
'deck', $this->request, $this->permissionService, $this->initialState, $this->configService, $this->eventDispatcher
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user