Merge pull request #488 from nextcloud/feature/109/file-attachments
File attachments for cards
This commit is contained in:
@@ -33,6 +33,7 @@ rules:
|
||||
no-fallthrough: error
|
||||
no-mixed-spaces-and-tabs: error
|
||||
no-unused-vars: warn
|
||||
no-useless-escape: warn
|
||||
no-use-before-define: error
|
||||
semi: ["error", "always"]
|
||||
indent:
|
||||
|
||||
@@ -198,12 +198,6 @@
|
||||
<autoincrement>1</autoincrement>
|
||||
<length>4</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>title</name>
|
||||
<type>text</type>
|
||||
<notnull>true</notnull>
|
||||
<length>100</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>card_id</name>
|
||||
<type>integer</type>
|
||||
@@ -218,12 +212,12 @@
|
||||
</field>
|
||||
<field>
|
||||
<name>data</name>
|
||||
<type>clob</type>
|
||||
<type>text</type>
|
||||
</field>
|
||||
<field>
|
||||
<name>last_modified</name>
|
||||
<type>integer</type>
|
||||
<default></default>
|
||||
<default/>
|
||||
<length>8</length>
|
||||
<notnull>false</notnull>
|
||||
<unsigned>true</unsigned>
|
||||
@@ -231,7 +225,21 @@
|
||||
<field>
|
||||
<name>created_at</name>
|
||||
<type>integer</type>
|
||||
<default></default>
|
||||
<default/>
|
||||
<length>8</length>
|
||||
<notnull>false</notnull>
|
||||
<unsigned>true</unsigned>
|
||||
</field>
|
||||
<field>
|
||||
<name>created_by</name>
|
||||
<type>text</type>
|
||||
<notnull>true</notnull>
|
||||
<length>64</length>
|
||||
</field>
|
||||
<field>
|
||||
<name>deleted_at</name>
|
||||
<type>integer</type>
|
||||
<default>0</default>
|
||||
<length>8</length>
|
||||
<notnull>false</notnull>
|
||||
<unsigned>true</unsigned>
|
||||
|
||||
@@ -59,6 +59,16 @@ return [
|
||||
['name' => 'card#assignUser', 'url' => '/cards/{cardId}/assign', 'verb' => 'POST'],
|
||||
['name' => 'card#unassignUser', 'url' => '/cards/{cardId}/assign/{userId}', 'verb' => 'DELETE'],
|
||||
|
||||
['name' => 'attachment#getAll', 'url' => '/cards/{cardId}/attachments', 'verb' => 'GET'],
|
||||
['name' => 'attachment#create', 'url' => '/cards/{cardId}/attachment', 'verb' => 'POST'],
|
||||
['name' => 'attachment#display', 'url' => '/cards/{cardId}/attachment/{attachmentId}', 'verb' => 'GET'],
|
||||
['name' => 'attachment#update', 'url' => '/cards/{cardId}/attachment/{attachmentId}', 'verb' => 'PUT'],
|
||||
// also allow to use POST for updates so we can properly access files when using application/x-www-form-urlencoded
|
||||
['name' => 'attachment#update', 'url' => '/cards/{cardId}/attachment/{attachmentId}', 'verb' => 'POST'],
|
||||
['name' => 'attachment#delete', 'url' => '/cards/{cardId}/attachment/{attachmentId}', 'verb' => 'DELETE'],
|
||||
['name' => 'attachment#restore', 'url' => '/cards/{cardId}/attachment/{attachmentId}/restore', 'verb' => 'GET'],
|
||||
|
||||
|
||||
// labels
|
||||
['name' => 'label#create', 'url' => '/labels', 'verb' => 'POST'],
|
||||
['name' => 'label#update', 'url' => '/labels/{labelId}', 'verb' => 'PUT'],
|
||||
|
||||
154
css/style.scss
154
css/style.scss
@@ -143,6 +143,10 @@ input.input-inline {
|
||||
|
||||
.card {
|
||||
opacity: 1;
|
||||
|
||||
&.file-drop {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
&.card-selected {
|
||||
@@ -261,10 +265,6 @@ input.input-inline {
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
> button {
|
||||
padding: 16px 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.filter-select {
|
||||
@@ -461,7 +461,7 @@ input.input-inline {
|
||||
}
|
||||
}
|
||||
|
||||
.card-tasks {
|
||||
.card-tasks, .card-files {
|
||||
border-radius: 3px;
|
||||
margin: 4px 4px 4px 0px;
|
||||
padding: 0 2px;
|
||||
@@ -472,6 +472,7 @@ input.input-inline {
|
||||
|
||||
.icon {
|
||||
background-size: contain;
|
||||
margin-right: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -653,6 +654,32 @@ input.input-inline {
|
||||
}
|
||||
}
|
||||
|
||||
.drop-indicator {
|
||||
display: none;
|
||||
}
|
||||
.card .nv-file-over,
|
||||
.drop-indicator.nv-file-over {
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #fff;
|
||||
z-index: 100;
|
||||
opacity: 0.9;
|
||||
text-align: center;
|
||||
|
||||
p {
|
||||
width: calc(100% - 20px);
|
||||
height: calc(100% - 20px);
|
||||
position: absolute;
|
||||
padding: 20px;
|
||||
border: 1px dashed #AAA;
|
||||
margin: 10px;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#card-meta { // TODO: use .card-block instead?
|
||||
height: 100%;
|
||||
display: flex;
|
||||
@@ -696,10 +723,23 @@ input.input-inline {
|
||||
flex: 1;
|
||||
}
|
||||
}
|
||||
.section-header-tabbed {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 5px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
.tabHeaders {
|
||||
margin: 0;
|
||||
flex-grow: 1;
|
||||
}
|
||||
.tabDetails {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.save-indicator {
|
||||
border-radius: 3px;
|
||||
float: right;
|
||||
margin: 5px;
|
||||
padding: 0 10px;
|
||||
font-size: 8pt;
|
||||
display: none;
|
||||
@@ -750,6 +790,97 @@ input.input-inline {
|
||||
}
|
||||
}
|
||||
|
||||
.icon-upload.icon-loading-small {
|
||||
background-image: none;
|
||||
}
|
||||
.attachment-list-wrapper {
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba($color-darkgrey, 0.5);
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
.attachment-list {
|
||||
&.selector {
|
||||
padding: 10px;
|
||||
position: absolute;
|
||||
width: 30%;
|
||||
max-width: 500px;
|
||||
min-width: 200px;
|
||||
max-height: 50%;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
background-color: $color-main-background;
|
||||
z-index: 2;
|
||||
border-radius: 3px;
|
||||
box-shadow: 0 0 3px $color-darkgrey;
|
||||
overflow: scroll;
|
||||
}
|
||||
h3.attachment-selector {
|
||||
margin: 0 0 10px;
|
||||
padding: 0;
|
||||
.icon-close {
|
||||
display: inline-block;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
|
||||
li.attachment {
|
||||
display: flex;
|
||||
padding: 3px;
|
||||
|
||||
&.deleted {
|
||||
opacity: .5;
|
||||
}
|
||||
|
||||
.fileicon {
|
||||
display: inline-block;
|
||||
min-width: 32px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background-size: contain;
|
||||
}
|
||||
.details {
|
||||
flex-grow: 1;
|
||||
flex-shrink: 1;
|
||||
min-width: 0;
|
||||
flex-basis: 50%;
|
||||
line-height: 110%;
|
||||
padding: 2px;
|
||||
}
|
||||
.filename {
|
||||
width: 70%;
|
||||
display: flex;
|
||||
.basename {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-bottom: 2px;
|
||||
}
|
||||
.extension {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
.filesize, .filedate {
|
||||
font-size: 90%;
|
||||
color: $color-darkgrey;
|
||||
}
|
||||
.app-popover-menu-utils {
|
||||
position: relative;
|
||||
right: -10px;
|
||||
button {
|
||||
height: 32px;
|
||||
width: 42px;
|
||||
}
|
||||
}
|
||||
button.icon-history {
|
||||
width: 44px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.card-description {
|
||||
&.section-header {
|
||||
.save-indicator {
|
||||
@@ -1137,7 +1268,11 @@ input.input-inline {
|
||||
border: 0 !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
.select2-search-field {
|
||||
margin-right: -10px;
|
||||
}
|
||||
}
|
||||
|
||||
.select2-choice {
|
||||
height: auto;
|
||||
}
|
||||
@@ -1234,6 +1369,13 @@ input.input-inline {
|
||||
}
|
||||
}
|
||||
|
||||
img {
|
||||
max-width: 100%;
|
||||
max-height: 50vh;
|
||||
margin: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
input[type=checkbox] {
|
||||
margin: 0px 10px 0px 0px;
|
||||
line-height: 10px;
|
||||
|
||||
@@ -47,12 +47,14 @@ import angularuiselect from 'ui-select';
|
||||
import ngsortable from 'ng-sortable';
|
||||
import md from 'angular-markdown-it';
|
||||
import nganimate from 'angular-animate';
|
||||
import 'angular-file-upload';
|
||||
|
||||
var app = angular.module('Deck', [
|
||||
ngsanitize,
|
||||
uirouter,
|
||||
angularuiselect,
|
||||
ngsortable, md, nganimate
|
||||
ngsortable, md, nganimate,
|
||||
'angularFileUpload'
|
||||
]);
|
||||
|
||||
export default app;
|
||||
|
||||
@@ -74,6 +74,9 @@ app.config(function ($provide, $interpolateProvider, $httpProvider, $urlRouterPr
|
||||
})
|
||||
.state('board.card', {
|
||||
url: '/card/:cardId',
|
||||
params: {
|
||||
tab: {value: 0, dynamic: true},
|
||||
},
|
||||
views: {
|
||||
'sidebarView': {
|
||||
templateUrl: '/card.sidebarView.html',
|
||||
@@ -82,4 +85,28 @@ app.config(function ($provide, $interpolateProvider, $httpProvider, $urlRouterPr
|
||||
}
|
||||
});
|
||||
|
||||
$provide.decorator('nvFileOverDirective', function ($delegate) {
|
||||
var directive = $delegate[0],
|
||||
link = directive.link;
|
||||
|
||||
directive.compile = function () {
|
||||
return function (scope, element, attrs) {
|
||||
var overClass = attrs.overClass || 'nv-file-over';
|
||||
link.apply(this, arguments);
|
||||
let counter = 0;
|
||||
element.on('dragenter', function (event) {
|
||||
counter++;
|
||||
});
|
||||
element.on('dragleave', function (event) {
|
||||
counter--;
|
||||
if (counter <= 0) {
|
||||
$('.' + overClass).removeClass(overClass);
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
return $delegate;
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -24,6 +24,7 @@ import app from './App.js';
|
||||
/* global Snap */
|
||||
app.run(function ($document, $rootScope, $transitions, BoardService) {
|
||||
'use strict';
|
||||
|
||||
$document.click(function (event) {
|
||||
$rootScope.$broadcast('documentClicked', event);
|
||||
});
|
||||
|
||||
78
js/controller/AttachmentController.js
Normal file
78
js/controller/AttachmentController.js
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
/* global OC */
|
||||
|
||||
class AttachmentListController {
|
||||
constructor ($scope, CardService, FileService) {
|
||||
'ngInject';
|
||||
this.cardservice = CardService;
|
||||
this.fileservice = FileService;
|
||||
this.attachments = CardService.getCurrent().attachments;
|
||||
}
|
||||
|
||||
mimetypeForAttachment(attachment) {
|
||||
let url = OC.MimeType.getIconUrl(attachment.extendedData.mimetype);
|
||||
let styles = {
|
||||
'background-image': `url("${url}")`,
|
||||
};
|
||||
return styles;
|
||||
}
|
||||
|
||||
attachmentUrl(attachment) {
|
||||
let cardId = this.cardservice.getCurrent().id;
|
||||
let attachmentId = attachment.id;
|
||||
return OC.generateUrl(`/apps/deck/cards/${cardId}/attachment/${attachmentId}`);
|
||||
}
|
||||
|
||||
getAttachmentMarkdown(attachment) {
|
||||
const inlineMimetypes = ['image/png', 'image/jpg', 'image/jpeg'];
|
||||
let url = this.attachmentUrl(attachment);
|
||||
let filename = attachment.data;
|
||||
let insertText = `[📎 ${filename}](${url})`;
|
||||
if (inlineMimetypes.indexOf(attachment.extendedData.mimetype) > -1) {
|
||||
insertText = ``;
|
||||
}
|
||||
return insertText;
|
||||
}
|
||||
|
||||
select(attachment) {
|
||||
this.onSelect({attachment: this.getAttachmentMarkdown(attachment)});
|
||||
}
|
||||
|
||||
abort() {
|
||||
this.onAbort();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let attachmentListComponent = {
|
||||
templateUrl: '/card.attachments.html',
|
||||
controller: AttachmentListController,
|
||||
bindings: {
|
||||
isFileSelector: '<',
|
||||
attachments: '=',
|
||||
onSelect: '&',
|
||||
onAbort: '&'
|
||||
}
|
||||
};
|
||||
export default attachmentListComponent;
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
import app from '../app/App.js';
|
||||
/* global oc_defaults OC */
|
||||
app.controller('BoardController', function ($rootScope, $scope, $stateParams, StatusService, BoardService, StackService, CardService, LabelService, $state, $transitions, $filter) {
|
||||
app.controller('BoardController', function ($rootScope, $scope, $stateParams, StatusService, BoardService, StackService, CardService, LabelService, $state, $transitions, $filter, FileService) {
|
||||
|
||||
$scope.sidebar = $rootScope.sidebar;
|
||||
|
||||
@@ -40,6 +40,7 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St
|
||||
$scope.labelservice = LabelService;
|
||||
$scope.defaultColors = ['31CC7C', '317CCC', 'FF7A66', 'F1DB50', '7C31CC', 'CC317C', '3A3B3D', 'CACBCD'];
|
||||
$scope.board = BoardService.getCurrent();
|
||||
$scope.uploader = FileService.uploader;
|
||||
|
||||
// workaround for $stateParams changes not being propagated
|
||||
$scope.$watch(function() {
|
||||
@@ -47,7 +48,7 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St
|
||||
}, function (params) {
|
||||
$scope.params = params;
|
||||
}, true);
|
||||
$scope.params = $state;
|
||||
$scope.params = $state.params;
|
||||
|
||||
/**
|
||||
* Check for markdown checkboxes in description to render the counter
|
||||
@@ -353,4 +354,11 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St
|
||||
};
|
||||
};
|
||||
|
||||
$scope.attachmentCount = function(card) {
|
||||
if (Array.isArray(card.attachments)) {
|
||||
return card.attachments.filter((obj) => obj.deletedAt === 0).length;
|
||||
}
|
||||
return card.attachmentCount;
|
||||
};
|
||||
|
||||
});
|
||||
|
||||
@@ -20,10 +20,10 @@
|
||||
*
|
||||
*/
|
||||
|
||||
/* global app moment */
|
||||
/* global app moment angular OC */
|
||||
import app from '../app/App.js';
|
||||
|
||||
app.controller('CardController', function ($scope, $rootScope, $sce, $location, $stateParams, $interval, $timeout, $filter, BoardService, CardService, StackService, StatusService, markdownItConverter) {
|
||||
app.controller('CardController', function ($scope, $rootScope, $sce, $location, $stateParams, $state, $interval, $timeout, $filter, BoardService, CardService, StackService, StatusService, markdownItConverter, FileService) {
|
||||
$scope.sidebar = $rootScope.sidebar;
|
||||
$scope.status = {
|
||||
lastEdit: 0,
|
||||
@@ -31,11 +31,44 @@ app.controller('CardController', function ($scope, $rootScope, $sce, $location,
|
||||
};
|
||||
|
||||
$scope.cardservice = CardService;
|
||||
$scope.fileservice = FileService;
|
||||
$scope.cardId = $stateParams.cardId;
|
||||
|
||||
$scope.statusservice = StatusService.getInstance();
|
||||
$scope.boardservice = BoardService;
|
||||
|
||||
$scope.isArray = angular.isArray;
|
||||
// workaround for $stateParams changes not being propagated
|
||||
$scope.$watch(function() {
|
||||
return $state.params;
|
||||
}, function (params) {
|
||||
$scope.params = params;
|
||||
}, true);
|
||||
$scope.params = $state.params;
|
||||
|
||||
$scope.addAttachmentToDescription = function(insertText) {
|
||||
let el = document.querySelectorAll('textarea')[0];
|
||||
let start = el.selectionStart;
|
||||
let end = el.selectionEnd;
|
||||
let text = $scope.status.edit.description || '';
|
||||
let before = text.substring(0, start);
|
||||
let after = text.substring(end, text.length);
|
||||
let newText = before + '\n' + insertText + '\n' + after;
|
||||
$scope.status.edit.description = newText;
|
||||
el.selectionStart = el.selectionEnd = start + newText.length;
|
||||
el.focus();
|
||||
$scope.status.continueEdit = false;
|
||||
$scope.cardEditDescriptionChanged();
|
||||
$scope.status.selectAttachment = false;
|
||||
};
|
||||
|
||||
$scope.abortAttachmentSelection = function() {
|
||||
$scope.status.continueEdit = false;
|
||||
$scope.status.selectAttachment = false;
|
||||
let el = document.querySelectorAll('textarea')[0];
|
||||
el.focus();
|
||||
};
|
||||
|
||||
$scope.statusservice.retainWaiting();
|
||||
|
||||
$scope.description = function() {
|
||||
@@ -68,8 +101,8 @@ app.controller('CardController', function ($scope, $rootScope, $sce, $location,
|
||||
var reg = /\[(X|\s|\_|\-)\]\s(.*)/ig;
|
||||
var nth = 0;
|
||||
$scope.status.edit.description = $scope.status.edit.description.replace(reg, function (match, i, original) {
|
||||
var result = match;
|
||||
if (nth++ === id) {
|
||||
var result;
|
||||
if (match.match(/^\[\s\]/i)) {
|
||||
result = match.replace(/\[\s\]/i, '[x]');
|
||||
}
|
||||
@@ -81,10 +114,9 @@ app.controller('CardController', function ($scope, $rootScope, $sce, $location,
|
||||
return match;
|
||||
});
|
||||
CardService.update($scope.status.edit).then(function (data) {
|
||||
var header = $('.section-header.card-description');
|
||||
var header = $('.section-header-tabbed .tabDetails');
|
||||
header.find('.save-indicator.unsaved').hide();
|
||||
header.find('.save-indicator.saved').fadeIn(250).fadeOut(1000);
|
||||
StackService.updateCard($scope.status.edit);
|
||||
});
|
||||
$('#markdown input[type=checkbox]').removeAttr('disabled');
|
||||
|
||||
@@ -111,7 +143,7 @@ app.controller('CardController', function ($scope, $rootScope, $sce, $location,
|
||||
};
|
||||
$scope.cardEditDescriptionChanged = function ($event) {
|
||||
$scope.status.lastEdit = Date.now();
|
||||
var header = $('.section-header.card-description');
|
||||
var header = $('.section-header-tabbed .tabDetails');
|
||||
header.find('.save-indicator.unsaved').show();
|
||||
header.find('.save-indicator.saved').hide();
|
||||
};
|
||||
@@ -121,13 +153,12 @@ app.controller('CardController', function ($scope, $rootScope, $sce, $location,
|
||||
if (timeSinceEdit > 1000 && $scope.status.lastEdit > $scope.status.lastSave && !$scope.status.saving) {
|
||||
$scope.status.lastSave = currentTime;
|
||||
$scope.status.saving = true;
|
||||
var header = $('.section-header.card-description');
|
||||
var header = $('.section-header-tabbed .tabDetails');
|
||||
header.find('.save-indicator.unsaved').fadeIn(500);
|
||||
CardService.update($scope.status.edit).then(function (data) {
|
||||
var header = $('.section-header.card-description');
|
||||
var header = $('.section-header-tabbed .tabDetails');
|
||||
header.find('.save-indicator.unsaved').hide();
|
||||
header.find('.save-indicator.saved').fadeIn(250).fadeOut(1000);
|
||||
StackService.updateCard($scope.status.edit);
|
||||
$scope.status.saving = false;
|
||||
});
|
||||
}
|
||||
@@ -136,29 +167,26 @@ app.controller('CardController', function ($scope, $rootScope, $sce, $location,
|
||||
// handle rename to update information on the board as well
|
||||
$scope.cardRename = function (card) {
|
||||
CardService.rename(card).then(function (data) {
|
||||
StackService.updateCard(card);
|
||||
$scope.status.renameCard = false;
|
||||
});
|
||||
};
|
||||
$scope.cardUpdate = function (card) {
|
||||
CardService.update(card).then(function (data) {
|
||||
$scope.status.cardEditDescription = false;
|
||||
var header = $('.section-content.card-description');
|
||||
$scope.updateMarkdown($scope.status.edit.description);
|
||||
var header = $('.section-header-tabbed .tabDetails');
|
||||
header.find('.save-indicator.unsaved').hide();
|
||||
header.find('.save-indicator.saved').fadeIn(500).fadeOut(1000);
|
||||
StackService.updateCard(card);
|
||||
});
|
||||
};
|
||||
|
||||
$scope.labelAssign = function (element, model) {
|
||||
CardService.assignLabel($scope.cardId, element.id).then(function (data) {
|
||||
StackService.updateCard(CardService.getCurrent());
|
||||
});
|
||||
};
|
||||
|
||||
$scope.labelRemove = function (element, model) {
|
||||
CardService.removeLabel($scope.cardId, element.id).then(function (data) {
|
||||
StackService.updateCard(CardService.getCurrent());
|
||||
});
|
||||
};
|
||||
|
||||
@@ -173,7 +201,6 @@ app.controller('CardController', function ($scope, $rootScope, $sce, $location,
|
||||
newDate.year(duedate.year());
|
||||
element.duedate = newDate.toISOString();
|
||||
CardService.update(element);
|
||||
StackService.updateCard(element);
|
||||
};
|
||||
$scope.setDuedateTime = function (time) {
|
||||
var element = CardService.getCurrent();
|
||||
@@ -185,14 +212,12 @@ app.controller('CardController', function ($scope, $rootScope, $sce, $location,
|
||||
newDate.minute(time.minute());
|
||||
element.duedate = newDate.toISOString();
|
||||
CardService.update(element);
|
||||
StackService.updateCard(element);
|
||||
};
|
||||
|
||||
$scope.resetDuedate = function () {
|
||||
var element = CardService.getCurrent();
|
||||
element.duedate = null;
|
||||
CardService.update(element);
|
||||
StackService.updateCard(element);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -216,14 +241,12 @@ app.controller('CardController', function ($scope, $rootScope, $sce, $location,
|
||||
|
||||
$scope.addAssignedUser = function(item) {
|
||||
CardService.assignUser(CardService.getCurrent(), item.uid).then(function (data) {
|
||||
StackService.updateCard(CardService.getCurrent());
|
||||
});
|
||||
$scope.status.showAssignUser = false;
|
||||
};
|
||||
|
||||
$scope.removeAssignedUser = function(uid) {
|
||||
CardService.unassignUser(CardService.getCurrent(), uid).then(function (data) {
|
||||
StackService.updateCard(CardService.getCurrent());
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
37
js/filters/bytesFilter.js
Normal file
37
js/filters/bytesFilter.js
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import app from '../app/App.js';
|
||||
|
||||
app.filter('bytes', function () {
|
||||
return function (bytes, precision) {
|
||||
if (isNaN(parseFloat(bytes, 10)) || !isFinite(bytes)) {
|
||||
return '-';
|
||||
}
|
||||
if (typeof precision === 'undefined') {
|
||||
precision = 2;
|
||||
}
|
||||
var units = ['bytes', 'KB', 'MB', 'GB', 'TB', 'PB'],
|
||||
number = Math.floor(Math.log(bytes) / Math.log(1024));
|
||||
return (bytes / Math.pow(1024, Math.floor(number))).toFixed(precision) + ' ' + units[number];
|
||||
};
|
||||
});
|
||||
@@ -13,7 +13,10 @@ import './app/Run.js';
|
||||
|
||||
|
||||
import ListController from 'controller/ListController.js';
|
||||
import attachmentListComponent from './controller/AttachmentController.js';
|
||||
|
||||
app.controller('ListController', ListController);
|
||||
app.component('attachmentListComponent', attachmentListComponent);
|
||||
|
||||
|
||||
// require all the js files from subdirectories
|
||||
|
||||
107
js/package-lock.json
generated
107
js/package-lock.json
generated
@@ -317,7 +317,6 @@
|
||||
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.2.0.tgz",
|
||||
"integrity": "sha512-c+R/U5X+2zz2+UCrCFv6odQzJdoqI+YecuhnAJLa1zYaMc13zPfwMwZrr91Pd1DYNo/yPRbiM4WVf9whgwFsIg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"es6-promisify": "^5.0.0"
|
||||
}
|
||||
@@ -405,6 +404,11 @@
|
||||
"resolved": "https://registry.npmjs.org/angular-animate/-/angular-animate-1.7.2.tgz",
|
||||
"integrity": "sha512-/MQL2FEFXhdzFUKJ9AJq6gAYz3YGsh2xHTztuQKY5FkqjEBpF5YGypgprTz153P7t51T8nFqLsG8jwkpRdM6gg=="
|
||||
},
|
||||
"angular-file-upload": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/angular-file-upload/-/angular-file-upload-2.5.0.tgz",
|
||||
"integrity": "sha1-D1PJP7xw7YuoNAqaKJumS2gOIWc="
|
||||
},
|
||||
"angular-markdown-it": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/angular-markdown-it/-/angular-markdown-it-0.6.1.tgz",
|
||||
@@ -1201,8 +1205,7 @@
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/buffer-more-ints/-/buffer-more-ints-0.0.2.tgz",
|
||||
"integrity": "sha1-JrOIXRD6E9t/wBquOquHAZngEkw=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"buffer-xor": {
|
||||
"version": "1.0.3",
|
||||
@@ -2321,15 +2324,13 @@
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.4.tgz",
|
||||
"integrity": "sha512-/NdNZVJg+uZgtm9eS3O6lrOLYmQag2DjdEXuPaHlZ6RuVqgqaVZfgYCepEIKsLqwdQArOPtC3XzRLqGGfT8KQQ==",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"es6-promisify": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
|
||||
"integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"es6-promise": "^4.0.3"
|
||||
}
|
||||
@@ -2899,8 +2900,7 @@
|
||||
"ansi-regex": {
|
||||
"version": "2.1.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"aproba": {
|
||||
"version": "1.2.0",
|
||||
@@ -2921,14 +2921,12 @@
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
@@ -2943,20 +2941,17 @@
|
||||
"code-point-at": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"console-control-strings": {
|
||||
"version": "1.1.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"core-util-is": {
|
||||
"version": "1.0.2",
|
||||
@@ -3073,8 +3068,7 @@
|
||||
"inherits": {
|
||||
"version": "2.0.3",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"ini": {
|
||||
"version": "1.3.5",
|
||||
@@ -3086,7 +3080,6 @@
|
||||
"version": "1.0.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"number-is-nan": "^1.0.0"
|
||||
}
|
||||
@@ -3101,7 +3094,6 @@
|
||||
"version": "3.0.4",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
@@ -3109,14 +3101,12 @@
|
||||
"minimist": {
|
||||
"version": "0.0.8",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"minipass": {
|
||||
"version": "2.2.4",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"safe-buffer": "^5.1.1",
|
||||
"yallist": "^3.0.0"
|
||||
@@ -3135,7 +3125,6 @@
|
||||
"version": "0.5.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"minimist": "0.0.8"
|
||||
}
|
||||
@@ -3216,8 +3205,7 @@
|
||||
"number-is-nan": {
|
||||
"version": "1.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"object-assign": {
|
||||
"version": "4.1.1",
|
||||
@@ -3229,7 +3217,6 @@
|
||||
"version": "1.4.0",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
@@ -3315,8 +3302,7 @@
|
||||
"safe-buffer": {
|
||||
"version": "5.1.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
@@ -3352,7 +3338,6 @@
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"code-point-at": "^1.0.0",
|
||||
"is-fullwidth-code-point": "^1.0.0",
|
||||
@@ -3372,7 +3357,6 @@
|
||||
"version": "3.0.1",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ansi-regex": "^2.0.0"
|
||||
}
|
||||
@@ -3416,14 +3400,12 @@
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "3.0.2",
|
||||
"bundled": true,
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -3880,7 +3862,6 @@
|
||||
"resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz",
|
||||
"integrity": "sha512-qwHbBLV7WviBl0rQsOzH6o5lwyOIvwp/BdFnvVxXORldu5TmjFfjzBcWUWS5kWAZhmv+JtiDhSuQCp4sBfbIgg==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"agent-base": "4",
|
||||
"debug": "3.1.0"
|
||||
@@ -3891,7 +3872,6 @@
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
@@ -3914,18 +3894,24 @@
|
||||
"resolved": "https://registry.npmjs.org/httpntlm/-/httpntlm-1.6.1.tgz",
|
||||
"integrity": "sha1-rQFScUOi6Hc8+uapb1hla7UqNLI=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"httpreq": ">=0.4.22",
|
||||
"underscore": "~1.7.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"underscore": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz",
|
||||
"integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"httpreq": {
|
||||
"version": "0.4.24",
|
||||
"resolved": "https://registry.npmjs.org/httpreq/-/httpreq-0.4.24.tgz",
|
||||
"integrity": "sha1-QzX/2CzZaWaKOUZckprGHWOTYn8=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"https-browserify": {
|
||||
"version": "1.0.0",
|
||||
@@ -3938,7 +3924,6 @@
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz",
|
||||
"integrity": "sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"agent-base": "^4.1.0",
|
||||
"debug": "^3.1.0"
|
||||
@@ -3949,7 +3934,6 @@
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
|
||||
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
@@ -4226,8 +4210,7 @@
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.5.tgz",
|
||||
"integrity": "sha1-vd7XARQpCCjAoDnnLvJfWq7ENUo=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"is-absolute-url": {
|
||||
"version": "2.1.0",
|
||||
@@ -4657,15 +4640,13 @@
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/libbase64/-/libbase64-0.1.0.tgz",
|
||||
"integrity": "sha1-YjUag5VjrF/1vSbxL2Dpgwu3UeY=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"libmime": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/libmime/-/libmime-3.0.0.tgz",
|
||||
"integrity": "sha1-UaGp50SOy9Ms2lRCFnW7IbwJPaY=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"iconv-lite": "0.4.15",
|
||||
"libbase64": "0.1.0",
|
||||
@@ -4676,8 +4657,7 @@
|
||||
"version": "0.4.15",
|
||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.15.tgz",
|
||||
"integrity": "sha1-/iZaIYrGpXz+hUkn6dBMGYJe3es=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -4685,8 +4665,7 @@
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/libqp/-/libqp-1.1.0.tgz",
|
||||
"integrity": "sha1-9ebgatdLeU+1tbZpiL9yjvHe2+g=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"linkify-it": {
|
||||
"version": "2.0.3",
|
||||
@@ -5503,15 +5482,13 @@
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/nodemailer-fetch/-/nodemailer-fetch-1.6.0.tgz",
|
||||
"integrity": "sha1-ecSQihwPXzdbc/6IjamCj23JY6Q=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"nodemailer-shared": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/nodemailer-shared/-/nodemailer-shared-1.1.0.tgz",
|
||||
"integrity": "sha1-z1mU4v0mjQD1zw+nZ6CBae2wfsA=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"nodemailer-fetch": "1.6.0"
|
||||
}
|
||||
@@ -5544,8 +5521,7 @@
|
||||
"version": "0.1.10",
|
||||
"resolved": "https://registry.npmjs.org/nodemailer-wellknown/-/nodemailer-wellknown-0.1.10.tgz",
|
||||
"integrity": "sha1-WG24EB2zDLRDjrVGc3pBqtDPE9U=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"nopt": {
|
||||
"version": "3.0.6",
|
||||
@@ -6624,8 +6600,7 @@
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
|
||||
"integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"prepend-http": {
|
||||
"version": "1.0.4",
|
||||
@@ -7386,15 +7361,13 @@
|
||||
"version": "1.1.15",
|
||||
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-1.1.15.tgz",
|
||||
"integrity": "sha1-fxFLW2X6s+KjWqd1uxLw0cZJvxY=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
"dev": true
|
||||
},
|
||||
"smtp-connection": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/smtp-connection/-/smtp-connection-2.12.0.tgz",
|
||||
"integrity": "sha1-1275EnyyPCJZ7bHoNJwujV4tdME=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"httpntlm": "1.6.1",
|
||||
"nodemailer-shared": "1.1.0"
|
||||
@@ -7591,7 +7564,6 @@
|
||||
"resolved": "https://registry.npmjs.org/socks/-/socks-1.1.10.tgz",
|
||||
"integrity": "sha1-W4t/x8jzQcU+0FbpKbe/Tei6e1o=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"ip": "^1.1.4",
|
||||
"smart-buffer": "^1.0.13"
|
||||
@@ -7602,7 +7574,6 @@
|
||||
"resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-3.0.1.tgz",
|
||||
"integrity": "sha512-ZwEDymm204mTzvdqyUqOdovVr2YRd2NYskrYrF2LXyZ9qDiMAoFESGK8CRphiO7rtbo2Y757k2Nia3x2hGtalA==",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"agent-base": "^4.1.0",
|
||||
"socks": "^1.1.10"
|
||||
@@ -8127,7 +8098,6 @@
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
|
||||
"integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"requires": {
|
||||
"prelude-ls": "~1.1.2"
|
||||
}
|
||||
@@ -8212,13 +8182,6 @@
|
||||
"integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==",
|
||||
"dev": true
|
||||
},
|
||||
"underscore": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/underscore/-/underscore-1.7.0.tgz",
|
||||
"integrity": "sha1-a7rwh3UA02vjTsqlhODbn+8DUgk=",
|
||||
"dev": true,
|
||||
"optional": true
|
||||
},
|
||||
"union-value": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz",
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"dependencies": {
|
||||
"angular": "^1.7.2",
|
||||
"angular-animate": "^1.7.2",
|
||||
"angular-file-upload": "^2.5.0",
|
||||
"angular-markdown-it": "^0.6.1",
|
||||
"angular-sanitize": "^1.7.2",
|
||||
"markdown-it": "^8.4.1",
|
||||
|
||||
@@ -125,7 +125,7 @@ app.factory('ApiService', function ($http, $q) {
|
||||
this.data[entity.id] = entity;
|
||||
} else {
|
||||
Object.keys(entity).forEach(function (key) {
|
||||
if (entity[key] !== null) {
|
||||
if (entity[key] !== null && element[key] !== entity[key]) {
|
||||
element[key] = entity[key];
|
||||
}
|
||||
});
|
||||
@@ -163,6 +163,10 @@ app.factory('ApiService', function ($http, $q) {
|
||||
return this.data;
|
||||
};
|
||||
|
||||
ApiService.prototype.get = function (id) {
|
||||
return this.data[id];
|
||||
};
|
||||
|
||||
ApiService.prototype.getName = function () {
|
||||
var funcNameRegex = /function (.{1,})\(/;
|
||||
var results = (funcNameRegex).exec((this).constructor.toString());
|
||||
|
||||
@@ -198,13 +198,13 @@ app.factory('BoardService', function (ApiService, $http, $q) {
|
||||
return [];
|
||||
}
|
||||
|
||||
var result = [this.getCurrent().owner];
|
||||
this.getCurrent().users = [this.getCurrent().owner];
|
||||
let self = this;
|
||||
angular.forEach(this.getCurrent().acl, function(value, key) {
|
||||
if (value.type === OC.Share.SHARE_TYPE_USER) {
|
||||
result.push(value.participant);
|
||||
self.getCurrent().users.push(value.participant);
|
||||
}
|
||||
});
|
||||
this.getCurrent().users = result;
|
||||
};
|
||||
|
||||
BoardService.prototype.getUsers = function () {
|
||||
|
||||
@@ -133,6 +133,45 @@ app.factory('CardService', function (ApiService, $http, $q) {
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
CardService.prototype.attachmentRemove = function (attachment) {
|
||||
var deferred = $q.defer();
|
||||
var self = this;
|
||||
$http.delete(this.baseUrl + '/' + this.getCurrent().id + '/attachment/' + attachment.id, {}).then(function (response) {
|
||||
if (response.data.deletedAt > 0) {
|
||||
let currentAttachment = self.getCurrent().attachments.find(function (obj) {
|
||||
if (obj.id === attachment.id) {
|
||||
obj.deletedAt = response.data.deletedAt;
|
||||
}
|
||||
});
|
||||
|
||||
} else {
|
||||
self.getCurrent().attachments = self.getCurrent().attachments.filter(function (obj) {
|
||||
return obj.id !== attachment.id;
|
||||
});
|
||||
}
|
||||
deferred.resolve(response.data);
|
||||
}, function (error) {
|
||||
deferred.reject('Error when removing the attachment');
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
CardService.prototype.attachmentRemoveUndo = function (attachment) {
|
||||
var deferred = $q.defer();
|
||||
var self = this;
|
||||
$http.get(this.baseUrl + '/' + this.getCurrent().id + '/attachment/' + attachment.id + '/restore', {}).then(function (response) {
|
||||
let currentAttachment = self.getCurrent().attachments.find(function (obj) {
|
||||
if (obj.id === attachment.id) {
|
||||
obj.deletedAt = response.data.deletedAt;
|
||||
}
|
||||
});
|
||||
deferred.resolve(response.data);
|
||||
}, function (error) {
|
||||
deferred.reject('Error when restoring the attachment');
|
||||
});
|
||||
return deferred.promise;
|
||||
};
|
||||
|
||||
var service = new CardService($http, 'cards', $q);
|
||||
return service;
|
||||
});
|
||||
100
js/service/FileService.js
Normal file
100
js/service/FileService.js
Normal file
@@ -0,0 +1,100 @@
|
||||
/*
|
||||
* @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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
import app from '../app/App.js';
|
||||
|
||||
/* global OC oc_requesttoken */
|
||||
export default class FileService {
|
||||
|
||||
constructor ($http, FileUploader, CardService) {
|
||||
this.uploader = new FileUploader();
|
||||
this.cardservice = CardService;
|
||||
this.uploader.onAfterAddingFile = this.onAfterAddingFile.bind(this);
|
||||
this.uploader.onSuccessItem = this.onSuccessItem.bind(this);
|
||||
}
|
||||
|
||||
|
||||
runUpload (fileItem, attachmentId) {
|
||||
fileItem.url = OC.generateUrl('/apps/deck/cards/' + fileItem.cardId + '/attachment');
|
||||
if (typeof attachmentId !== 'undefined') {
|
||||
fileItem.url = OC.generateUrl('/apps/deck/cards/' + fileItem.cardId + '/attachment/' + attachmentId);
|
||||
} else {
|
||||
fileItem.formData = [
|
||||
{
|
||||
requesttoken: oc_requesttoken,
|
||||
type: 'deck_file',
|
||||
}
|
||||
];
|
||||
}
|
||||
fileItem.headers = {requesttoken: oc_requesttoken};
|
||||
|
||||
this.uploader.uploadItem(fileItem);
|
||||
}
|
||||
|
||||
onAfterAddingFile (fileItem) {
|
||||
// Fetch card details before trying to upload so we can detect filename collisions properly
|
||||
let self = this;
|
||||
this.cardservice.fetchOne(fileItem.cardId).then(function (data) {
|
||||
let attachments = self.cardservice.get(fileItem.cardId).attachments;
|
||||
let existingFile = attachments.find((attachment) => {
|
||||
return attachment.data === fileItem.file.name;
|
||||
});
|
||||
if (typeof existingFile !== 'undefined') {
|
||||
OC.dialogs.confirm(
|
||||
`A file with the name ${fileItem.file.name} already exists. Do you want to overwrite it?`,
|
||||
'File already exists',
|
||||
function (result) {
|
||||
if (result) {
|
||||
self.runUpload(fileItem, existingFile.id);
|
||||
} else {
|
||||
let fileName = existingFile.extendedData.info.filename;
|
||||
let foundFilesMatching = attachments.filter((attachment) => {
|
||||
return attachment.extendedData.info.extension === existingFile.extendedData.info.extension
|
||||
&& attachment.extendedData.info.filename.startsWith(fileName);
|
||||
});
|
||||
let nextIndex = foundFilesMatching.length + 1;
|
||||
fileItem.file.name = fileName + ' (' + nextIndex + ').' + existingFile.extendedData.info.extension;
|
||||
self.runUpload(fileItem);
|
||||
}
|
||||
}
|
||||
);
|
||||
} else {
|
||||
self.runUpload(fileItem);
|
||||
}
|
||||
}, function (error) {
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
onSuccessItem (item, response) {
|
||||
let attachments = this.cardservice.get(item.cardId).attachments;
|
||||
let index = attachments.indexOf(attachments.find((attachment) => attachment.id === response.id));
|
||||
if (~index) {
|
||||
attachments = attachments.splice(index, 1);
|
||||
}
|
||||
this.cardservice.get(item.cardId).attachments.push(response);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
app.service('FileService', FileService);
|
||||
@@ -21,7 +21,8 @@
|
||||
*/
|
||||
import app from '../app/App.js';
|
||||
|
||||
app.factory('StackService', function (ApiService, $http, $q) {
|
||||
/* global app angular */
|
||||
app.factory('StackService', function (ApiService, CardService, $http, $q) {
|
||||
var StackService = function ($http, ep, $q) {
|
||||
ApiService.call(this, $http, ep, $q);
|
||||
};
|
||||
@@ -32,6 +33,12 @@ app.factory('StackService', function (ApiService, $http, $q) {
|
||||
$http.get(this.baseUrl + '/' + boardId).then(function (response) {
|
||||
self.clear();
|
||||
self.addAll(response.data);
|
||||
// When loading a stack add cards to the CardService so we can fetch
|
||||
// information from there. That way we don't need to refresh the whole
|
||||
// stack data during digest if some value changes
|
||||
angular.forEach(response.data, function (entity) {
|
||||
CardService.addAll(entity.cards);
|
||||
});
|
||||
deferred.resolve(self.data);
|
||||
}, function (error) {
|
||||
deferred.reject('Error while loading stacks');
|
||||
@@ -45,6 +52,9 @@ app.factory('StackService', function (ApiService, $http, $q) {
|
||||
$http.get(this.baseUrl + '/' + boardId + '/archived').then(function (response) {
|
||||
self.clear();
|
||||
self.addAll(response.data);
|
||||
angular.forEach(response.data, function (entity) {
|
||||
CardService.addAll(entity.cards);
|
||||
});
|
||||
deferred.resolve(self.data);
|
||||
}, function (error) {
|
||||
deferred.reject('Error while loading stacks');
|
||||
@@ -119,7 +129,7 @@ app.factory('StackService', function (ApiService, $http, $q) {
|
||||
}
|
||||
};
|
||||
|
||||
// FIXME: Should not sure popup but proper undo mechanism
|
||||
// FIXME: Should not show popup but proper undo mechanism
|
||||
StackService.prototype.delete = function (id) {
|
||||
var deferred = $q.defer();
|
||||
var self = this;
|
||||
|
||||
75
lib/Controller/AttachmentController.php
Normal file
75
lib/Controller/AttachmentController.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?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 AttachmentController extends Controller {
|
||||
|
||||
/** @var AttachmentService */
|
||||
private $attachmentService;
|
||||
|
||||
public function __construct($appName, IRequest $request, AttachmentService $attachmentService) {
|
||||
parent::__construct($appName, $request);
|
||||
$this->attachmentService = $attachmentService;
|
||||
}
|
||||
|
||||
public function getAll($cardId) {
|
||||
return $this->attachmentService->findAll($cardId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $cardId
|
||||
* @param $attachmentId
|
||||
* @NoCSRFRequired
|
||||
* @return \OCP\AppFramework\Http\Response
|
||||
* @throws \OCA\Deck\NotFoundException
|
||||
*/
|
||||
public function display($cardId, $attachmentId) {
|
||||
return $this->attachmentService->display($cardId, $attachmentId);
|
||||
}
|
||||
|
||||
public function create($cardId) {
|
||||
return $this->attachmentService->create(
|
||||
$cardId,
|
||||
$this->request->getParam('type'),
|
||||
$this->request->getParam('data')
|
||||
);
|
||||
}
|
||||
|
||||
public function update($cardId, $attachmentId) {
|
||||
return $this->attachmentService->update($cardId, $attachmentId, $this->request->getParam('data'));
|
||||
}
|
||||
|
||||
public function delete($cardId, $attachmentId) {
|
||||
return $this->attachmentService->delete($cardId, $attachmentId);
|
||||
}
|
||||
|
||||
public function restore($cardId, $attachmentId) {
|
||||
return $this->attachmentService->restore($cardId, $attachmentId);
|
||||
}
|
||||
}
|
||||
@@ -26,12 +26,13 @@ namespace OCA\Deck\Controller;
|
||||
use OCA\Deck\Db\Acl;
|
||||
use OCA\Deck\Service\BoardService;
|
||||
use OCA\Deck\Service\PermissionService;
|
||||
use OCP\AppFramework\ApiController;
|
||||
use OCP\IRequest;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\IUserManager;
|
||||
use OCP\IGroupManager;
|
||||
|
||||
class BoardController extends Controller {
|
||||
class BoardController extends ApiController {
|
||||
private $userId;
|
||||
private $boardService;
|
||||
private $userManager;
|
||||
|
||||
@@ -21,25 +21,28 @@
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* Created by PhpStorm.
|
||||
* User: jus
|
||||
* Date: 16.05.17
|
||||
* Time: 12:34
|
||||
*/
|
||||
|
||||
namespace OCA\Deck\Cron;
|
||||
|
||||
use OC\BackgroundJob\Job;
|
||||
use OCA\Deck\Db\AttachmentMapper;
|
||||
use OCA\Deck\Db\BoardMapper;
|
||||
use OCA\Deck\InvalidAttachmentType;
|
||||
use OCA\Deck\Service\AttachmentService;
|
||||
|
||||
class DeleteCron extends Job {
|
||||
|
||||
/** @var BoardMapper */
|
||||
private $boardMapper;
|
||||
/** @var AttachmentService */
|
||||
private $attachmentService;
|
||||
/** @var AttachmentMapper */
|
||||
private $attachmentMapper;
|
||||
|
||||
public function __construct(BoardMapper $boardMapper) {
|
||||
public function __construct(BoardMapper $boardMapper, AttachmentService $attachmentService, AttachmentMapper $attachmentMapper) {
|
||||
$this->boardMapper = $boardMapper;
|
||||
$this->attachmentService = $attachmentService;
|
||||
$this->attachmentMapper = $attachmentMapper;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,6 +54,18 @@ class DeleteCron extends Job {
|
||||
foreach ($boards as $board) {
|
||||
$this->boardMapper->delete($board);
|
||||
}
|
||||
|
||||
$attachments = $this->attachmentMapper->findToDelete();
|
||||
foreach ($attachments as $attachment) {
|
||||
try {
|
||||
$service = $this->attachmentService->getService($attachment->getType());
|
||||
$service->delete($attachment);
|
||||
} catch (InvalidAttachmentType $e) {
|
||||
// Just delete the attachment if no service is available
|
||||
}
|
||||
$this->attachmentMapper->delete($attachment);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
47
lib/Db/Attachment.php
Normal file
47
lib/Db/Attachment.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?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\Db;
|
||||
|
||||
class Attachment extends RelationalEntity {
|
||||
|
||||
protected $cardId;
|
||||
protected $type;
|
||||
protected $data;
|
||||
protected $lastModified = 0;
|
||||
protected $createdAt = 0;
|
||||
protected $createdBy;
|
||||
protected $deletedAt = 0;
|
||||
protected $extendedData = [];
|
||||
|
||||
public function __construct() {
|
||||
$this->addType('id', 'integer');
|
||||
$this->addType('cardId', 'integer');
|
||||
$this->addType('lastModified', 'integer');
|
||||
$this->addType('createdAt', 'integer');
|
||||
$this->addType('deletedAt', 'integer');
|
||||
$this->addResolvable('createdBy');
|
||||
$this->addRelation('extendedData');
|
||||
}
|
||||
|
||||
}
|
||||
147
lib/Db/AttachmentMapper.php
Normal file
147
lib/Db/AttachmentMapper.php
Normal file
@@ -0,0 +1,147 @@
|
||||
<?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\Db;
|
||||
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\AppFramework\Db\Entity;
|
||||
use OCP\AppFramework\Db\MultipleObjectsReturnedException;
|
||||
use OCP\DB\QueryBuilder\IQueryBuilder;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IUserManager;
|
||||
use PDO;
|
||||
|
||||
|
||||
class AttachmentMapper extends DeckMapper implements IPermissionMapper {
|
||||
|
||||
private $cardMapper;
|
||||
private $userManager;
|
||||
private $qb;
|
||||
|
||||
public function __construct(IDBConnection $db, CardMapper $cardMapper, IUserManager $userManager) {
|
||||
parent::__construct($db, 'deck_attachment', Attachment::class);
|
||||
$this->cardMapper = $cardMapper;
|
||||
$this->userManager = $userManager;
|
||||
$this->qb = $this->db->getQueryBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $id
|
||||
* @return Entity|Attachment
|
||||
* @throws \OCP\AppFramework\Db\DoesNotExistException
|
||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||
*/
|
||||
public function find($id) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from('deck_attachment')
|
||||
->where($qb->expr()->eq('id', $qb->createNamedParameter($id, IQueryBuilder::PARAM_INT)));
|
||||
|
||||
$cursor = $qb->execute();
|
||||
$row = $cursor->fetch(PDO::FETCH_ASSOC);
|
||||
$cursor->closeCursor();
|
||||
return $this->mapRowToEntity($row);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all attachments for a card
|
||||
*
|
||||
* @param $cardId
|
||||
* @return array
|
||||
*/
|
||||
public function findAll($cardId) {
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from('deck_attachment')
|
||||
->where($qb->expr()->eq('card_id', $qb->createNamedParameter($cardId, IQueryBuilder::PARAM_INT)))
|
||||
->andWhere($qb->expr()->eq('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
|
||||
|
||||
|
||||
$entities = [];
|
||||
$cursor = $qb->execute();
|
||||
while($row = $cursor->fetch()){
|
||||
$entities[] = $this->mapRowToEntity($row);
|
||||
}
|
||||
$cursor->closeCursor();
|
||||
return $entities;
|
||||
}
|
||||
|
||||
public function findToDelete($cardId = null, $withOffset = true) {
|
||||
// add buffer of 5 min
|
||||
$timeLimit = time() - (60 * 5);
|
||||
$qb = $this->db->getQueryBuilder();
|
||||
$qb->select('*')
|
||||
->from('deck_attachment')
|
||||
->where($qb->expr()->gt('deleted_at', $qb->createNamedParameter(0, IQueryBuilder::PARAM_INT)));
|
||||
if ($withOffset) {
|
||||
$qb
|
||||
->andWhere($qb->expr()->lt('deleted_at', $qb->createNamedParameter($timeLimit, IQueryBuilder::PARAM_INT)));
|
||||
}
|
||||
if ($cardId !== null) {
|
||||
$qb
|
||||
->andWhere($qb->expr()->eq('card_id', $qb->createNamedParameter($cardId, IQueryBuilder::PARAM_INT)));
|
||||
}
|
||||
|
||||
$entities = [];
|
||||
$cursor = $qb->execute();
|
||||
while($row = $cursor->fetch()){
|
||||
$entities[] = $this->mapRowToEntity($row);
|
||||
}
|
||||
$cursor->closeCursor();
|
||||
return $entities;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Check if $userId is owner of Entity with $id
|
||||
*
|
||||
* @param $userId string userId
|
||||
* @param $id int|string unique entity identifier
|
||||
* @return boolean
|
||||
*/
|
||||
public function isOwner($userId, $id) {
|
||||
try {
|
||||
$attachment = $this->find($id);
|
||||
return $this->cardMapper->isOwner($userId, $attachment->getCardId());
|
||||
} catch (DoesNotExistException $e) {
|
||||
} catch (MultipleObjectsReturnedException $e) {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Query boardId for Entity of given $id
|
||||
*
|
||||
* @param $id int|string unique entity identifier
|
||||
* @return int|null id of Board
|
||||
*/
|
||||
public function findBoardId($id) {
|
||||
try {
|
||||
$attachment = $this->find($id);
|
||||
} catch (\Exception $e) {
|
||||
return null;
|
||||
}
|
||||
return $this->cardMapper->findBoardId($attachment->getCardId());
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,8 @@ class Card extends RelationalEntity {
|
||||
protected $createdAt;
|
||||
protected $labels;
|
||||
protected $assignedUsers;
|
||||
protected $attachments;
|
||||
protected $attachmentCount;
|
||||
protected $owner;
|
||||
protected $order;
|
||||
protected $archived = false;
|
||||
@@ -58,6 +60,8 @@ class Card extends RelationalEntity {
|
||||
$this->addType('notified', 'boolean');
|
||||
$this->addRelation('labels');
|
||||
$this->addRelation('assignedUsers');
|
||||
$this->addRelation('attachments');
|
||||
$this->addRelation('attachmentCount');
|
||||
$this->addRelation('participants');
|
||||
$this->addResolvable('owner');
|
||||
}
|
||||
|
||||
@@ -25,6 +25,13 @@ namespace OCA\Deck\Db;
|
||||
|
||||
use OCP\AppFramework\Db\Mapper;
|
||||
|
||||
/**
|
||||
* Class DeckMapper
|
||||
*
|
||||
* @package OCA\Deck\Db
|
||||
*
|
||||
* TODO: Move to QBMapper once Nextcloud 14 is a minimum requirement
|
||||
*/
|
||||
abstract class DeckMapper extends Mapper {
|
||||
|
||||
/**
|
||||
|
||||
37
lib/InvalidAttachmentType.php
Normal file
37
lib/InvalidAttachmentType.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?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;
|
||||
|
||||
|
||||
class InvalidAttachmentType extends \Exception {
|
||||
|
||||
/**
|
||||
* InvalidAttachmentType constructor.
|
||||
*/
|
||||
public function __construct($type) {
|
||||
|
||||
parent::__construct('No matching IAttachmentService implementation found for type ' . $type);
|
||||
|
||||
}
|
||||
}
|
||||
264
lib/Service/AttachmentService.php
Normal file
264
lib/Service/AttachmentService.php
Normal file
@@ -0,0 +1,264 @@
|
||||
<?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\AppInfo\Application;
|
||||
use OCA\Deck\Db\Acl;
|
||||
use OCA\Deck\Db\Attachment;
|
||||
use OCA\Deck\Db\AttachmentMapper;
|
||||
use OCA\Deck\Db\CardMapper;
|
||||
use OCA\Deck\InvalidAttachmentType;
|
||||
use OCA\Deck\NoPermissionException;
|
||||
use OCA\Deck\NotFoundException;
|
||||
use OCP\AppFramework\Http\Response;
|
||||
use OCP\ICache;
|
||||
use OCP\ICacheFactory;
|
||||
|
||||
class AttachmentService {
|
||||
|
||||
private $attachmentMapper;
|
||||
private $cardMapper;
|
||||
private $permissionService;
|
||||
private $userId;
|
||||
|
||||
/** @var IAttachmentService[] */
|
||||
private $services = [];
|
||||
private $application;
|
||||
/** @var ICache */
|
||||
private $cache;
|
||||
|
||||
/**
|
||||
* AttachmentService constructor.
|
||||
*
|
||||
* @param AttachmentMapper $attachmentMapper
|
||||
* @param CardMapper $cardMapper
|
||||
* @param PermissionService $permissionService
|
||||
* @param Application $application
|
||||
* @param ICacheFactory $cacheFactory
|
||||
* @param $userId
|
||||
* @throws \OCP\AppFramework\QueryException
|
||||
*/
|
||||
public function __construct(AttachmentMapper $attachmentMapper, CardMapper $cardMapper, PermissionService $permissionService, Application $application, ICacheFactory $cacheFactory, $userId) {
|
||||
$this->attachmentMapper = $attachmentMapper;
|
||||
$this->cardMapper = $cardMapper;
|
||||
$this->permissionService = $permissionService;
|
||||
$this->userId = $userId;
|
||||
$this->application = $application;
|
||||
$this->cache = $cacheFactory->createDistributed('deck-card-attachments-');
|
||||
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @param string $class
|
||||
* @throws \OCP\AppFramework\QueryException
|
||||
*/
|
||||
public function registerAttachmentService($type, $class) {
|
||||
$this->services[$type] = $this->application->getContainer()->query($class);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $type
|
||||
* @return IAttachmentService
|
||||
* @throws InvalidAttachmentType
|
||||
*/
|
||||
public function getService($type) {
|
||||
if (isset($this->services[$type])) {
|
||||
return $this->services[$type];
|
||||
}
|
||||
throw new InvalidAttachmentType($type);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $cardId
|
||||
* @return array
|
||||
* @throws \OCA\Deck\NoPermissionException
|
||||
*/
|
||||
public function findAll($cardId, $withDeleted = false) {
|
||||
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
|
||||
|
||||
$attachments = $this->attachmentMapper->findAll($cardId);
|
||||
if ($withDeleted) {
|
||||
$attachments = array_merge($attachments, $this->attachmentMapper->findToDelete($cardId, false));
|
||||
}
|
||||
foreach ($attachments as &$attachment) {
|
||||
try {
|
||||
$service = $this->getService($attachment->getType());
|
||||
$service->extendData($attachment);
|
||||
} catch (InvalidAttachmentType $e) {
|
||||
// Ingore invalid attachment types when extending the data
|
||||
}
|
||||
}
|
||||
return $attachments;
|
||||
}
|
||||
|
||||
public function count($cardId) {
|
||||
$count = $this->cache->get('card-' . $cardId);
|
||||
if (!$count) {
|
||||
$count = count($this->attachmentMapper->findAll($cardId));
|
||||
$this->cache->set('card-' . $cardId, $count);
|
||||
}
|
||||
return $count;
|
||||
}
|
||||
|
||||
public function create($cardId, $type, $data) {
|
||||
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_EDIT);
|
||||
|
||||
$this->cache->clear('card-' . $cardId);
|
||||
$attachment = new Attachment();
|
||||
$attachment->setCardId($cardId);
|
||||
$attachment->setType($type);
|
||||
$attachment->setData($data);
|
||||
$attachment->setCreatedBy($this->userId);
|
||||
$attachment->setLastModified(time());
|
||||
$attachment->setCreatedAt(time());
|
||||
|
||||
try {
|
||||
$service = $this->getService($attachment->getType());
|
||||
$service->create($attachment);
|
||||
} catch (InvalidAttachmentType $e) {
|
||||
// just store the data
|
||||
}
|
||||
$attachment = $this->attachmentMapper->insert($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
|
||||
}
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Display the attachment
|
||||
*
|
||||
* @param $cardId
|
||||
* @param $attachmentId
|
||||
* @return Response
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function display($cardId, $attachmentId) {
|
||||
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
|
||||
|
||||
$attachment = $this->attachmentMapper->find($attachmentId);
|
||||
|
||||
try {
|
||||
$service = $this->getService($attachment->getType());
|
||||
return $service->display($attachment);
|
||||
} catch (InvalidAttachmentType $e) {
|
||||
throw new NotFoundException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an attachment with custom data
|
||||
*
|
||||
* @param $cardId
|
||||
* @param $attachmentId
|
||||
* @param $request
|
||||
* @return mixed
|
||||
* @throws \OCA\Deck\NoPermissionException
|
||||
* @throws \OCP\AppFramework\Db\DoesNotExistException
|
||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||
*/
|
||||
public function update($cardId, $attachmentId, $data) {
|
||||
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_EDIT);
|
||||
|
||||
$this->cache->clear('card-' . $cardId);
|
||||
|
||||
$attachment = $this->attachmentMapper->find($attachmentId);
|
||||
$attachment->setData($data);
|
||||
try {
|
||||
$service = $this->getService($attachment->getType());
|
||||
$service->update($attachment);
|
||||
} catch (InvalidAttachmentType $e) {
|
||||
// just update without further action
|
||||
}
|
||||
$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
|
||||
}
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Either mark an attachment as deleted for later removal or just remove it depending
|
||||
* on the IAttachmentService implementation
|
||||
*
|
||||
* @param $cardId
|
||||
* @param $attachmentId
|
||||
* @return \OCP\AppFramework\Db\Entity
|
||||
* @throws \OCA\Deck\NoPermissionException
|
||||
* @throws \OCP\AppFramework\Db\DoesNotExistException
|
||||
* @throws \OCP\AppFramework\Db\MultipleObjectsReturnedException
|
||||
*/
|
||||
public function delete($cardId, $attachmentId) {
|
||||
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_EDIT);
|
||||
|
||||
$this->cache->clear('card-' . $cardId);
|
||||
|
||||
$attachment = $this->attachmentMapper->find($attachmentId);
|
||||
try {
|
||||
$service = $this->getService($attachment->getType());
|
||||
if ($service->allowUndo()) {
|
||||
$service->markAsDeleted($attachment);
|
||||
return $this->attachmentMapper->update($attachment);
|
||||
}
|
||||
$service->delete($attachment);
|
||||
} catch (InvalidAttachmentType $e) {
|
||||
// just delete without further action
|
||||
}
|
||||
return $this->attachmentMapper->delete($attachment);
|
||||
}
|
||||
|
||||
public function restore($cardId, $attachmentId) {
|
||||
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_EDIT);
|
||||
|
||||
$this->cache->clear('card-' . $cardId);
|
||||
|
||||
$attachment = $this->attachmentMapper->find($attachmentId);
|
||||
try {
|
||||
$service = $this->getService($attachment->getType());
|
||||
if ($service->allowUndo()) {
|
||||
$attachment->setDeletedAt(0);
|
||||
return $this->attachmentMapper->update($attachment);
|
||||
}
|
||||
} catch (InvalidAttachmentType $e) {
|
||||
}
|
||||
throw new NoPermissionException('Restore is not allowed.');
|
||||
}
|
||||
}
|
||||
@@ -40,20 +40,24 @@ class CardService {
|
||||
private $permissionService;
|
||||
private $boardService;
|
||||
private $assignedUsersMapper;
|
||||
private $attachmentService;
|
||||
|
||||
public function __construct(CardMapper $cardMapper, StackMapper $stackMapper, PermissionService $permissionService, BoardService $boardService, AssignedUsersMapper $assignedUsersMapper) {
|
||||
public function __construct(CardMapper $cardMapper, StackMapper $stackMapper, PermissionService $permissionService, BoardService $boardService, AssignedUsersMapper $assignedUsersMapper, AttachmentService $attachmentService) {
|
||||
$this->cardMapper = $cardMapper;
|
||||
$this->stackMapper = $stackMapper;
|
||||
$this->permissionService = $permissionService;
|
||||
$this->boardService = $boardService;
|
||||
$this->assignedUsersMapper = $assignedUsersMapper;
|
||||
$this->attachmentService = $attachmentService;
|
||||
}
|
||||
|
||||
public function find($cardId) {
|
||||
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
|
||||
$card = $this->cardMapper->find($cardId);
|
||||
$assignedUsers = $this->assignedUsersMapper->find($card->getId());
|
||||
$attachments = $this->attachmentService->findAll($cardId, true);
|
||||
$card->setAssignedUsers($assignedUsers);
|
||||
$card->setAttachments($attachments);
|
||||
return $card;
|
||||
}
|
||||
|
||||
|
||||
196
lib/Service/FileService.php
Normal file
196
lib/Service/FileService.php
Normal file
@@ -0,0 +1,196 @@
|
||||
<?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 OC\Security\CSP\ContentSecurityPolicyManager;
|
||||
use OCA\Deck\Db\Attachment;
|
||||
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
||||
use OCP\AppFramework\Http\EmptyContentSecurityPolicy;
|
||||
use OCP\AppFramework\Http\FileDisplayResponse;
|
||||
use OCP\Files\IAppData;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\Files\NotPermittedException;
|
||||
use OCP\Files\SimpleFS\ISimpleFile;
|
||||
use OCP\Files\SimpleFS\ISimpleFolder;
|
||||
use OCP\IL10N;
|
||||
use OCP\ILogger;
|
||||
use OCP\IRequest;
|
||||
|
||||
class FileService implements IAttachmentService {
|
||||
|
||||
private $l10n;
|
||||
private $appData;
|
||||
private $request;
|
||||
private $logger;
|
||||
|
||||
public function __construct(
|
||||
IL10N $l10n,
|
||||
IAppData $appData,
|
||||
IRequest $request,
|
||||
ILogger $logger
|
||||
) {
|
||||
$this->l10n = $l10n;
|
||||
$this->appData = $appData;
|
||||
$this->request = $request;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Attachment $attachment
|
||||
* @return ISimpleFile
|
||||
* @throws NotFoundException
|
||||
* @throws NotPermittedException
|
||||
*/
|
||||
private function getFileForAttachment(Attachment $attachment) {
|
||||
return $this->getFolder($attachment)
|
||||
->getFile($attachment->getData());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Attachment $attachment
|
||||
* @return ISimpleFolder
|
||||
* @throws NotPermittedException
|
||||
*/
|
||||
private function getFolder(Attachment $attachment) {
|
||||
$folderName = 'file-card-' . (int)$attachment->getCardId();
|
||||
try {
|
||||
$folder = $this->appData->getFolder($folderName);
|
||||
} catch (NotFoundException $e) {
|
||||
$folder = $this->appData->newFolder($folderName);
|
||||
}
|
||||
return $folder;
|
||||
}
|
||||
|
||||
public function extendData(Attachment $attachment) {
|
||||
try {
|
||||
$file = $this->getFileForAttachment($attachment);
|
||||
} catch (NotFoundException $e) {
|
||||
$this->logger->info('Extending data for file attachment failed');
|
||||
return $attachment;
|
||||
} catch (NotPermittedException $e) {
|
||||
$this->logger->info('Extending data for file attachment failed');
|
||||
return $attachment;
|
||||
}
|
||||
$attachment->setExtendedData([
|
||||
'filesize' => $file->getSize(),
|
||||
'mimetype' => $file->getMimeType(),
|
||||
'info' => pathinfo($file->getName())
|
||||
]);
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
if (!empty($file) && array_key_exists('error', $file) && $file['error'] !== UPLOAD_ERR_OK) {
|
||||
$error = $phpFileUploadErrors[$file['error']];
|
||||
}
|
||||
if ($error !== null) {
|
||||
throw new \Exception($error);
|
||||
}
|
||||
return $file;
|
||||
}
|
||||
|
||||
public function create(Attachment $attachment) {
|
||||
$file = $this->getUploadedFile();
|
||||
$folder = $this->getFolder($attachment);
|
||||
$fileName = $file['name'];
|
||||
if ($folder->fileExists($fileName)) {
|
||||
throw new \Exception('File already exists.');
|
||||
}
|
||||
$target = $folder->newFile($fileName);
|
||||
$target->putContent(file_get_contents($file['tmp_name'], 'r'));
|
||||
|
||||
$attachment->setData($fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method requires to be used with POST so we can properly get the form data
|
||||
*/
|
||||
public function update(Attachment $attachment) {
|
||||
$file = $this->getUploadedFile();
|
||||
$fileName = $file['name'];
|
||||
$attachment->setData($fileName);
|
||||
|
||||
$target = $this->getFileForAttachment($attachment);
|
||||
$target->putContent(file_get_contents($file['tmp_name'], 'r'));
|
||||
|
||||
$attachment->setLastModified(time());
|
||||
}
|
||||
|
||||
public function delete(Attachment $attachment) {
|
||||
try {
|
||||
$file = $this->getFileForAttachment($attachment);
|
||||
$file->delete();
|
||||
} catch (NotFoundException $e) {
|
||||
}
|
||||
}
|
||||
|
||||
public function display(Attachment $attachment) {
|
||||
$file = $this->getFileForAttachment($attachment);
|
||||
$response = new FileDisplayResponse($file);
|
||||
if ($file->getMimeType() === 'application/pdf') {
|
||||
// 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:');
|
||||
$response->setContentSecurityPolicy($policy);
|
||||
}
|
||||
$response->addHeader('Content-Type', $file->getMimeType());
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should undo be allowed and the delete action be done by a background job
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function allowUndo() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark an attachment as deleted
|
||||
*
|
||||
* @param Attachment $attachment
|
||||
*/
|
||||
public function markAsDeleted(Attachment $attachment) {
|
||||
$attachment->setDeletedAt(time());
|
||||
}
|
||||
}
|
||||
99
lib/Service/IAttachmentService.php
Normal file
99
lib/Service/IAttachmentService.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?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 OCP\AppFramework\Http\Response;
|
||||
|
||||
/**
|
||||
* Interface IAttachmentService
|
||||
*
|
||||
* Implement this interface to extend the default attachment behaviour
|
||||
* This interface allows to extend/reduce the data stored with an attachment,
|
||||
* as well as rendering a custom output per attachment type
|
||||
*
|
||||
*/
|
||||
interface IAttachmentService {
|
||||
|
||||
/**
|
||||
* Add extended data to the returned data of an attachment
|
||||
*
|
||||
* @param Attachment $attachment
|
||||
* @return mixed
|
||||
*/
|
||||
public function extendData(Attachment $attachment);
|
||||
|
||||
/**
|
||||
* Display the attachment
|
||||
*
|
||||
* TODO: Move to IAttachmentDisplayService for better separation
|
||||
*
|
||||
* @param Attachment $attachment
|
||||
* @return Response
|
||||
*/
|
||||
public function display(Attachment $attachment);
|
||||
|
||||
/**
|
||||
* Create a new attachment
|
||||
*
|
||||
* This method will be called before inserting the attachment entry in the database
|
||||
*
|
||||
* @param Attachment $attachment
|
||||
*/
|
||||
public function create(Attachment $attachment);
|
||||
|
||||
/**
|
||||
* Update an attachment with custom data
|
||||
*
|
||||
* This method will be called before updating the attachment entry in the database
|
||||
*
|
||||
* @param Attachment $attachment
|
||||
*/
|
||||
public function update(Attachment $attachment);
|
||||
|
||||
/**
|
||||
* Delete an attachment
|
||||
*
|
||||
* This method will be called before removing the attachment entry from the database
|
||||
*
|
||||
* @param Attachment $attachment
|
||||
*/
|
||||
public function delete(Attachment $attachment);
|
||||
|
||||
/**
|
||||
* Should undo be allowed and the delete action be done by a background job
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function allowUndo();
|
||||
|
||||
/**
|
||||
* Mark an attachment as deleted
|
||||
*
|
||||
* @param Attachment $attachment
|
||||
*/
|
||||
public function markAsDeleted(Attachment $attachment);
|
||||
|
||||
}
|
||||
@@ -30,6 +30,8 @@ use OCA\Deck\Db\AssignedUsersMapper;
|
||||
use OCA\Deck\Db\Stack;
|
||||
use OCA\Deck\Db\StackMapper;
|
||||
use OCA\Deck\StatusException;
|
||||
use OCP\ICache;
|
||||
use OCP\ICacheFactory;
|
||||
|
||||
|
||||
class StackService {
|
||||
@@ -40,6 +42,7 @@ class StackService {
|
||||
private $permissionService;
|
||||
private $boardService;
|
||||
private $assignedUsersMapper;
|
||||
private $attachmentService;
|
||||
|
||||
public function __construct(
|
||||
StackMapper $stackMapper,
|
||||
@@ -47,7 +50,8 @@ class StackService {
|
||||
LabelMapper $labelMapper,
|
||||
PermissionService $permissionService,
|
||||
BoardService $boardService,
|
||||
AssignedUsersMapper $assignedUsersMapper
|
||||
AssignedUsersMapper $assignedUsersMapper,
|
||||
AttachmentService $attachmentService
|
||||
) {
|
||||
$this->stackMapper = $stackMapper;
|
||||
$this->cardMapper = $cardMapper;
|
||||
@@ -55,6 +59,7 @@ class StackService {
|
||||
$this->permissionService = $permissionService;
|
||||
$this->boardService = $boardService;
|
||||
$this->assignedUsersMapper = $assignedUsersMapper;
|
||||
$this->attachmentService = $attachmentService;
|
||||
}
|
||||
|
||||
public function findAll($boardId) {
|
||||
@@ -69,6 +74,7 @@ class StackService {
|
||||
if (array_key_exists($card->id, $labels)) {
|
||||
$cards[$cardIndex]->setLabels($labels[$card->id]);
|
||||
}
|
||||
$card->setAttachmentCount($this->attachmentService->count($card->getId()));
|
||||
}
|
||||
$stacks[$stackIndex]->setCards($cards);
|
||||
}
|
||||
|
||||
@@ -58,5 +58,8 @@ Util::addScript('deck', 'build/deck');
|
||||
<script type="text/ng-template" id="/card.sidebarView.html">
|
||||
<?php print_unescaped($this->inc('part.card')); ?>
|
||||
</script>
|
||||
<script type="text/ng-template" id="/card.attachments.html">
|
||||
<?php print_unescaped($this->inc('part.card.attachments')); ?>
|
||||
</script>
|
||||
|
||||
</div>
|
||||
|
||||
@@ -61,12 +61,16 @@
|
||||
data-as-sortable-item
|
||||
ng-click="$event.stopPropagation()"
|
||||
ui-sref="board.card({boardId: id, cardId: c.id})"
|
||||
ng-class="{'archived': c.archived, 'has-labels': c.labels.length>0, 'current': c.id == params.cardId }">
|
||||
ng-class="{'archived': cardservice.get(c.id).archived, 'has-labels': cardservice.get(c.id).labels.length>0, 'current': cardservice.get(c.id).id == params.cardId }"
|
||||
nv-file-drop="" uploader="uploader" options="{cardId: c.id}">
|
||||
<div class="drop-indicator" uploader="uploader" nv-file-over>
|
||||
<p><?php p($l->t('Drop your files here to upload it to the card')); ?></p>
|
||||
</div>
|
||||
<div data-as-sortable-item-handle>
|
||||
<div class="card-upper">
|
||||
<h4>{{ c.title }}</h4>
|
||||
<h4>{{ cardservice.get(c.id).title }}</h4>
|
||||
<ul class="labels">
|
||||
<li ng-repeat="label in c.labels"
|
||||
<li ng-repeat="label in cardservice.get(c.id).labels"
|
||||
ng-style="labelStyle(label.color)" title="{{ label.title }}">
|
||||
<span>{{ label.title }}</span>
|
||||
</li>
|
||||
@@ -75,17 +79,21 @@
|
||||
</div>
|
||||
|
||||
<div class="card-controls">
|
||||
<i class="icon icon-filetype-text" ng-if="c.description" title="{{ c.description }}"></i>
|
||||
<span class="due" ng-if="c.duedate" ng-class="{'overdue': c.overdue == 3, 'now': c.overdue == 2, 'next': c.overdue == 1 }" title="{{ c.duedate }}">
|
||||
<i class="icon icon-filetype-text" ng-if="cardservice.get(c.id).description" title="{{ cardservice.get(c.id).description }}"></i>
|
||||
<span class="due" ng-if="cardservice.get(c.id).duedate" ng-class="{'overdue': cardservice.get(c.id).overdue == 3, 'now': cardservice.get(c.id).overdue == 2, 'next': cardservice.get(c.id).overdue == 1 }" title="{{ cardservice.get(c.id).duedate }}">
|
||||
<i class="icon icon-badge"></i>
|
||||
<span data-timestamp="{{ c.duedate | dateToTimestamp }}" class="live-relative-timestamp">{{ c.duedate | relativeDateFilterString }}</span>
|
||||
<span data-timestamp="{{ cardservice.get(c.id).duedate | dateToTimestamp }}" class="live-relative-timestamp">{{ cardservice.get(c.id).duedate | relativeDateFilterString }}</span>
|
||||
</span>
|
||||
<div class="card-tasks" ng-if="getCheckboxes(c.description)[1] > 0">
|
||||
<div class="card-tasks" ng-if="getCheckboxes(cardservice.get(c.id).description)[1] > 0">
|
||||
<i class="icon icon-checkmark"></i>
|
||||
<span>{{ getCheckboxes(c.description)[0] }}/{{ getCheckboxes(c.description)[1] }}</span>
|
||||
<span>{{ getCheckboxes(cardservice.get(c.id).description)[0] }}/{{ getCheckboxes(cardservice.get(c.id).description)[1] }}</span>
|
||||
</div>
|
||||
<div class="card-files" ng-if="attachmentCount(cardservice.get(c.id)) > 0">
|
||||
<i class="icon icon-files-dark"></i>
|
||||
<span>{{ attachmentCount(cardservice.get(c.id)) }}</span>
|
||||
</div>
|
||||
<div class="card-assigned-users">
|
||||
<div class="assigned-user" ng-repeat="user in c.assignedUsers | limitTo: 3">
|
||||
<div class="assigned-user" ng-repeat="user in cardservice.get(c.id).assignedUsers | limitTo: 3">
|
||||
<avatar data-user="{{ user.participant.uid }}" data-displayname="{{ user.participant.displayname }}" data-tooltip></avatar>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
<td>
|
||||
<div id="assigned-users">
|
||||
<avatar data-contactsmenu data-tooltip data-user="{{ b.owner.uid }}" data-displayname="{{ b.owner.displayname }}"></avatar>
|
||||
<avatar data-contactsmenu data-tooltip data-user="{{ acl.participant.uid }}" data-displayname="{{ acl.participant.displayname }}" ng-repeat="acl in b.acl | limitTo: 7 track by acl.i"></avatar>
|
||||
<avatar data-contactsmenu data-tooltip data-user="{{ acl.participant.uid }}" data-displayname="{{ acl.participant.displayname }}" ng-repeat="acl in b.acl | limitTo: 7 track by acl.id"></avatar>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
|
||||
42
templates/part.card.attachments.php
Normal file
42
templates/part.card.attachments.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<div ng-class="{'attachment-list-wrapper': $ctrl.isFileSelector}">
|
||||
<div class="attachment-list" ng-class="{selector: $ctrl.isFileSelector}">
|
||||
<h3 class="attachment-selector" ng-if="$ctrl.isFileSelector"><?php p($l->t('Select an attachment')); ?> <a class="icon-close" ng-click="$ctrl.abort()"></a></h3>
|
||||
<ul>
|
||||
<li class="attachment"
|
||||
ng-repeat="attachment in $ctrl.cardservice.getCurrent().attachments | filter: {type: 'deck_file'} | orderBy: ['deletedAt', '-lastModified']"
|
||||
ng-class="{deleted: attachment.deletedAt > 0, selector: $ctrl.isFileSelector}"
|
||||
ng-if="!$ctrl.isFileSelector || attachment.deletedAt == 0">
|
||||
<a class="fileicon" ng-style="$ctrl.mimetypeForAttachment(attachment)" ng-href="{{ attachmentUrl(attachment) }}"></a>
|
||||
<div class="details">
|
||||
<a ng-href="{{ $ctrl.attachmentUrl(attachment) }}" target="_blank">
|
||||
<div class="filename">
|
||||
<span class="basename">{{ attachment.extendedData.info.filename}}</span>
|
||||
<span class="extension">.{{ attachment.extendedData.info.extension}}</span>
|
||||
</div>
|
||||
<span class="filesize">{{ attachment.extendedData.filesize | bytes }}</span>
|
||||
<span class="filedate">{{ attachment.lastModified|relativeDateFilter }}</span>
|
||||
<span class="filedate"><?php p($l->t('by')); ?> {{ attachment.createdBy }}</span>
|
||||
</a>
|
||||
</div>
|
||||
<button class="icon icon-history button-inline" ng-click="$ctrl.cardservice.attachmentRemoveUndo(attachment)" ng-if="!$ctrl.isFileSelector && attachment.deletedAt > 0" title="<?php p($l->t('Undo file deletion - Otherwise the file will be deleted during the next cronjob run.')); ?>">
|
||||
<span class="hidden-visually"><?php p($l->t('Undo file deletion')); ?></span>
|
||||
</button>
|
||||
<button class="icon icon-confirm button-inline" ng-click="$ctrl.select(attachment)" ng-if="$ctrl.isFileSelector">
|
||||
<span class="hidden-visually"><?php p($l->t('Insert the file into the description')); ?></span>
|
||||
</button>
|
||||
<div class="app-popover-menu-utils" ng-if="!$ctrl.isFileSelector && attachment.deletedAt == 0">
|
||||
<button class="button-inline icon icon-more"></button>
|
||||
<div class="popovermenu hidden">
|
||||
<ul>
|
||||
<li>
|
||||
<a class="menuitem action action-delete"
|
||||
ng-click="$ctrl.cardservice.attachmentRemove(attachment); $event.stopPropagation();"><span
|
||||
class="icon icon-delete"></span><span><?php p($l->t('Delete')); ?></span></a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -1,4 +1,8 @@
|
||||
<div id="board-status" ng-if="statusservice.active">
|
||||
<div nv-file-drop="" uploader="uploader" class="drop-zone" options="{cardId: cardservice.getCurrent().id}">
|
||||
<div class="drop-indicator" nv-file-over uploader="uploader">
|
||||
<p><?php p($l->t('Drop your files here to upload it to the card')); ?></p>
|
||||
</div>
|
||||
<div id="board-status" ng-if="statusservice.active">
|
||||
<div id="emptycontent">
|
||||
<div class="icon-{{ statusservice.icon }}" title="<?php p($l->t('Status')); ?>"><span class="hidden-visually"><?php p($l->t('Status')); ?></span></div>
|
||||
<h2>{{ statusservice.title }}</h2>
|
||||
@@ -83,20 +87,36 @@
|
||||
<button class="icon icon-delete button-inline" title="<?php p($l->t('Remove due date')); ?>" ng-if="cardservice.getCurrent().duedate" ng-click="resetDuedate()"><span class="hidden-visually"><?php p($l->t('Remove due date')); ?></span></button>
|
||||
</div>
|
||||
|
||||
<div class="section-header card-description">
|
||||
<h4>
|
||||
<div>
|
||||
<?php p($l->t('Description')); ?>
|
||||
<a href="https://github.com/nextcloud/deck/wiki/Markdown-Help" target="_blank" class="icon icon-help" data-toggle="tooltip" data-placement="right" title="<?php p($l->t('Formatting help')); ?>"><span class="hidden-visually"><?php p($l->t('Formatting help')); ?></span></a>
|
||||
</div>
|
||||
</h4>
|
||||
<span class="save-indicator saved"><?php p($l->t('Saved')); ?></span>
|
||||
<span class="save-indicator unsaved"><?php p($l->t('Unsaved changes')); ?></span>
|
||||
|
||||
<div class="section-header-tabbed">
|
||||
<ul class="tabHeaders ng-scope">
|
||||
<li class="tabHeader selected" ng-class="{'selected': (params.tab==0 || !params.tab)}" ui-sref="{tab: 0}"><a><?php p($l->t('Description')); ?></a></li>
|
||||
<li class="tabHeader" ng-class="{'selected': (params.tab==1)}" ui-sref="{tab: 1}"><a><?php p($l->t('Attachments')); ?></a></li>
|
||||
</ul>
|
||||
<div class="tabDetails">
|
||||
<span class="save-indicator saved"><?php p($l->t('Saved')); ?></span>
|
||||
<span class="save-indicator unsaved"><?php p($l->t('Unsaved changes')); ?></span>
|
||||
<a ng-if="params.tab === 0" href="https://github.com/nextcloud/deck/wiki/Markdown-Help" target="_blank" class="icon icon-help" data-toggle="tooltip" data-placement="left" title="<?php p($l->t('Formatting help')); ?>"><span class="hidden-visually"><?php p($l->t('Formatting help')); ?></span></a>
|
||||
<label ng-if="params.tab === 1" for="attachment-upload" class="button icon-upload" ng-class="{'icon-loading-small': uploader.isUploading}" data-toggle="tooltip" data-placement="left" title="<?php p($l->t('Upload attachment')); ?>"></label>
|
||||
<input id="attachment-upload" type="file" nv-file-select="" uploader="uploader" class="hidden" options="{cardId: cardservice.getCurrent().id}"/>
|
||||
<input ng-if="status.cardEditDescription" type="button" ng-mousedown="status.continueEdit = true; status.selectAttachment = true;" class="icon-files-dark" data-toggle="tooltip" data-placement="left" title="<?php p($l->t('Insert attachment')); ?>"/>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="section-content card-description">
|
||||
<div class="section-content card-attachments">
|
||||
<attachment-list-component ng-if="params.tab === 1 && cardservice.getCurrent() && isArray(cardservice.getCurrent().attachments)" attachments="cardservice.getCurrent().attachments"></attachment-list-component>
|
||||
</div>
|
||||
|
||||
<div class="section-content card-description" ng-if="params.tab === 0">
|
||||
<attachment-list-component
|
||||
ng-if="status.selectAttachment"
|
||||
attachments="cardservice.getCurrent().attachments"
|
||||
is-file-selector="true"
|
||||
on-select="addAttachmentToDescription(attachment)" on-abort="abortAttachmentSelection()">
|
||||
</attachment-list-component>
|
||||
<textarea elastic ng-if="status.cardEditDescription"
|
||||
placeholder="<?php p($l->t('Add a card description…')); ?>"
|
||||
ng-blur="cardUpdate(status.edit)"
|
||||
ng-blur="!status.continueEdit && cardUpdate(status.edit)"
|
||||
ng-model="status.edit.description"
|
||||
ng-change="cardEditDescriptionChanged(); updateMarkdown(status.edit.description)"
|
||||
autofocus-on-insert> </textarea>
|
||||
|
||||
@@ -23,20 +23,31 @@
|
||||
|
||||
namespace OCA\Deck\Cron;
|
||||
|
||||
use OCA\Deck\Db\Attachment;
|
||||
use OCA\Deck\Db\AttachmentMapper;
|
||||
use OCA\Deck\Db\Board;
|
||||
use OCA\Deck\Db\BoardMapper;
|
||||
use OCA\Deck\InvalidAttachmentType;
|
||||
use OCA\Deck\Service\AttachmentService;
|
||||
use OCA\Deck\Service\IAttachmentService;
|
||||
|
||||
class DeleteCronTest extends \Test\TestCase {
|
||||
|
||||
/** @var BoardMapper|\PHPUnit_Framework_MockObject_MockObject */
|
||||
/** @var BoardMapper|\PHPUnit\Framework\MockObject\MockObject */
|
||||
protected $boardMapper;
|
||||
/** @var AttachmentService|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $attachmentService;
|
||||
/** @var AttachmentMapper|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $attachmentMapper;
|
||||
/** @var DeleteCron */
|
||||
protected $deleteCron;
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
$this->boardMapper = $this->createMock(BoardMapper::class);
|
||||
$this->deleteCron = new DeleteCron($this->boardMapper);
|
||||
$this->attachmentService = $this->createMock(AttachmentService::class);
|
||||
$this->attachmentMapper = $this->createMock(AttachmentMapper::class);
|
||||
$this->deleteCron = new DeleteCron($this->boardMapper, $this->attachmentService, $this->attachmentMapper);
|
||||
}
|
||||
|
||||
protected function getBoard($id) {
|
||||
@@ -67,6 +78,44 @@ class DeleteCronTest extends \Test\TestCase {
|
||||
$this->boardMapper->expects($this->at(4))
|
||||
->method('delete')
|
||||
->with($boards[3]);
|
||||
|
||||
$attachment = new Attachment();
|
||||
$attachment->setType('deck_file');
|
||||
$this->attachmentMapper->expects($this->once())
|
||||
->method('findToDelete')
|
||||
->willReturn([
|
||||
$attachment
|
||||
]);
|
||||
$service = $this->createMock(IAttachmentService::class);
|
||||
$service->expects($this->once())
|
||||
->method('delete')
|
||||
->with($attachment);
|
||||
$this->attachmentService->expects($this->once())
|
||||
->method('getService')
|
||||
->willReturn($service);
|
||||
$this->attachmentMapper->expects($this->once())
|
||||
->method('delete')
|
||||
->with($attachment);
|
||||
$this->invokePrivate($this->deleteCron, 'run', [null]);
|
||||
}
|
||||
|
||||
public function testDeleteCronInvalidAttachment() {
|
||||
$boards = [];
|
||||
$this->boardMapper->expects($this->once())
|
||||
->method('findToDelete')
|
||||
->willReturn($boards);
|
||||
|
||||
$attachment = new Attachment();
|
||||
$attachment->setType('deck_file_invalid');
|
||||
$this->attachmentMapper->expects($this->once())
|
||||
->method('findToDelete')
|
||||
->willReturn([$attachment]);
|
||||
$this->attachmentService->expects($this->once())
|
||||
->method('getService')
|
||||
->will($this->throwException(new InvalidAttachmentType('deck_file_invalid')));
|
||||
$this->attachmentMapper->expects($this->once())
|
||||
->method('delete')
|
||||
->with($attachment);
|
||||
$this->invokePrivate($this->deleteCron, 'run', [null]);
|
||||
}
|
||||
}
|
||||
@@ -31,9 +31,9 @@ use OCA\Deck\Notification\NotificationHelper;
|
||||
|
||||
class ScheduledNoificationsTest extends \Test\TestCase {
|
||||
|
||||
/** @var CardMapper|\PHPUnit_Framework_MockObject_MockObject */
|
||||
/** @var CardMapper|\PHPUnit\Framework\MockObject\MockObject */
|
||||
protected $cardMapper;
|
||||
/** @var NotificationHelper|\PHPUnit_Framework_MockObject_MockObject */
|
||||
/** @var NotificationHelper|\PHPUnit\Framework\MockObject\MockObject */
|
||||
protected $notificationHelper;
|
||||
/** @var ScheduledNotifications */
|
||||
protected $scheduledNotifications;
|
||||
|
||||
148
tests/unit/Db/AttachmentMapperTest.php
Normal file
148
tests/unit/Db/AttachmentMapperTest.php
Normal file
@@ -0,0 +1,148 @@
|
||||
<?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\Db;
|
||||
|
||||
use OCP\AppFramework\Db\DoesNotExistException;
|
||||
use OCP\IDBConnection;
|
||||
use OCP\IGroupManager;
|
||||
use OCP\IUserManager;
|
||||
use Test\AppFramework\Db\MapperTestUtility;
|
||||
|
||||
/**
|
||||
* @group DB
|
||||
*/
|
||||
class AttachmentMapperTest extends MapperTestUtility {
|
||||
|
||||
/** @var IDBConnection */
|
||||
private $dbConnection;
|
||||
/** @var AttachmentMapper */
|
||||
private $attachmentMapper;
|
||||
/** @var IUserManager|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $userManager;
|
||||
/** @var CardMapper|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $cardMapper;
|
||||
|
||||
// Data
|
||||
private $attachments;
|
||||
private $attachmentsById = [];
|
||||
|
||||
public function setup(){
|
||||
parent::setUp();
|
||||
|
||||
$this->userManager = $this->createMock(IUserManager::class);
|
||||
$this->cardMapper = $this->createMock(CardMapper::class);
|
||||
|
||||
$this->dbConnection = \OC::$server->getDatabaseConnection();
|
||||
$this->attachmentMapper = new AttachmentMapper(
|
||||
$this->dbConnection,
|
||||
$this->cardMapper,
|
||||
$this->userManager
|
||||
);
|
||||
$this->attachments = [
|
||||
$this->createAttachmentEntity(1, 'deck_file', 'file1.pdf'),
|
||||
$this->createAttachmentEntity(1, 'deck_file', 'file2.pdf'),
|
||||
$this->createAttachmentEntity(2, 'deck_file', 'file3.pdf'),
|
||||
$this->createAttachmentEntity(3, 'deck_file', 'file4.pdf')
|
||||
];
|
||||
foreach ($this->attachments as $attachment) {
|
||||
$entry = $this->attachmentMapper->insert($attachment);
|
||||
$entry->resetUpdatedFields();
|
||||
$this->attachmentsById[$entry->getId()] = $entry;
|
||||
}
|
||||
}
|
||||
|
||||
private function createAttachmentEntity($cardId, $type, $data) {
|
||||
$attachment = new Attachment();
|
||||
$attachment->setCardId($cardId);
|
||||
$attachment->setType($type);
|
||||
$attachment->setData($data);
|
||||
$attachment->setCreatedBy('admin');
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
public function testFind() {
|
||||
foreach ($this->attachmentsById as $id => $attachment) {
|
||||
$this->assertEquals($attachment, $this->attachmentMapper->find($id));
|
||||
}
|
||||
}
|
||||
|
||||
public function testFindAll() {
|
||||
$attachmentsByCard = [
|
||||
$this->attachmentMapper->findAll(1),
|
||||
$this->attachmentMapper->findAll(2),
|
||||
$this->attachmentMapper->findAll(3)
|
||||
];
|
||||
$this->assertEquals($attachmentsByCard[0], $this->attachmentMapper->findAll(1));
|
||||
$this->assertEquals($attachmentsByCard[1], $this->attachmentMapper->findAll(2));
|
||||
$this->assertEquals($attachmentsByCard[2], $this->attachmentMapper->findAll(3));
|
||||
$this->assertEquals([], $this->attachmentMapper->findAll(5));
|
||||
}
|
||||
|
||||
public function testFindToDelete() {
|
||||
$attachmentsToDelete = $this->attachments;
|
||||
$attachmentsToDelete[0]->setDeletedAt(1);
|
||||
$attachmentsToDelete[2]->setDeletedAt(1);
|
||||
$this->attachmentMapper->update($attachmentsToDelete[0]);
|
||||
$this->attachmentMapper->update($attachmentsToDelete[2]);
|
||||
foreach ($attachmentsToDelete as $attachment) {
|
||||
$attachment->resetUpdatedFields();
|
||||
}
|
||||
|
||||
$this->assertEquals([$attachmentsToDelete[0]], $this->attachmentMapper->findToDelete(1));
|
||||
$this->assertEquals([$attachmentsToDelete[2]], $this->attachmentMapper->findToDelete(2));
|
||||
}
|
||||
|
||||
public function testIsOwner() {
|
||||
$this->cardMapper->expects($this->once())
|
||||
->method('isOwner')
|
||||
->with('admin', 1)
|
||||
->willReturn(true);
|
||||
$this->assertTrue($this->attachmentMapper->isOwner('admin', (string)$this->attachments[0]->getId()));
|
||||
}
|
||||
|
||||
public function testIsOwnerInvalid() {
|
||||
$this->cardMapper->expects($this->once())
|
||||
->method('isOwner')
|
||||
->with('admin', 1)
|
||||
->will($this->throwException(new DoesNotExistException('does not exist')));
|
||||
$this->assertFalse($this->attachmentMapper->isOwner('admin', (string)$this->attachments[0]->getId()));
|
||||
}
|
||||
|
||||
public function testFindBoardId() {
|
||||
$this->cardMapper->expects($this->any())
|
||||
->method('findBoardId')
|
||||
->willReturn(123);
|
||||
foreach ($this->attachmentsById as $attachment) {
|
||||
$this->assertEquals(123, $this->attachmentMapper->findBoardId($attachment->getId()));
|
||||
}
|
||||
}
|
||||
|
||||
public function tearDown() {
|
||||
parent::tearDown();
|
||||
foreach ($this->attachments as $attachment) {
|
||||
$this->attachmentMapper->delete($attachment);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
51
tests/unit/Db/AttachmentTest.php
Normal file
51
tests/unit/Db/AttachmentTest.php
Normal file
@@ -0,0 +1,51 @@
|
||||
<?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\Db;
|
||||
|
||||
class AttachmentTest extends \Test\TestCase {
|
||||
private function createAttachment() {
|
||||
$attachment = new Attachment();
|
||||
$attachment->setId(1);
|
||||
$attachment->setCardId(123);
|
||||
$attachment->setData("blob");
|
||||
$attachment->setCreatedBy('admin');
|
||||
$attachment->setType('deck_file');
|
||||
return $attachment;
|
||||
}
|
||||
public function testJsonSerialize() {
|
||||
$board = $this->createAttachment();
|
||||
$this->assertEquals([
|
||||
'id' => 1,
|
||||
'cardId' => 123,
|
||||
'lastModified' => 0,
|
||||
'data' => 'blob',
|
||||
'type' => 'deck_file',
|
||||
'createdAt' => 0,
|
||||
'createdBy' => 'admin',
|
||||
'deletedAt' => 0,
|
||||
'extendedData' => []
|
||||
|
||||
], $board->jsonSerialize());
|
||||
}
|
||||
}
|
||||
@@ -35,13 +35,13 @@ class BoardMapperTest extends MapperTestUtility {
|
||||
|
||||
/** @var IDBConnection */
|
||||
private $dbConnection;
|
||||
/** @var AclMapper|\PHPUnit_Framework_MockObject_MockObject */
|
||||
/** @var AclMapper|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $aclMapper;
|
||||
/** @var BoardMapper */
|
||||
private $boardMapper;
|
||||
/** @var IUserManager|\PHPUnit_Framework_MockObject_MockObject */
|
||||
/** @var IUserManager|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $userManager;
|
||||
/** @var IGroupManager|\PHPUnit_Framework_MockObject_MockObject */
|
||||
/** @var IGroupManager|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $groupManager;
|
||||
|
||||
// Data
|
||||
|
||||
@@ -25,8 +25,9 @@ namespace OCA\Deck\Db;
|
||||
|
||||
use DateInterval;
|
||||
use DateTime;
|
||||
use Test\TestCase;
|
||||
|
||||
class CardTest extends \PHPUnit_Framework_TestCase {
|
||||
class CardTest extends TestCase {
|
||||
private function createCard() {
|
||||
$card = new Card();
|
||||
$card->setId(1);
|
||||
@@ -77,6 +78,8 @@ class CardTest extends \PHPUnit_Framework_TestCase {
|
||||
'duedate' => null,
|
||||
'overdue' => 0,
|
||||
'archived' => false,
|
||||
'attachments' => null,
|
||||
'attachmentCount' => null,
|
||||
'assignedUsers' => null,
|
||||
], $card->jsonSerialize());
|
||||
}
|
||||
@@ -97,6 +100,8 @@ class CardTest extends \PHPUnit_Framework_TestCase {
|
||||
'duedate' => null,
|
||||
'overdue' => 0,
|
||||
'archived' => false,
|
||||
'attachments' => null,
|
||||
'attachmentCount' => null,
|
||||
'assignedUsers' => null,
|
||||
], $card->jsonSerialize());
|
||||
}
|
||||
@@ -127,6 +132,8 @@ class CardTest extends \PHPUnit_Framework_TestCase {
|
||||
'duedate' => null,
|
||||
'overdue' => 0,
|
||||
'archived' => false,
|
||||
'attachments' => null,
|
||||
'attachmentCount' => null,
|
||||
'assignedUsers' => ['user1'],
|
||||
], $card->jsonSerialize());
|
||||
}
|
||||
|
||||
@@ -23,7 +23,9 @@
|
||||
|
||||
namespace OCA\Deck\Db;
|
||||
|
||||
class LabelTest extends \PHPUnit_Framework_TestCase {
|
||||
use Test\TestCase;
|
||||
|
||||
class LabelTest extends TestCase {
|
||||
private function createLabel() {
|
||||
$label = new Label();
|
||||
$label->setId(1);
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
|
||||
namespace OCA\Deck\Db;
|
||||
|
||||
class StackTest extends \PHPUnit_Framework_TestCase {
|
||||
class StackTest extends \Test\TestCase {
|
||||
private function createStack() {
|
||||
$board = new Stack();
|
||||
$board->setId(1);
|
||||
|
||||
@@ -25,11 +25,12 @@ namespace OCA\Deck\Db;
|
||||
|
||||
use OCA\Deck\ArchivedItemException;
|
||||
use OCA\Deck\Controller\PageController;
|
||||
use OCA\Deck\InvalidAttachmentType;
|
||||
use OCA\Deck\NoPermissionException;
|
||||
use OCA\Deck\NotFoundException;
|
||||
use OCA\Deck\StatusException;
|
||||
|
||||
class ExceptionsTest extends \PHPUnit_Framework_TestCase {
|
||||
class ExceptionsTest extends \Test\TestCase {
|
||||
|
||||
public function testNoPermissionException() {
|
||||
$c = new \stdClass();
|
||||
@@ -49,6 +50,11 @@ class ExceptionsTest extends \PHPUnit_Framework_TestCase {
|
||||
$this->assertEquals('foo', $e->getMessage());
|
||||
}
|
||||
|
||||
public function testInvalidAttachmentType() {
|
||||
$e = new InvalidAttachmentType('foo');
|
||||
$this->assertEquals('No matching IAttachmentService implementation found for type foo', $e->getMessage());
|
||||
}
|
||||
|
||||
public function testStatusException() {
|
||||
$e = new StatusException('foo');
|
||||
$this->assertEquals('foo', $e->getMessage());
|
||||
|
||||
365
tests/unit/Service/AttachmentServiceTest.php
Normal file
365
tests/unit/Service/AttachmentServiceTest.php
Normal file
@@ -0,0 +1,365 @@
|
||||
<?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\AppInfo\Application;
|
||||
use OCA\Deck\Db\Acl;
|
||||
use OCA\Deck\Db\Attachment;
|
||||
use OCA\Deck\Db\AttachmentMapper;
|
||||
use OCA\Deck\Db\CardMapper;
|
||||
use OCA\Deck\InvalidAttachmentType;
|
||||
use OCP\AppFramework\Http\Response;
|
||||
use OCP\AppFramework\IAppContainer;
|
||||
use OCP\ICache;
|
||||
use OCP\ICacheFactory;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Test\TestCase;
|
||||
|
||||
/** @internal Just for testing the service registration */
|
||||
class MyAttachmentService {
|
||||
public function extendData(Attachment $attachment) {}
|
||||
public function display(Attachment $attachment) {}
|
||||
public function create(Attachment $attachment) {}
|
||||
public function update(Attachment $attachment) {}
|
||||
public function delete(Attachment $attachment) {}
|
||||
public function allowUndo() {}
|
||||
public function markAsDeleted(Attachment $attachment) {}
|
||||
}
|
||||
|
||||
class AttachmentServiceTest extends TestCase {
|
||||
|
||||
/** @var AttachmentMapper|MockObject */
|
||||
private $attachmentMapper;
|
||||
/** @var CardMapper|MockObject */
|
||||
private $cardMapper;
|
||||
/** @var PermissionService|MockObject */
|
||||
private $permissionService;
|
||||
private $userId = 'admin';
|
||||
/** @var Application|MockObject */
|
||||
private $application;
|
||||
private $cacheFactory;
|
||||
/** @var AttachmentService */
|
||||
private $attachmentService;
|
||||
/** @var MockObject */
|
||||
private $attachmentServiceImpl;
|
||||
private $appContainer;
|
||||
/** ICache */
|
||||
private $cache;
|
||||
|
||||
/**
|
||||
* @throws \OCP\AppFramework\QueryException
|
||||
*/
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->attachmentServiceImpl = $this->createMock(IAttachmentService::class);
|
||||
$this->appContainer = $this->createMock(IAppContainer::class);
|
||||
|
||||
$this->attachmentMapper = $this->createMock(AttachmentMapper::class);
|
||||
$this->cardMapper = $this->createMock(CardMapper::class);
|
||||
$this->permissionService = $this->createMock(PermissionService::class);
|
||||
$this->application = $this->createMock(Application::class);
|
||||
$this->cacheFactory = $this->createMock(ICacheFactory::class);
|
||||
|
||||
$this->cache = $this->createMock(ICache::class);
|
||||
$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->application->expects($this->any())
|
||||
->method('getContainer')
|
||||
->willReturn($this->appContainer);
|
||||
|
||||
$this->attachmentService = new AttachmentService($this->attachmentMapper, $this->cardMapper, $this->permissionService, $this->application, $this->cacheFactory, $this->userId);
|
||||
}
|
||||
|
||||
public function testRegisterAttachmentService() {
|
||||
$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());
|
||||
$appContainer->expects($this->at(0))->method('query')->with(FileService::class)->willReturn($fileServiceMock);
|
||||
$application->expects($this->any())
|
||||
->method('getContainer')
|
||||
->willReturn($appContainer);
|
||||
$attachmentService = new AttachmentService($this->attachmentMapper, $this->cardMapper, $this->permissionService, $application, $this->cacheFactory, $this->userId);
|
||||
$attachmentService->registerAttachmentService('custom', MyAttachmentService::class);
|
||||
$this->assertEquals($fileServiceMock, $attachmentService->getService('deck_file'));
|
||||
$this->assertEquals(MyAttachmentService::class, get_class($attachmentService->getService('custom')));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \OCA\Deck\InvalidAttachmentType
|
||||
*/
|
||||
public function testRegisterAttachmentServiceNotExisting() {
|
||||
$application = $this->createMock(Application::class);
|
||||
$appContainer = $this->createMock(IAppContainer::class);
|
||||
$fileServiceMock = $this->createMock(FileService::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());
|
||||
$application->expects($this->any())
|
||||
->method('getContainer')
|
||||
->willReturn($appContainer);
|
||||
$attachmentService = new AttachmentService($this->attachmentMapper, $this->cardMapper, $this->permissionService, $application, $this->cacheFactory, $this->userId);
|
||||
$attachmentService->registerAttachmentService('custom', MyAttachmentService::class);
|
||||
$attachmentService->getService('deck_file_invalid');
|
||||
}
|
||||
|
||||
private function mockPermission($permission) {
|
||||
$this->permissionService->expects($this->once())
|
||||
->method('checkPermission')
|
||||
->with($this->cardMapper, 123, $permission);
|
||||
}
|
||||
|
||||
private function createAttachment($type, $data) {
|
||||
$attachment = new Attachment();
|
||||
$attachment->setType($type);
|
||||
$attachment->setData($data);
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
public function testFindAll() {
|
||||
$this->mockPermission(Acl::PERMISSION_READ);
|
||||
$attachments = [
|
||||
$this->createAttachment('deck_file','file1'),
|
||||
$this->createAttachment('deck_file','file2'),
|
||||
$this->createAttachment('deck_file_invalid','file3'),
|
||||
];
|
||||
$this->attachmentMapper->expects($this->once())
|
||||
->method('findAll')
|
||||
->with(123)
|
||||
->willReturn($attachments);
|
||||
|
||||
$this->attachmentServiceImpl->expects($this->at(0))
|
||||
->method('extendData')
|
||||
->with($attachments[0]);
|
||||
$this->attachmentServiceImpl->expects($this->at(1))
|
||||
->method('extendData')
|
||||
->with($attachments[1]);
|
||||
$this->assertEquals($attachments, $this->attachmentService->findAll(123, false));
|
||||
}
|
||||
|
||||
public function testFindAllWithDeleted() {
|
||||
$this->mockPermission(Acl::PERMISSION_READ);
|
||||
$attachments = [
|
||||
$this->createAttachment('deck_file','file1'),
|
||||
$this->createAttachment('deck_file','file2'),
|
||||
$this->createAttachment('deck_file_invalid','file3'),
|
||||
];
|
||||
$attachmentsDeleted = [
|
||||
$this->createAttachment('deck_file','file4'),
|
||||
$this->createAttachment('deck_file','file5'),
|
||||
$this->createAttachment('deck_file_invalid','file6'),
|
||||
];
|
||||
$this->attachmentMapper->expects($this->once())
|
||||
->method('findAll')
|
||||
->with(123)
|
||||
->willReturn($attachments);
|
||||
$this->attachmentMapper->expects($this->once())
|
||||
->method('findToDelete')
|
||||
->with(123, false)
|
||||
->willReturn($attachmentsDeleted);
|
||||
|
||||
$this->attachmentServiceImpl->expects($this->at(0))
|
||||
->method('extendData')
|
||||
->with($attachments[0]);
|
||||
$this->attachmentServiceImpl->expects($this->at(1))
|
||||
->method('extendData')
|
||||
->with($attachments[1]);
|
||||
$this->assertEquals(array_merge($attachments, $attachmentsDeleted), $this->attachmentService->findAll(123, true));
|
||||
}
|
||||
|
||||
public function testCount() {
|
||||
$this->cache->expects($this->once())->method('get')->with('card-123')->willReturn(null);
|
||||
$this->attachmentMapper->expects($this->once())->method('findAll')->willReturn([1,2,3,4]);
|
||||
$this->cache->expects($this->once())->method('set')->with('card-123', 4)->willReturn(null);
|
||||
$this->assertEquals(4, $this->attachmentService->count(123));
|
||||
}
|
||||
|
||||
public function testCountCacheHit() {
|
||||
$this->cache->expects($this->once())->method('get')->with('card-123')->willReturn(4);
|
||||
$this->assertEquals(4, $this->attachmentService->count(123));
|
||||
}
|
||||
|
||||
public function testCreate() {
|
||||
$attachment = $this->createAttachment('deck_file', 'file_name.jpg');
|
||||
$expected = $this->createAttachment('deck_file', 'file_name.jpg');
|
||||
$this->mockPermission(Acl::PERMISSION_EDIT);
|
||||
$this->cache->expects($this->once())->method('clear')->with('card-123');
|
||||
$this->attachmentServiceImpl->expects($this->once())
|
||||
->method('create');
|
||||
$this->attachmentMapper->expects($this->once())
|
||||
->method('insert')
|
||||
->willReturn($attachment);
|
||||
$this->attachmentServiceImpl->expects($this->once())
|
||||
->method('extendData')
|
||||
->willReturnCallback(function($a) { $a->setExtendedData(['mime' => 'image/jpeg']); });
|
||||
|
||||
$actual = $this->attachmentService->create(123, 'deck_file', 'file_name.jpg');
|
||||
|
||||
$expected->setExtendedData(['mime' => 'image/jpeg']);
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
public function testDisplay() {
|
||||
$attachment = $this->createAttachment('deck_file', 'filename');
|
||||
$response = new Response();
|
||||
$this->mockPermission(Acl::PERMISSION_READ);
|
||||
$this->attachmentMapper->expects($this->once())
|
||||
->method('find')
|
||||
->with(1)
|
||||
->willReturn($attachment);
|
||||
$this->attachmentServiceImpl->expects($this->once())
|
||||
->method('display')
|
||||
->with($attachment)
|
||||
->willReturn($response);
|
||||
$actual = $this->attachmentService->display(123, 1);
|
||||
$this->assertEquals($response, $actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \OCA\Deck\NotFoundException
|
||||
*/
|
||||
public function testDisplayInvalid() {
|
||||
$attachment = $this->createAttachment('deck_file', 'filename');
|
||||
$response = new Response();
|
||||
$this->mockPermission(Acl::PERMISSION_READ);
|
||||
$this->attachmentMapper->expects($this->once())
|
||||
->method('find')
|
||||
->with(1)
|
||||
->willReturn($attachment);
|
||||
$this->attachmentServiceImpl->expects($this->once())
|
||||
->method('display')
|
||||
->with($attachment)
|
||||
->will($this->throwException(new InvalidAttachmentType('deck_file')));
|
||||
$this->attachmentService->display(123, 1);
|
||||
}
|
||||
public function testUpdate() {
|
||||
$attachment = $this->createAttachment('deck_file', 'file_name.jpg');
|
||||
$expected = $this->createAttachment('deck_file', 'file_name.jpg');
|
||||
$this->mockPermission(Acl::PERMISSION_EDIT);
|
||||
$this->cache->expects($this->once())->method('clear')->with('card-123');
|
||||
$this->attachmentMapper->expects($this->once())
|
||||
->method('find')
|
||||
->with(1)
|
||||
->willReturn($attachment);
|
||||
$this->attachmentServiceImpl->expects($this->once())
|
||||
->method('update');
|
||||
$this->attachmentMapper->expects($this->once())
|
||||
->method('update')
|
||||
->willReturn($attachment);
|
||||
$this->attachmentServiceImpl->expects($this->once())
|
||||
->method('extendData')
|
||||
->willReturnCallback(function($a) { $a->setExtendedData(['mime' => 'image/jpeg']); });
|
||||
|
||||
$actual = $this->attachmentService->update(123, 1, 'file_name.jpg');
|
||||
|
||||
$expected->setExtendedData(['mime' => 'image/jpeg']);
|
||||
$expected->setLastModified($attachment->getLastModified());
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
public function testDelete() {
|
||||
$attachment = $this->createAttachment('deck_file', 'file_name.jpg');
|
||||
$expected = $this->createAttachment('deck_file', 'file_name.jpg');
|
||||
$this->mockPermission(Acl::PERMISSION_EDIT);
|
||||
$this->cache->expects($this->once())->method('clear')->with('card-123');
|
||||
$this->attachmentMapper->expects($this->once())
|
||||
->method('find')
|
||||
->with(1)
|
||||
->willReturn($attachment);
|
||||
$this->attachmentServiceImpl->expects($this->once())
|
||||
->method('allowUndo')
|
||||
->willReturn(false);
|
||||
$this->attachmentServiceImpl->expects($this->once())
|
||||
->method('delete');
|
||||
$this->attachmentMapper->expects($this->once())
|
||||
->method('delete')
|
||||
->willReturn($attachment);
|
||||
$actual = $this->attachmentService->delete(123, 1);
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
public function testDeleteWithUndo() {
|
||||
$attachment = $this->createAttachment('deck_file', 'file_name.jpg');
|
||||
$expected = $this->createAttachment('deck_file', 'file_name.jpg');
|
||||
$this->mockPermission(Acl::PERMISSION_EDIT);
|
||||
$this->cache->expects($this->once())->method('clear')->with('card-123');
|
||||
$this->attachmentMapper->expects($this->once())
|
||||
->method('find')
|
||||
->with(1)
|
||||
->willReturn($attachment);
|
||||
$this->attachmentServiceImpl->expects($this->once())
|
||||
->method('allowUndo')
|
||||
->willReturn(true);
|
||||
$this->attachmentServiceImpl->expects($this->once())
|
||||
->method('markAsDeleted')
|
||||
->willReturnCallback(function($a) { $a->setDeletedAt(23); });
|
||||
$this->attachmentMapper->expects($this->once())
|
||||
->method('update')
|
||||
->willReturn($attachment);
|
||||
$expected->setDeletedAt(23);
|
||||
$actual = $this->attachmentService->delete(123, 1);
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
public function testRestore() {
|
||||
$attachment = $this->createAttachment('deck_file', 'file_name.jpg');
|
||||
$expected = $this->createAttachment('deck_file', 'file_name.jpg');
|
||||
$this->mockPermission(Acl::PERMISSION_EDIT);
|
||||
$this->cache->expects($this->once())->method('clear')->with('card-123');
|
||||
$this->attachmentMapper->expects($this->once())
|
||||
->method('find')
|
||||
->with(1)
|
||||
->willReturn($attachment);
|
||||
$this->attachmentServiceImpl->expects($this->once())
|
||||
->method('allowUndo')
|
||||
->willReturn(true);
|
||||
$this->attachmentMapper->expects($this->once())
|
||||
->method('update')
|
||||
->willReturn($attachment);
|
||||
$expected->setDeletedAt(0);
|
||||
$actual = $this->attachmentService->restore(123, 1);
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \OCA\Deck\NoPermissionException
|
||||
*/
|
||||
public function testRestoreNotAllowed() {
|
||||
$attachment = $this->createAttachment('deck_file', 'file_name.jpg');
|
||||
$expected = $this->createAttachment('deck_file', 'file_name.jpg');
|
||||
$this->mockPermission(Acl::PERMISSION_EDIT);
|
||||
$this->cache->expects($this->once())->method('clear')->with('card-123');
|
||||
$this->attachmentMapper->expects($this->once())
|
||||
->method('find')
|
||||
->with(1)
|
||||
->willReturn($attachment);
|
||||
$this->attachmentServiceImpl->expects($this->once())
|
||||
->method('allowUndo')
|
||||
->willReturn(false);
|
||||
$actual = $this->attachmentService->restore(123, 1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -35,17 +35,17 @@ use Test\TestCase;
|
||||
|
||||
class CardServiceTest extends TestCase {
|
||||
|
||||
/** @var CardService|\PHPUnit_Framework_MockObject_MockObject */
|
||||
/** @var CardService|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $cardService;
|
||||
/** @var CardMapper|\PHPUnit_Framework_MockObject_MockObject */
|
||||
/** @var CardMapper|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $cardMapper;
|
||||
/** @var StackMapper|\PHPUnit_Framework_MockObject_MockObject */
|
||||
/** @var StackMapper|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $stackMapper;
|
||||
/** @var PermissionService|\PHPUnit_Framework_MockObject_MockObject */
|
||||
/** @var PermissionService|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $permissionService;
|
||||
/** @var AssignedUsersMapper|\PHPUnit_Framework_MockObject_MockObject */
|
||||
/** @var AssignedUsersMapper|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $assignedUsersMapper;
|
||||
/** @var BoardService|\PHPUnit_Framework_MockObject_MockObject */
|
||||
/** @var BoardService|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $boardService;
|
||||
|
||||
public function setUp() {
|
||||
@@ -55,7 +55,8 @@ class CardServiceTest extends TestCase {
|
||||
$this->permissionService = $this->createMock(PermissionService::class);
|
||||
$this->boardService = $this->createMock(BoardService::class);
|
||||
$this->assignedUsersMapper = $this->createMock(AssignedUsersMapper::class);
|
||||
$this->cardService = new CardService($this->cardMapper, $this->stackMapper, $this->permissionService, $this->boardService, $this->assignedUsersMapper);
|
||||
$this->attachmentService = $this->createMock(AttachmentService::class);
|
||||
$this->cardService = new CardService($this->cardMapper, $this->stackMapper, $this->permissionService, $this->boardService, $this->assignedUsersMapper, $this->attachmentService);
|
||||
}
|
||||
|
||||
public function testFind() {
|
||||
|
||||
302
tests/unit/Service/FileServiceTest.php
Normal file
302
tests/unit/Service/FileServiceTest.php
Normal file
@@ -0,0 +1,302 @@
|
||||
<?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\AppInfo\Application;
|
||||
use OCA\Deck\Db\AssignedUsers;
|
||||
use OCA\Deck\Db\AssignedUsersMapper;
|
||||
use OCA\Deck\Db\Attachment;
|
||||
use OCA\Deck\Db\AttachmentMapper;
|
||||
use OCA\Deck\Db\Card;
|
||||
use OCA\Deck\Db\CardMapper;
|
||||
use OCA\Deck\Db\StackMapper;
|
||||
use OCA\Deck\InvalidAttachmentType;
|
||||
use OCA\Deck\NotFoundException;
|
||||
use OCA\Deck\StatusException;
|
||||
use OCP\AppFramework\Http\ContentSecurityPolicy;
|
||||
use OCP\AppFramework\Http\FileDisplayResponse;
|
||||
use OCP\AppFramework\Http\Response;
|
||||
use OCP\AppFramework\IAppContainer;
|
||||
use OCP\Files\IAppData;
|
||||
use OCP\Files\SimpleFS\ISimpleFile;
|
||||
use OCP\Files\SimpleFS\ISimpleFolder;
|
||||
use OCP\ICacheFactory;
|
||||
use OCP\IL10N;
|
||||
use OCP\ILogger;
|
||||
use OCP\IRequest;
|
||||
use PHPUnit\Framework\MockObject\MockObject;
|
||||
use Test\TestCase;
|
||||
|
||||
class FileServiceTest extends TestCase {
|
||||
|
||||
/** @var IL10N|MockObject */
|
||||
private $l10n;
|
||||
/** @var IAppData|MockObject */
|
||||
private $appData;
|
||||
/** @var IRequest|MockObject */
|
||||
private $request;
|
||||
/** @var ILogger|MockObject */
|
||||
private $logger;
|
||||
/** @var FileService */
|
||||
private $fileService;
|
||||
|
||||
public function setUp() {
|
||||
parent::setUp();
|
||||
$this->request = $this->createMock(IRequest::class);
|
||||
$this->appData = $this->createMock(IAppData::class);
|
||||
$this->l10n = $this->createMock(IL10N::class);
|
||||
$this->logger = $this->createMock(ILogger::class);
|
||||
$this->fileService = new FileService($this->l10n, $this->appData, $this->request, $this->logger);
|
||||
}
|
||||
|
||||
public function mockGetFolder($cardId) {
|
||||
$folder = $this->createMock(ISimpleFolder::class);
|
||||
$this->appData->expects($this->once())
|
||||
->method('getFolder')
|
||||
->with('file-card-' . $cardId)
|
||||
->willReturn($folder);
|
||||
return $folder;
|
||||
}
|
||||
public function mockGetFolderFailure($cardId) {
|
||||
$folder = $this->createMock(ISimpleFolder::class);
|
||||
$this->appData->expects($this->once())
|
||||
->method('getFolder')
|
||||
->with('file-card-' . $cardId)
|
||||
->will($this->throwException(new \OCP\Files\NotFoundException()));
|
||||
$this->appData->expects($this->once())
|
||||
->method('newFolder')
|
||||
->with('file-card-' . $cardId)
|
||||
->willReturn($folder);
|
||||
return $folder;
|
||||
}
|
||||
|
||||
private function getAttachment() {
|
||||
$attachment = new Attachment();
|
||||
$attachment->setId(1);
|
||||
$attachment->setCardId(123);
|
||||
return $attachment;
|
||||
}
|
||||
|
||||
private function mockGetUploadedFileEmpty() {
|
||||
$this->request->expects($this->once())
|
||||
->method('getUploadedFile')
|
||||
->willReturn([]);
|
||||
}
|
||||
private function mockGetUploadedFileError($error) {
|
||||
$this->request->expects($this->once())
|
||||
->method('getUploadedFile')
|
||||
->willReturn(['error' => $error]);
|
||||
}
|
||||
private function mockGetUploadedFile() {
|
||||
$this->request->expects($this->once())
|
||||
->method('getUploadedFile')
|
||||
->willReturn([
|
||||
'name' => 'file.jpg',
|
||||
'tmp_name' => __FILE__,
|
||||
]);
|
||||
}
|
||||
|
||||
public function testExtendDataNotFound() {
|
||||
$attachment = $this->getAttachment();
|
||||
$folder = $this->mockGetFolder(123);
|
||||
$folder->expects($this->once())->method('getFile')->will($this->throwException(new \OCP\Files\NotFoundException()));
|
||||
$this->assertEquals($attachment, $this->fileService->extendData($attachment));
|
||||
}
|
||||
|
||||
public function testExtendDataNotPermitted() {
|
||||
$attachment = $this->getAttachment();
|
||||
$folder = $this->mockGetFolder(123);
|
||||
$folder->expects($this->once())->method('getFile')->will($this->throwException(new \OCP\Files\NotPermittedException()));
|
||||
$this->assertEquals($attachment, $this->fileService->extendData($attachment));
|
||||
}
|
||||
|
||||
public function testExtendData() {
|
||||
$attachment = $this->getAttachment();
|
||||
$expected = $this->getAttachment();
|
||||
$expected->setExtendedData([
|
||||
'filesize' => 100,
|
||||
'mimetype' => 'image/jpeg',
|
||||
'info' => pathinfo(__FILE__)
|
||||
]);
|
||||
|
||||
$file = $this->createMock(ISimpleFile::class);
|
||||
$file->expects($this->once())->method('getSize')->willReturn(100);
|
||||
$file->expects($this->once())->method('getMimeType')->willReturn('image/jpeg');
|
||||
$file->expects($this->once())->method('getName')->willReturn(__FILE__);
|
||||
|
||||
$folder = $this->mockGetFolder(123);
|
||||
$folder->expects($this->once())->method('getFile')->willReturn($file);
|
||||
$this->assertEquals($expected, $this->fileService->extendData($attachment));
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Exception
|
||||
*/
|
||||
public function testCreateEmpty() {
|
||||
$attachment = $this->getAttachment();
|
||||
$this->l10n->expects($this->any())
|
||||
->method('t')
|
||||
->willReturn('Error');
|
||||
$this->mockGetUploadedFileEmpty();
|
||||
$this->fileService->create($attachment);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Exception
|
||||
*/
|
||||
public function testCreateError() {
|
||||
$attachment = $this->getAttachment();
|
||||
$this->mockGetUploadedFileError(UPLOAD_ERR_INI_SIZE);
|
||||
$this->l10n->expects($this->any())
|
||||
->method('t')
|
||||
->willReturn('Error');
|
||||
$this->fileService->create($attachment);
|
||||
}
|
||||
|
||||
public function testCreate() {
|
||||
$attachment = $this->getAttachment();
|
||||
$this->mockGetUploadedFile();
|
||||
$folder = $this->mockGetFolder(123);
|
||||
$folder->expects($this->once())
|
||||
->method('fileExists')
|
||||
->willReturn(false);
|
||||
$file = $this->createMock(ISimpleFile::class);
|
||||
$file->expects($this->once())
|
||||
->method('putContent')
|
||||
->with(file_get_contents(__FILE__, 'r'));
|
||||
$folder->expects($this->once())
|
||||
->method('newFile')
|
||||
->willReturn($file);
|
||||
|
||||
$this->fileService->create($attachment);
|
||||
}
|
||||
|
||||
public function testCreateNoFolder() {
|
||||
$attachment = $this->getAttachment();
|
||||
$this->mockGetUploadedFile();
|
||||
$folder = $this->mockGetFolderFailure(123);
|
||||
$folder->expects($this->once())
|
||||
->method('fileExists')
|
||||
->willReturn(false);
|
||||
$file = $this->createMock(ISimpleFile::class);
|
||||
$file->expects($this->once())
|
||||
->method('putContent')
|
||||
->with(file_get_contents(__FILE__, 'r'));
|
||||
$folder->expects($this->once())
|
||||
->method('newFile')
|
||||
->willReturn($file);
|
||||
|
||||
$this->fileService->create($attachment);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Exception
|
||||
* @expectedExceptionMessage File already exists.
|
||||
*/
|
||||
public function testCreateExists() {
|
||||
$attachment = $this->getAttachment();
|
||||
$this->mockGetUploadedFile();
|
||||
$folder = $this->mockGetFolder(123);
|
||||
$folder->expects($this->once())
|
||||
->method('fileExists')
|
||||
->willReturn(true);
|
||||
$this->fileService->create($attachment);
|
||||
}
|
||||
|
||||
public function testUpdate() {
|
||||
$attachment = $this->getAttachment();
|
||||
$this->mockGetUploadedFile();
|
||||
$folder = $this->mockGetFolder(123);
|
||||
$file = $this->createMock(ISimpleFile::class);
|
||||
$file->expects($this->once())
|
||||
->method('putContent')
|
||||
->with(file_get_contents(__FILE__, 'r'));
|
||||
$folder->expects($this->once())
|
||||
->method('getFile')
|
||||
->willReturn($file);
|
||||
|
||||
$this->fileService->update($attachment);
|
||||
}
|
||||
|
||||
public function testDelete() {
|
||||
$attachment = $this->getAttachment();
|
||||
$file = $this->createMock(ISimpleFile::class);
|
||||
$folder = $this->mockGetFolder('123');
|
||||
$folder->expects($this->once())
|
||||
->method('getFile')
|
||||
->willReturn($file);
|
||||
$file->expects($this->once())
|
||||
->method('delete');
|
||||
$this->fileService->delete($attachment);
|
||||
}
|
||||
|
||||
public function testDisplay() {
|
||||
$attachment = $this->getAttachment();
|
||||
$file = $this->createMock(ISimpleFile::class);
|
||||
$folder = $this->mockGetFolder('123');
|
||||
$folder->expects($this->once())
|
||||
->method('getFile')
|
||||
->willReturn($file);
|
||||
$file->expects($this->exactly(2))
|
||||
->method('getMimeType')
|
||||
->willReturn('image/jpeg');
|
||||
$actual = $this->fileService->display($attachment);
|
||||
$expected = new FileDisplayResponse($file);
|
||||
$expected->addHeader('Content-Type', 'image/jpeg');
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
public function testDisplayPdf() {
|
||||
$attachment = $this->getAttachment();
|
||||
$file = $this->createMock(ISimpleFile::class);
|
||||
$folder = $this->mockGetFolder('123');
|
||||
$folder->expects($this->once())
|
||||
->method('getFile')
|
||||
->willReturn($file);
|
||||
$file->expects($this->exactly(2))
|
||||
->method('getMimeType')
|
||||
->willReturn('application/pdf');
|
||||
$actual = $this->fileService->display($attachment);
|
||||
$expected = new FileDisplayResponse($file);
|
||||
$expected->addHeader('Content-Type', 'application/pdf');
|
||||
$policy = new ContentSecurityPolicy();
|
||||
$policy->addAllowedObjectDomain('\'self\'');
|
||||
$policy->addAllowedObjectDomain('blob:');
|
||||
$expected->setContentSecurityPolicy($policy);
|
||||
$this->assertEquals($expected, $actual);
|
||||
}
|
||||
|
||||
public function testAllowUndo() {
|
||||
$this->assertTrue($this->fileService->allowUndo());
|
||||
}
|
||||
|
||||
public function testMarkAsDeleted() {
|
||||
// TODO: use proper ITimeFactory in the service so we can mock the call to time
|
||||
$attachment = $this->getAttachment();
|
||||
$this->assertEquals(0, $attachment->getDeletedAt());
|
||||
$this->fileService->markAsDeleted($attachment);
|
||||
$this->assertGreaterThan(0, $attachment->getDeletedAt());
|
||||
}
|
||||
}
|
||||
@@ -30,13 +30,13 @@ use Test\TestCase;
|
||||
|
||||
class LabelServiceTest extends TestCase {
|
||||
|
||||
/** @var LabelMapper|\PHPUnit_Framework_MockObject_MockObject */
|
||||
/** @var LabelMapper|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $labelMapper;
|
||||
/** @var PermissionService|\PHPUnit_Framework_MockObject_MockObject */
|
||||
/** @var PermissionService|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $permissionService;
|
||||
/** @var LabelService */
|
||||
private $labelService;
|
||||
/** @var BoardService|\PHPUnit_Framework_MockObject_MockObject */
|
||||
/** @var BoardService|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $boardService;
|
||||
|
||||
public function setUp() {
|
||||
|
||||
@@ -37,7 +37,7 @@ use OCP\ILogger;
|
||||
use OCP\IUser;
|
||||
use OCP\IUserManager;
|
||||
|
||||
class PermissionServiceTest extends \PHPUnit_Framework_TestCase {
|
||||
class PermissionServiceTest extends \Test\TestCase {
|
||||
|
||||
/** @var PermissionService*/
|
||||
private $service;
|
||||
|
||||
@@ -44,17 +44,19 @@ class StackServiceTest extends TestCase {
|
||||
|
||||
/** @var StackService */
|
||||
private $stackService;
|
||||
/** @var \PHPUnit_Framework_MockObject_MockObject|StackMapper */
|
||||
/** @var \PHPUnit\Framework\MockObject\MockObject|StackMapper */
|
||||
private $stackMapper;
|
||||
/** @var \PHPUnit_Framework_MockObject_MockObject|CardMapper */
|
||||
/** @var \PHPUnit\Framework\MockObject\MockObject|CardMapper */
|
||||
private $cardMapper;
|
||||
/** @var \PHPUnit_Framework_MockObject_MockObject|LabelMapper */
|
||||
/** @var \PHPUnit\Framework\MockObject\MockObject|LabelMapper */
|
||||
private $labelMapper;
|
||||
/** @var \PHPUnit_Framework_MockObject_MockObject|PermissionService */
|
||||
/** @var \PHPUnit\Framework\MockObject\MockObject|PermissionService */
|
||||
private $permissionService;
|
||||
/** @var AssignedUsersMapper|\PHPUnit_Framework_MockObject_MockObject */
|
||||
/** @var AssignedUsersMapper|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $assignedUsersMapper;
|
||||
/** @var BoardService|\PHPUnit_Framework_MockObject_MockObject */
|
||||
/** @var AttachmentService|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $attachmentService;
|
||||
/** @var BoardService|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $boardService;
|
||||
|
||||
public function setUp() {
|
||||
@@ -65,6 +67,7 @@ class StackServiceTest extends TestCase {
|
||||
$this->permissionService = $this->createMock(PermissionService::class);
|
||||
$this->boardService = $this->createMock(BoardService::class);
|
||||
$this->assignedUsersMapper = $this->createMock(AssignedUsersMapper::class);
|
||||
$this->attachmentService = $this->createMock(AttachmentService::class);
|
||||
|
||||
$this->stackService = new StackService(
|
||||
$this->stackMapper,
|
||||
@@ -72,7 +75,8 @@ class StackServiceTest extends TestCase {
|
||||
$this->labelMapper,
|
||||
$this->permissionService,
|
||||
$this->boardService,
|
||||
$this->assignedUsersMapper
|
||||
$this->assignedUsersMapper,
|
||||
$this->attachmentService
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
105
tests/unit/controller/AttachmentControllerTest.php
Normal file
105
tests/unit/controller/AttachmentControllerTest.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?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\Db\Acl;
|
||||
use OCA\Deck\Service\AttachmentService;
|
||||
use OCA\Deck\Service\CardService;
|
||||
use OCA\Deck\Service\LabelService;
|
||||
use OCA\Deck\Service\StackService;
|
||||
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() {
|
||||
$this->request = $this->createMock(IRequest::class);
|
||||
$this->attachmentService = $this->createMock(AttachmentService::class);
|
||||
$this->controller = new AttachmentController(
|
||||
'deck',
|
||||
$this->request,
|
||||
$this->attachmentService,
|
||||
$this->userId
|
||||
);
|
||||
}
|
||||
|
||||
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(1, 2);
|
||||
$this->controller->display(1, 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(1, 2, 'data')
|
||||
->willReturn(1);
|
||||
$this->assertEquals(1, $this->controller->update(1, 2));
|
||||
}
|
||||
|
||||
|
||||
public function testDelete() {
|
||||
$this->attachmentService->expects($this->once())
|
||||
->method('delete')
|
||||
->with(123, 234)
|
||||
->willReturn(1);
|
||||
$this->assertEquals(1, $this->controller->delete(123, 234));
|
||||
}
|
||||
|
||||
public function testRestore() {
|
||||
$this->attachmentService->expects($this->once())
|
||||
->method('restore')
|
||||
->with(123, 234)
|
||||
->willReturn(1);
|
||||
$this->assertEquals(1, $this->controller->restore(123, 234));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -26,7 +26,7 @@ namespace OCA\Deck\Controller;
|
||||
use OCA\Deck\Db\Acl;
|
||||
use OCP\IUser;
|
||||
|
||||
class BoardControllerTest extends \PHPUnit_Framework_TestCase {
|
||||
class BoardControllerTest extends \Test\TestCase {
|
||||
|
||||
private $controller;
|
||||
private $request;
|
||||
|
||||
@@ -27,13 +27,13 @@ use OCA\Deck\Service\CardService;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\IRequest;
|
||||
|
||||
class CardControllerTest extends \PHPUnit_Framework_TestCase {
|
||||
class CardControllerTest extends \Test\TestCase {
|
||||
|
||||
/** @var CardController|\PHPUnit_Framework_MockObject_MockObject */
|
||||
/** @var CardController|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $controller;
|
||||
/** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */
|
||||
/** @var IRequest|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $request;
|
||||
/** @var CardService|\PHPUnit_Framework_MockObject_MockObject */
|
||||
/** @var CardService|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $cardService;
|
||||
/** @var string */
|
||||
private $userId = 'user';
|
||||
|
||||
@@ -29,13 +29,13 @@ use OCA\Deck\Service\LabelService;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\IRequest;
|
||||
|
||||
class LabelControllerTest extends \PHPUnit_Framework_TestCase {
|
||||
class LabelControllerTest extends \Test\TestCase {
|
||||
|
||||
/** @var Controller|\PHPUnit_Framework_MockObject_MockObject */
|
||||
/** @var Controller|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $controller;
|
||||
/** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */
|
||||
/** @var IRequest|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $request;
|
||||
/** @var LabelService|\PHPUnit_Framework_MockObject_MockObject */
|
||||
/** @var LabelService|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $labelService;
|
||||
/** @var string */
|
||||
private $userId = 'user';
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace OCA\Deck\Controller;
|
||||
|
||||
use PHPUnit_Framework_TestCase;
|
||||
|
||||
class PageControllerTest extends \PHPUnit_Framework_TestCase {
|
||||
class PageControllerTest extends \Test\TestCase {
|
||||
|
||||
private $controller;
|
||||
private $request;
|
||||
|
||||
@@ -30,13 +30,13 @@ use OCA\Deck\Service\StackService;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\IRequest;
|
||||
|
||||
class StackControllerTest extends \PHPUnit_Framework_TestCase {
|
||||
class StackControllerTest extends \Test\TestCase {
|
||||
|
||||
/** @var Controller|\PHPUnit_Framework_MockObject_MockObject */
|
||||
/** @var Controller|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $controller;
|
||||
/** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */
|
||||
/** @var IRequest|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $request;
|
||||
/** @var StackService|\PHPUnit_Framework_MockObject_MockObject */
|
||||
/** @var StackService|\PHPUnit\Framework\MockObject\MockObject */
|
||||
private $stackService;
|
||||
/** @var string */
|
||||
private $userId = 'user';
|
||||
|
||||
Reference in New Issue
Block a user