diff --git a/.drone.yml b/.drone.yml
index d57d9fc1f..32e80c476 100644
--- a/.drone.yml
+++ b/.drone.yml
@@ -67,7 +67,7 @@ pipeline:
matrix:
TESTS: syntax-php7.0
php5.6:
- image: nextcloudci/php5.6:php5.6-3
+ image: nextcloudci/php5.6:php5.6-7
environment:
- APP_NAME=deck
- CORE_BRANCH=master
@@ -93,7 +93,7 @@ pipeline:
matrix:
TESTS: php5.6
php7.0:
- image: nextcloudci/php7.0:php7.0-7
+ image: nextcloudci/php7.0:php7.0-8
environment:
- APP_NAME=deck
- CORE_BRANCH=master
@@ -170,4 +170,4 @@ matrix:
- TESTS: jsbuild
- TESTS: integration
-branches: [ master, stable* ]
+
diff --git a/appinfo/database.xml b/appinfo/database.xml
index 50af46975..a49526952 100644
--- a/appinfo/database.xml
+++ b/appinfo/database.xml
@@ -38,6 +38,14 @@
boolean
false
+
+ deleted_at
+ integer
+ 0
+ 8
+ false
+ true
+
diff --git a/appinfo/info.xml b/appinfo/info.xml
index 05f092643..6db4b30dd 100644
--- a/appinfo/info.xml
+++ b/appinfo/info.xml
@@ -16,7 +16,7 @@
💥 This is still alpha software: it may not be stable enough for production!
- 0.1.4.1
+ 0.1.4.2
agpl
Julius Härtl
Deck
@@ -30,6 +30,9 @@
+
+ OCA\Deck\Cron\DeleteCron
+
OCA\Deck\Migration\UnknownUsers
diff --git a/appinfo/routes.php b/appinfo/routes.php
index 52ddf0455..2f0e32af5 100644
--- a/appinfo/routes.php
+++ b/appinfo/routes.php
@@ -31,6 +31,7 @@ return [
['name' => 'board#read', 'url' => '/boards/{boardId}', 'verb' => 'GET'],
['name' => 'board#update', 'url' => '/boards/{boardId}', 'verb' => 'PUT'],
['name' => 'board#delete', 'url' => '/boards/{boardId}', 'verb' => 'DELETE'],
+ ['name' => 'board#deleteUndo', 'url' => '/boards/{boardId}/deleteUndo', 'verb' => 'POST'],
['name' => 'board#getUserPermissions', 'url' => '/boards/{boardId}/permissions', 'verb' => 'GET'],
['name' => 'board#addAcl', 'url' => '/boards/{boardId}/acl', 'verb' => 'POST'],
['name' => 'board#updateAcl', 'url' => '/boards/{boardId}/acl', 'verb' => 'PUT'],
diff --git a/css/style.css b/css/style.css
index ef639ac2f..faeab3f5c 100644
--- a/css/style.css
+++ b/css/style.css
@@ -65,6 +65,14 @@ button.button-inline:hover {
* Navigation sidebar
*/
+.app-navigation-entry-menu ul {
+ flex-direction: row;
+}
+
+.app-navigation-entry-utils-menu-button {
+ display: block !important;
+}
+
.app-navigation-entry-utils-menu-share {
display: flex !important;
padding: 14px;
@@ -318,9 +326,9 @@ button.button-inline:hover {
.popovermenu {
z-index: 999;
opacity: 1;
- margin-top: 25px;
- margin-right: 3px;
display: block;
+ margin-top: 25px;
+ margin-right: 0px;
}
.popovermenu.hidden {
@@ -614,23 +622,30 @@ button.button-inline:hover {
.colorselect {
overflow: hidden;
- clear: both;
- padding-top: 4px;
- padding-left: 4px;
+ border-radius:3px;
+ flex-direction: row;
+ min-width: 240px;
+ height: 34px;
+ display: flex;
+ margin: 3px 3px 3px 0;
}
.colorselect .color {
opacity: 0.7;
- width: 27px;
- height: 27px;
- float: left;
- margin-right: 2px;
+ height: 100%;
+ flex-grow: 1;
border: none;
}
.colorselect .selected {
+ background-image: url(../../../core/img/actions/checkmark.svg);
+ background-position: center center;
+ background-repeat: no-repeat;
opacity: 1.0;
- border: 1px solid #333333;
+}
+
+.colorselect .dark.selected {
+ background-image: url(../../../core/img/actions/checkmark-white.svg);
}
.labels .colorselect {
@@ -662,6 +677,16 @@ button.button-inline:hover {
cursor: pointer;
display: block;
}
+#boardlist .app-popover-menu-utils {
+ width: 30px;
+ display: inline;
+ position: relative;
+}
+
+.popovermenu ul {
+ display: flex !important;
+ flex-direction: column;
+}
#boardlist td {
padding: 10px;
@@ -680,15 +705,34 @@ button.button-inline:hover {
.cell-board-title {
width: 50%;
-
}
-#boardlist .colorselect,
-#boardlist input {
- float: left;
+#boardlist tr.deleted td * {
+ opacity: 0.5;
}
-#boardlist .colorselect {
+#boardlist td form {
+ display: flex;
+ width: 100%;
+}
+
+#boardlist td .colorselect {
+ flex-grow: 1;
+}
+
+#boardlist td input[type=text] {
+ flex-grow: 2;
+}
+
+#boardlist td input[type=submit] {
+ width: 32px;
+}
+
+#boardlist td .app-popover-menu-utils {
+ float: right;
+}
+
+#boardlist .popovermenu {
margin-top: 5px;
}
@@ -925,6 +969,10 @@ button.button-inline:hover {
* Custom icons
*/
+.icon-deck {
+ background-image: url(../img/deck.svg);
+}
+
.icon-group {
background-image: url('../../../settings/img/users.svg');
}
diff --git a/img/deck.svg b/img/deck.svg
new file mode 100644
index 000000000..9431bc06b
--- /dev/null
+++ b/img/deck.svg
@@ -0,0 +1 @@
+
diff --git a/js/app/Config.js b/js/app/Config.js
index 3c3ae25b3..f1330ddb3 100644
--- a/js/app/Config.js
+++ b/js/app/Config.js
@@ -33,36 +33,40 @@ app.config(function ($provide, $routeProvider, $interpolateProvider, $httpProvid
});
markdownItConverterProvider.use(markdownitLinkTarget);
- $urlRouterProvider.otherwise("/");
+ $urlRouterProvider.otherwise('/');
$stateProvider
.state('list', {
- url: "/",
- templateUrl: "/boardlist.mainView.html",
- controller: 'ListController'
+ url: '/:filter',
+ templateUrl: '/boardlist.mainView.html',
+ controller: 'ListController',
+ reloadOnSearch: false,
+ params: {
+ filter: { value: '', dynamic: true }
+ }
})
.state('board', {
- url: "/board/:boardId/:filter",
- templateUrl: "/board.html",
+ url: '/board/:boardId/:filter',
+ templateUrl: '/board.html',
controller: 'BoardController',
params: {
filter: { value: '', dynamic: true }
}
})
.state('board.detail', {
- url: "/detail/",
+ url: '/detail/',
reloadOnSearch : false,
views: {
- "sidebarView": {
- templateUrl: "/board.sidebarView.html"
+ 'sidebarView': {
+ templateUrl: '/board.sidebarView.html'
}
}
})
.state('board.card', {
- url: "/card/:cardId",
+ url: '/card/:cardId',
views: {
- "sidebarView": {
- templateUrl: "/card.sidebarView.html",
+ 'sidebarView': {
+ templateUrl: '/card.sidebarView.html',
controller: 'CardController'
}
}
diff --git a/js/app/Run.js b/js/app/Run.js
index e76787b11..274f689a2 100644
--- a/js/app/Run.js
+++ b/js/app/Run.js
@@ -20,11 +20,17 @@
*
*/
-app.run(function ($document, $rootScope, $transitions) {
+app.run(function ($document, $rootScope, $transitions, BoardService) {
'use strict';
$document.click(function (event) {
$rootScope.$broadcast('documentClicked', event);
});
+ $transitions.onEnter({from: 'list'}, function($state, $transition$) {
+ BoardService.unsetCurrrent();
+ });
+ $transitions.onEnter({to: 'list'}, function($state, $transition$) {
+ BoardService.unsetCurrrent();
+ });
$transitions.onEnter({to: 'board.card'}, function ($state, $transition$) {
$rootScope.sidebar.show = true;
});
@@ -40,9 +46,6 @@ app.run(function ($document, $rootScope, $transitions) {
$transitions.onExit({from: 'board.detail'}, function ($state) {
$rootScope.sidebar.show = false;
});
- $transitions.onEnter({to: 'board.archive'}, function ($state) {
- //BoardController.loadArchived();
- });
$('link[rel="shortcut icon"]').attr(
'href',
diff --git a/js/controller/BoardController.js b/js/controller/BoardController.js
index 87895e805..46260f2eb 100644
--- a/js/controller/BoardController.js
+++ b/js/controller/BoardController.js
@@ -80,7 +80,7 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St
}
};
$scope.checkCanEdit = function () {
- return !$scope.archived;
+ return !BoardService.getCurrent().archived;
};
// filter cards here, as ng-sortable will not work nicely with html-inline filters
@@ -114,7 +114,6 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St
// Handle initial Loading
BoardService.fetchOne($scope.id).then(function (data) {
- BoardService.getPermissions();
$scope.statusservice.releaseWaiting();
}, function (error) {
$scope.statusservice.setError('Error occured', error);
diff --git a/js/controller/CardController.js b/js/controller/CardController.js
index 9aa2e9812..424659160 100644
--- a/js/controller/CardController.js
+++ b/js/controller/CardController.js
@@ -48,9 +48,10 @@ app.controller('CardController', function ($scope, $rootScope, $routeParams, $lo
}
};
$scope.cardEditDescriptionShow = function($event) {
+ if(BoardService.isArchived() || CardService.getCurrent().archived) {
+ return false;
+ }
var node = $event.target.nodeName;
- console.log($event);
- console.log(BoardService);
if($scope.card.archived || !$scope.boardservice.canEdit()) {
console.log(node);
} else {
diff --git a/js/controller/ListController.js b/js/controller/ListController.js
index 7389aab35..c4b76388e 100644
--- a/js/controller/ListController.js
+++ b/js/controller/ListController.js
@@ -1,4 +1,3 @@
-
/*
* @copyright Copyright (c) 2016 Julius Härtl
*
@@ -21,11 +20,15 @@
*
*/
-app.controller('ListController', function ($scope, $location, $filter, BoardService, $element, $timeout) {
+/* global app angular */
+
+app.controller('ListController', function ($scope, $location, $filter, BoardService, $element, $timeout, $stateParams, $state) {
$scope.boards = [];
$scope.newBoard = {};
$scope.status = {
- deleteUndo: []
+ deleteUndo: [],
+ filter: $stateParams.filter ? $stateParams.filter : '',
+ sidebar: false
};
$scope.colors = ['0082c9', '00c9c6','00c906', 'c92b00', 'F1DB50', '7C31CC', '3A3B3D', 'CACBCD'];
$scope.boardservice = BoardService;
@@ -42,13 +45,42 @@ app.controller('ListController', function ($scope, $location, $filter, BoardServ
$scope.filterData = function () {
angular.copy($scope.boardservice.getData(), $scope.boardservice.sorted);
- $scope.boardservice.sorted = $filter('orderBy')($scope.boardservice.sorted, 'title');
+ angular.copy($scope.boardservice.sorted, $scope.boardservice.sidebar);
+ $scope.boardservice.sidebar = $filter('orderBy')($scope.boardservice.sidebar, 'title');
+ $scope.boardservice.sidebar = $filter('cardFilter')($scope.boardservice.sidebar, {archived: false});
+
+ if ($scope.status.filter === 'archived') {
+ var filter = {};
+ filter[$scope.status.filter] = true;
+ $scope.boardservice.sorted = $filter('cardFilter')($scope.boardservice.sorted, filter);
+ } else if ($scope.status.filter === 'shared') {
+ $scope.boardservice.sorted = $filter('cardFilter')($scope.boardservice.sorted, {archived: false});
+ $scope.boardservice.sorted = $filter('boardFilterAcl')($scope.boardservice.sorted);
+ } else {
+ $scope.boardservice.sorted = $filter('cardFilter')($scope.boardservice.sorted, {archived: false});
+ }
+ $scope.boardservice.sorted = $filter('orderBy')($scope.boardservice.sorted, ['deletedAt', 'title']);
};
+ $scope.$watchCollection(function(){
+ return $state.params;
+ }, function(){
+ $scope.status.filter = $state.params.filter;
+ $scope.filterData();
+ });
+
+
$scope.selectColor = function(color) {
$scope.newBoard.color = color;
};
+ $scope.gotoBoard = function(board) {
+ if(board.deletedAt > 0) {
+ return false;
+ }
+ return $state.go('board', {boardId: board.id});
+ };
+
$scope.boardCreate = function() {
if(!$scope.newBoard.title || !$scope.newBoard.color) {
$scope.status.addBoard=false;
@@ -72,26 +104,30 @@ app.controller('ListController', function ($scope, $location, $filter, BoardServ
board.status.edit = false;
};
+ $scope.boardArchive = function (board) {
+ board.archived = true;
+ BoardService.update(board).then(function(data) {
+ $scope.filterData();
+ });
+ };
+
+ $scope.boardUnarchive = function (board) {
+ board.archived = false;
+ BoardService.update(board).then(function(data) {
+ $scope.filterData();
+ });
+ };
+
$scope.boardDelete = function(board) {
- var boardId = board.id;
- $scope.status.deleteUndo[boardId] = 10;
- $scope.boardDeleteCountdown = function () {
- console.log($scope.status);
- if($scope.status.deleteUndo[boardId] > 0) {
- $scope.status.deleteUndo[boardId]--;
- $timeout($scope.boardDeleteCountdown, 1000);
- }
- if($scope.status.deleteUndo[boardId] === 0) {
- BoardService.delete(board.id).then(function (data) {
- $scope.filterData();
- });
- }
- };
- $timeout($scope.boardDeleteCountdown, 1000);
+ BoardService.delete(board.id).then(function (data) {
+ $scope.filterData();
+ });
};
$scope.boardDeleteUndo = function (board) {
- delete $scope.status.deleteUndo[board.id];
+ BoardService.deleteUndo(board.id).then(function (data) {
+ $scope.filterData();
+ });
};
});
diff --git a/js/filters/boardFilterAcl.js b/js/filters/boardFilterAcl.js
new file mode 100644
index 000000000..ef023823f
--- /dev/null
+++ b/js/filters/boardFilterAcl.js
@@ -0,0 +1,33 @@
+/*
+ * @copyright Copyright (c) 2017 Julius Härtl
+ *
+ * @author Julius Härtl
+ *
+ * @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 .
+ *
+ */
+
+app.filter('boardFilterAcl', function() {
+ return function(boards) {
+ var _result = [];
+ angular.forEach(boards, function(board){
+ if(board.acl !== null && Object.keys(board.acl).length > 0) {
+ _result.push(board);
+ }
+ });
+ return _result;
+ };
+});
\ No newline at end of file
diff --git a/js/filters/cardFilter.js b/js/filters/cardFilter.js
index c0f1657fb..0f50b7eed 100644
--- a/js/filters/cardFilter.js
+++ b/js/filters/cardFilter.js
@@ -24,15 +24,16 @@
app.filter('cardFilter', function() {
return function(cards, rules) {
- var _result = {};
+ var _result = [];
angular.forEach(cards, function(card){
var _card = card;
- angular.some(rules, function(rule, condition) {
- if(_card[rule]===condition) {
+ var keys = Object.keys(rules);
+ keys.some(function(key, condition) {
+ if(_card[key]===rules[key]) {
_result.push(_card);
}
});
});
- return result;
+ return _result;
};
});
\ No newline at end of file
diff --git a/js/filters/textColorFilter.js b/js/filters/textColorFilter.js
index 75b76827c..caaf53ae2 100644
--- a/js/filters/textColorFilter.js
+++ b/js/filters/textColorFilter.js
@@ -24,7 +24,7 @@ app.filter('textColorFilter', function() {
return function (hex) {
// RGB2HLS by Garry Tan
// http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
- var result = /^([A-Fa-f\d]{2})([A-Fa-f\d]{2})([A-Fa-f\d]{2})$/i.exec(hex);
+ var result = /^#?([A-Fa-f\d]{2})([A-Fa-f\d]{2})([A-Fa-f\d]{2})$/i.exec(hex);
var color = result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
diff --git a/js/service/ApiService.js b/js/service/ApiService.js
index e061efa8c..cbc394d6a 100644
--- a/js/service/ApiService.js
+++ b/js/service/ApiService.js
@@ -145,7 +145,13 @@ app.factory('ApiService', function($http, $q){
return this.data[this.id];
};
- ApiService.prototype.getData = function() {
+ ApiService.prototype.unsetCurrrent = function () {
+ this.id = null;
+ };
+
+
+
+ ApiService.prototype.getData = function() {
return $.map(this.data, function(value, index) {
return [value];
});
diff --git a/js/service/BoardService.js b/js/service/BoardService.js
index b359b1730..d41c024c0 100644
--- a/js/service/BoardService.js
+++ b/js/service/BoardService.js
@@ -26,6 +26,33 @@ app.factory('BoardService', function(ApiService, $http, $q){
};
BoardService.prototype = angular.copy(ApiService.prototype);
+ BoardService.prototype.delete = function (id) {
+ var deferred = $q.defer();
+ var self = this;
+
+ $http.delete(this.baseUrl + '/' + id).then(function (response) {
+ self.data[id].deletedAt = response.data.deletedAt;
+ deferred.resolve(response.data);
+ }, function (error) {
+ deferred.reject('Deleting ' + self.endpoint + ' failed');
+ });
+ return deferred.promise;
+ };
+
+ BoardService.prototype.deleteUndo = function (id) {
+ var deferred = $q.defer();
+ var self = this;
+ var _id = id;
+ $http.post(this.baseUrl + '/' + id + '/deleteUndo').then(function (response) {
+ self.data[_id].deletedAt = 0;
+ console.log(self.data[_id]);
+ deferred.resolve(response.data);
+ }, function (error) {
+ deferred.reject('Deleting ' + self.endpoint + ' failed');
+ });
+ return deferred.promise;
+ };
+
BoardService.prototype.searchUsers = function (search) {
var deferred = $q.defer();
var self = this;
@@ -151,44 +178,43 @@ app.factory('BoardService', function(ApiService, $http, $q){
return deferred.promise;
};
- BoardService.prototype.getPermissions = function() {
- var board = this.getCurrent();
- var deferred = $q.defer();
- $http.get(this.baseUrl + '/' + board.id + '/permissions').then(function (response) {
- board.permissions = response.data;
- deferred.resolve(response.data);
- }, function (error) {
- deferred.reject('Error fetching board permissions ' + board);
- });
- };
-
BoardService.prototype.canRead = function() {
if(!this.getCurrent() || !this.getCurrent().permissions) {
return false;
}
return this.getCurrent().permissions['PERMISSION_READ'];
- }
+ };
BoardService.prototype.canEdit = function() {
if(!this.getCurrent() || !this.getCurrent().permissions) {
return false;
}
return this.getCurrent().permissions['PERMISSION_EDIT'];
- }
+ };
- BoardService.prototype.canManage = function() {
+ BoardService.prototype.canManage = function(board) {
+ if(board !== null && board !== undefined) {
+ return board.permissions['PERMISSION_MANAGE'];
+ }
if(!this.getCurrent() || !this.getCurrent().permissions) {
return false;
}
return this.getCurrent().permissions['PERMISSION_MANAGE'];
- }
+ };
BoardService.prototype.canShare = function() {
if(!this.getCurrent() || !this.getCurrent().permissions) {
return false;
}
return this.getCurrent().permissions['PERMISSION_SHARE'];
- }
+ };
+
+ BoardService.prototype.isArchived = function () {
+ if(!this.getCurrent() || this.getCurrent().archived) {
+ return true;
+ }
+ return false;
+ };
service = new BoardService($http, 'boards', $q);
return service;
diff --git a/lib/CardArchivedException.php b/lib/ArchivedItemException.php
similarity index 88%
rename from lib/CardArchivedException.php
rename to lib/ArchivedItemException.php
index 4586f7813..352c9452f 100644
--- a/lib/CardArchivedException.php
+++ b/lib/ArchivedItemException.php
@@ -23,12 +23,12 @@
namespace OCA\Deck;
-class CardArchivedException extends \Exception {
+class ArchivedItemException extends \Exception {
/**
* Constructor
* @param string $msg the error message
*/
- public function __construct($msg = "") {
+ public function __construct($msg = "Operation not allowed. Item is archived.") {
parent::__construct($msg);
}
}
\ No newline at end of file
diff --git a/lib/Controller/BoardController.php b/lib/Controller/BoardController.php
index 3a8df7c23..f497455d0 100644
--- a/lib/Controller/BoardController.php
+++ b/lib/Controller/BoardController.php
@@ -96,8 +96,8 @@ class BoardController extends Controller {
* @param $color
* @return \OCP\AppFramework\Db\Entity
*/
- public function update($id, $title, $color) {
- return $this->boardService->update($id, $title, $color);
+ public function update($id, $title, $color, $archived) {
+ return $this->boardService->update($id, $title, $color, $archived);
}
/**
@@ -108,6 +108,14 @@ class BoardController extends Controller {
public function delete($boardId) {
return $this->boardService->delete($boardId);
}
+ /**
+ * @NoAdminRequired
+ * @param $boardId
+ * @return \OCP\AppFramework\Db\Entity
+ */
+ public function deleteUndo($boardId) {
+ return $this->boardService->deleteUndo($boardId);
+ }
/**
* @NoAdminRequired
diff --git a/lib/Cron/DeleteCron.php b/lib/Cron/DeleteCron.php
new file mode 100644
index 000000000..cdf35cedb
--- /dev/null
+++ b/lib/Cron/DeleteCron.php
@@ -0,0 +1,49 @@
+
+ *
+ * @author Julius Härtl
+ *
+ * @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 .
+ *
+ */
+
+/**
+ * Created by PhpStorm.
+ * User: jus
+ * Date: 16.05.17
+ * Time: 12:34
+ */
+
+namespace OCA\Deck\Cron;
+
+use OC\BackgroundJob\Job;
+use OCA\Deck\Db\BoardMapper;
+
+class DeleteCron extends Job {
+
+ public function __construct(BoardMapper $boardMapper) {
+ $this->boardMapper = $boardMapper;
+ }
+
+ protected function run($argument) {
+ $boards = $this->boardMapper->findToDelete();
+ foreach ($boards as $board) {
+ $this->boardMapper->delete($board);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/lib/Db/Board.php b/lib/Db/Board.php
index 4d3b5e42d..e5531fea8 100644
--- a/lib/Db/Board.php
+++ b/lib/Db/Board.php
@@ -34,15 +34,19 @@ class Board extends RelationalEntity implements JsonSerializable {
protected $archived = false;
protected $labels = [];
protected $acl = [];
+ protected $permissions = [];
protected $shared;
+ protected $deletedAt = 0;
public function __construct() {
$this->addType('id', 'integer');
$this->addType('shared', 'integer');
$this->addType('archived', 'boolean');
+ $this->addType('deletedAt', 'integer');
$this->addRelation('labels');
$this->addRelation('acl');
$this->addRelation('shared');
+ $this->addRelation('permissions');
$this->addResolvable('owner');
$this->shared = -1;
}
diff --git a/lib/Db/BoardMapper.php b/lib/Db/BoardMapper.php
index e1b2d0598..0c16d9d6c 100644
--- a/lib/Db/BoardMapper.php
+++ b/lib/Db/BoardMapper.php
@@ -59,7 +59,7 @@ class BoardMapper extends DeckMapper implements IPermissionMapper {
* @return \OCP\AppFramework\Db\Entity if not found
*/
public function find($id, $withLabels = false, $withAcl = false) {
- $sql = 'SELECT id, title, owner, color, archived FROM `*PREFIX*deck_boards` ' .
+ $sql = 'SELECT id, title, owner, color, archived, deleted_at FROM `*PREFIX*deck_boards` ' .
'WHERE `id` = ?';
$board = $this->findEntity($sql, [$id]);
@@ -87,8 +87,8 @@ class BoardMapper extends DeckMapper implements IPermissionMapper {
* @return array
*/
public function findAllByUser($userId, $limit = null, $offset = null) {
- $sql = 'SELECT id, title, owner, color, archived, 0 as shared FROM `*PREFIX*deck_boards` WHERE owner = ? UNION ' .
- 'SELECT boards.id, title, owner, color, archived, 1 as shared FROM `*PREFIX*deck_boards` as boards ' .
+ $sql = 'SELECT id, title, owner, color, archived, deleted_at, 0 as shared FROM `*PREFIX*deck_boards` WHERE owner = ? UNION ' .
+ 'SELECT boards.id, title, owner, color, archived, deleted_at, 1 as shared FROM `*PREFIX*deck_boards` as boards ' .
'JOIN `*PREFIX*deck_board_acl` as acl ON boards.id=acl.board_id WHERE acl.participant=? AND acl.type=? AND boards.owner != ?';
$entries = $this->findEntities($sql, [$userId, $userId, Acl::PERMISSION_TYPE_USER, $userId], $limit, $offset);
/* @var Board $entry */
@@ -112,7 +112,7 @@ class BoardMapper extends DeckMapper implements IPermissionMapper {
if (count($groups) <= 0) {
return [];
}
- $sql = 'SELECT boards.id, title, owner, color, archived, 2 as shared FROM `*PREFIX*deck_boards` as boards ' .
+ $sql = 'SELECT boards.id, title, owner, color, archived, deleted_at, 2 as shared FROM `*PREFIX*deck_boards` as boards ' .
'INNER JOIN `*PREFIX*deck_board_acl` as acl ON boards.id=acl.board_id WHERE owner != ? AND type=? AND (';
for ($i = 0; $i < count($groups); $i++) {
$sql .= 'acl.participant = ? ';
@@ -135,6 +135,15 @@ class BoardMapper extends DeckMapper implements IPermissionMapper {
return $this->findEntities($sql, []);
}
+ public function findToDelete() {
+ // add buffer of 5 min
+ $timeLimit = time()-(60*5);
+ $sql = 'SELECT id, title, owner, color, archived, deleted_at FROM `*PREFIX*deck_boards` ' .
+ 'WHERE `deleted_at` > 0 AND `deleted_at` < ?';
+ \OC::$server->getLogger()->error($sql);
+ return $this->findEntities($sql, [$timeLimit]);
+ }
+
public function delete(/** @noinspection PhpUnnecessaryFullyQualifiedNameInspection */
\OCP\AppFramework\Db\Entity $entity) {
// delete acl
diff --git a/lib/Service/BoardService.php b/lib/Service/BoardService.php
index 5a8af4f88..9e3f9948f 100644
--- a/lib/Service/BoardService.php
+++ b/lib/Service/BoardService.php
@@ -23,9 +23,12 @@
namespace OCA\Deck\Service;
+use OCA\Deck\ArchivedItemException;
use OCA\Deck\Db\Acl;
use OCA\Deck\Db\AclMapper;
+use OCA\Deck\Db\IPermissionMapper;
use OCA\Deck\Db\Label;
+use OCP\AppFramework\Db\DoesNotExistException;
use OCP\IL10N;
use OCA\Deck\Db\Board;
use OCA\Deck\Db\BoardMapper;
@@ -61,6 +64,13 @@ class BoardService {
$this->boardMapper->mapAcl($acl);
}
}
+ $permissions = $this->permissionService->matchPermissions($item);
+ $item->setPermissions([
+ 'PERMISSION_READ' => $permissions[Acl::PERMISSION_READ],
+ 'PERMISSION_EDIT' => $permissions[Acl::PERMISSION_EDIT],
+ 'PERMISSION_MANAGE' => $permissions[Acl::PERMISSION_MANAGE],
+ 'PERMISSION_SHARE' => $permissions[Acl::PERMISSION_SHARE]
+ ]);
$result[$item->getId()] = $item;
}
}
@@ -77,9 +87,50 @@ class BoardService {
$this->boardMapper->mapAcl($acl);
}
}
+ $permissions = $this->permissionService->matchPermissions($board);
+ $board->setPermissions([
+ 'PERMISSION_READ' => $permissions[Acl::PERMISSION_READ],
+ 'PERMISSION_EDIT' => $permissions[Acl::PERMISSION_EDIT],
+ 'PERMISSION_MANAGE' => $permissions[Acl::PERMISSION_MANAGE],
+ 'PERMISSION_SHARE' => $permissions[Acl::PERMISSION_SHARE]
+ ]);
return $board;
}
+ public function isArchived($mapper, $id) {
+ try {
+ if ($mapper instanceof IPermissionMapper) {
+ $boardId = $mapper->findBoardId($id);
+ } else {
+ $boardId = $id;
+ }
+ if ($boardId === null) {
+ return false;
+ }
+ } catch (DoesNotExistException $exception) {
+ return false;
+ }
+ $board = $this->find($boardId);
+ return $board->getArchived();
+ }
+
+ public function isDeleted($mapper, $id) {
+ try {
+ if ($mapper instanceof IPermissionMapper) {
+ $boardId = $mapper->findBoardId($id);
+ } else {
+ $boardId = $id;
+ }
+ if ($boardId === null) {
+ return false;
+ }
+ } catch (DoesNotExistException $exception) {
+ return false;
+ }
+ $board = $this->find($boardId);
+ return $board->getDeletedAt() > 0;
+ }
+
public function create($title, $userId, $color) {
@@ -106,20 +157,44 @@ class BoardService {
}
$new_board->setLabels($labels);
$this->boardMapper->mapOwner($new_board);
+ $permissions = $this->permissionService->matchPermissions($new_board);
+ $new_board->setPermissions([
+ 'PERMISSION_READ' => $permissions[Acl::PERMISSION_READ],
+ 'PERMISSION_EDIT' => $permissions[Acl::PERMISSION_EDIT],
+ 'PERMISSION_MANAGE' => $permissions[Acl::PERMISSION_MANAGE],
+ 'PERMISSION_SHARE' => $permissions[Acl::PERMISSION_SHARE]
+ ]);
return $new_board;
}
public function delete($id) {
$this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_READ);
- return $this->boardMapper->delete($this->find($id));
+ $board = $this->find($id);
+ $board->setDeletedAt(time());
+ $this->boardMapper->update($board);
+ return $board;
}
- public function update($id, $title, $color) {
+ public function deleteUndo($id) {
+ $this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_READ);
+ $board = $this->find($id);
+ $board->setDeletedAt(0);
+ $this->boardMapper->update($board);
+ }
+
+ public function deleteForce($id) {
+ $this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_READ);
+ $board = $this->find($id);
+ return $this->boardMapper->delete($board);
+ }
+
+ public function update($id, $title, $color, $archived) {
$this->permissionService->checkPermission($this->boardMapper, $id, Acl::PERMISSION_MANAGE);
$board = $this->find($id);
$board->setTitle($title);
$board->setColor($color);
+ $board->setArchived($archived);
$this->boardMapper->mapOwner($board);
return $this->boardMapper->update($board);
}
diff --git a/lib/Service/CardService.php b/lib/Service/CardService.php
index 7fd7a6b36..d841d02bc 100644
--- a/lib/Service/CardService.php
+++ b/lib/Service/CardService.php
@@ -26,18 +26,22 @@ namespace OCA\Deck\Service;
use OCA\Deck\Db\Card;
use OCA\Deck\Db\CardMapper;
use OCA\Deck\Db\Acl;
-use OCA\Deck\CardArchivedException;
use OCA\Deck\Db\StackMapper;
+use OCA\Deck\StatusException;
class CardService {
private $cardMapper;
+ private $stackMapper;
+ private $permissionService;
+ private $boardService;
- public function __construct(CardMapper $cardMapper, StackMapper $stackMapper, PermissionService $permissionService) {
+ public function __construct(CardMapper $cardMapper, StackMapper $stackMapper, PermissionService $permissionService, BoardService $boardService) {
$this->cardMapper = $cardMapper;
$this->stackMapper = $stackMapper;
$this->permissionService = $permissionService;
+ $this->boardService = $boardService;
}
public function find($cardId) {
@@ -51,6 +55,9 @@ class CardService {
*/
public function create($title, $stackId, $type, $order, $owner) {
$this->permissionService->checkPermission($this->stackMapper, $stackId, Acl::PERMISSION_EDIT);
+ if($this->boardService->isArchived($this->stackMapper, $stackId)) {
+ throw new StatusException('Operation not allowed. This board is archived.');
+ }
$card = new Card();
$card->setTitle($title);
$card->setStackId($stackId);
@@ -63,14 +70,20 @@ class CardService {
public function delete($id) {
$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT);
+ if($this->boardService->isArchived($this->cardMapper, $id)) {
+ throw new StatusException('Operation not allowed. This board is archived.');
+ }
return $this->cardMapper->delete($this->cardMapper->find($id));
}
public function update($id, $title, $stackId, $type, $order, $description, $owner) {
$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT);
+ if($this->boardService->isArchived($this->cardMapper, $id)) {
+ throw new StatusException('Operation not allowed. This board is archived.');
+ }
$card = $this->cardMapper->find($id);
if ($card->getArchived()) {
- throw new CardArchivedException();
+ throw new StatusException('Operation not allowed. This card is archived.');
}
$card->setTitle($title);
$card->setStackId($stackId);
@@ -83,9 +96,12 @@ class CardService {
public function rename($id, $title) {
$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT);
+ if($this->boardService->isArchived($this->cardMapper, $id)) {
+ throw new StatusException('Operation not allowed. This board is archived.');
+ }
$card = $this->cardMapper->find($id);
if ($card->getArchived()) {
- throw new CardArchivedException();
+ throw new StatusException('Operation not allowed. This card is archived.');
}
$card->setTitle($title);
return $this->cardMapper->update($card);
@@ -93,12 +109,15 @@ class CardService {
public function reorder($id, $stackId, $order) {
$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT);
+ if($this->boardService->isArchived($this->cardMapper, $id)) {
+ throw new StatusException('Operation not allowed. This board is archived.');
+ }
$cards = $this->cardMapper->findAll($stackId);
$result = [];
$i = 0;
foreach ($cards as $card) {
if ($card->getArchived()) {
- throw new CardArchivedException();
+ throw new StatusException('Operation not allowed. This card is archived.');
}
if ($card->id === $id) {
$card->setOrder($order);
@@ -121,6 +140,9 @@ class CardService {
public function archive($id) {
$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT);
+ if($this->boardService->isArchived($this->cardMapper, $id)) {
+ throw new StatusException('Operation not allowed. This board is archived.');
+ }
$card = $this->cardMapper->find($id);
$card->setArchived(true);
return $this->cardMapper->update($card);
@@ -128,6 +150,9 @@ class CardService {
public function unarchive($id) {
$this->permissionService->checkPermission($this->cardMapper, $id, Acl::PERMISSION_EDIT);
+ if($this->boardService->isArchived($this->cardMapper, $id)) {
+ throw new StatusException('Operation not allowed. This board is archived.');
+ }
$card = $this->cardMapper->find($id);
$card->setArchived(false);
return $this->cardMapper->update($card);
@@ -135,18 +160,24 @@ class CardService {
public function assignLabel($cardId, $labelId) {
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_EDIT);
+ if($this->boardService->isArchived($this->cardMapper, $cardId)) {
+ throw new StatusException('Operation not allowed. This board is archived.');
+ }
$card = $this->cardMapper->find($cardId);
if ($card->getArchived()) {
- throw new CardArchivedException();
+ throw new StatusException('Operation not allowed. This card is archived.');
}
$this->cardMapper->assignLabel($cardId, $labelId);
}
public function removeLabel($cardId, $labelId) {
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_EDIT);
+ if($this->boardService->isArchived($this->cardMapper, $cardId)) {
+ throw new StatusException('Operation not allowed. This board is archived.');
+ }
$card = $this->cardMapper->find($cardId);
if ($card->getArchived()) {
- throw new CardArchivedException();
+ throw new StatusException('Operation not allowed. This card is archived.');
}
$this->cardMapper->removeLabel($cardId, $labelId);
}
diff --git a/lib/Service/LabelService.php b/lib/Service/LabelService.php
index e1538adff..5562a60ca 100644
--- a/lib/Service/LabelService.php
+++ b/lib/Service/LabelService.php
@@ -26,15 +26,22 @@ namespace OCA\Deck\Service;
use OCA\Deck\Db\Label;
use OCA\Deck\Db\Acl;
use OCA\Deck\Db\LabelMapper;
+use OCA\Deck\StatusException;
class LabelService {
+ /** @var LabelMapper */
private $labelMapper;
+ /** @var PermissionService */
+ private $permissionService;
+ /** @var BoardService */
+ private $boardService;
- public function __construct(LabelMapper $labelMapper, PermissionService $permissionService) {
+ public function __construct(LabelMapper $labelMapper, PermissionService $permissionService, BoardService $boardService) {
$this->labelMapper = $labelMapper;
$this->permissionService = $permissionService;
+ $this->boardService = $boardService;
}
public function find($labelId) {
@@ -44,6 +51,9 @@ class LabelService {
public function create($title, $color, $boardId) {
$this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_MANAGE);
+ if($this->boardService->isArchived(null, $boardId)) {
+ throw new StatusException('Operation not allowed. This board is archived.');
+ }
$label = new Label();
$label->setTitle($title);
$label->setColor($color);
@@ -53,11 +63,17 @@ class LabelService {
public function delete($id) {
$this->permissionService->checkPermission($this->labelMapper, $id, Acl::PERMISSION_MANAGE);
+ if($this->boardService->isArchived($this->labelMapper, $id)) {
+ throw new StatusException('Operation not allowed. This board is archived.');
+ }
return $this->labelMapper->delete($this->find($id));
}
public function update($id, $title, $color) {
$this->permissionService->checkPermission($this->labelMapper, $id, Acl::PERMISSION_MANAGE);
+ if($this->boardService->isArchived($this->labelMapper, $id)) {
+ throw new StatusException('Operation not allowed. This board is archived.');
+ }
$label = $this->find($id);
$label->setTitle($title);
$label->setColor($color);
diff --git a/lib/Service/PermissionService.php b/lib/Service/PermissionService.php
index bd3985a51..c877d7134 100644
--- a/lib/Service/PermissionService.php
+++ b/lib/Service/PermissionService.php
@@ -25,6 +25,7 @@ namespace OCA\Deck\Service;
use OCA\Deck\Db\Acl;
use OCA\Deck\Db\AclMapper;
+use OCA\Deck\Db\Board;
use OCA\Deck\Db\BoardMapper;
use OCA\Deck\Db\IPermissionMapper;
use OCA\Deck\NoPermissionException;
@@ -50,7 +51,7 @@ class PermissionService {
}
/**
- * Get current user permissions for a board
+ * Get current user permissions for a board by id
*
* @param $boardId
* @return bool|array
@@ -66,6 +67,24 @@ class PermissionService {
];
}
+ /**
+ * Get current user permissions for a board
+ *
+ * @param Board $board
+ * @return array|bool
+ * @internal param $boardId
+ */
+ public function matchPermissions(Board $board) {
+ $owner = $this->userIsBoardOwner($board->getId());
+ $acls = $board->getAcl();
+ return [
+ Acl::PERMISSION_READ => $owner || $this->userCan($acls, Acl::PERMISSION_READ),
+ Acl::PERMISSION_EDIT => $owner || $this->userCan($acls, Acl::PERMISSION_EDIT),
+ Acl::PERMISSION_MANAGE => $owner || $this->userCan($acls, Acl::PERMISSION_MANAGE),
+ Acl::PERMISSION_SHARE => $owner || $this->userCan($acls, Acl::PERMISSION_SHARE),
+ ];
+ }
+
/**
* check permissions for replacing dark magic middleware
*
diff --git a/lib/Service/StackService.php b/lib/Service/StackService.php
index 7c94ee3ce..18cb95cbb 100644
--- a/lib/Service/StackService.php
+++ b/lib/Service/StackService.php
@@ -31,6 +31,7 @@ use OCA\Deck\Db\LabelMapper;
use OCA\Deck\Db\Stack;
use OCA\Deck\Db\StackMapper;
+use OCA\Deck\StatusException;
class StackService {
@@ -39,12 +40,14 @@ class StackService {
private $cardMapper;
private $labelMapper;
private $permissionService;
+ private $boardService;
- public function __construct(StackMapper $stackMapper, CardMapper $cardMapper, LabelMapper $labelMapper, PermissionService $permissionService) {
+ public function __construct(StackMapper $stackMapper, CardMapper $cardMapper, LabelMapper $labelMapper, PermissionService $permissionService, BoardService $boardService) {
$this->stackMapper = $stackMapper;
$this->cardMapper = $cardMapper;
$this->labelMapper = $labelMapper;
$this->permissionService = $permissionService;
+ $this->boardService = $boardService;
}
public function findAll($boardId) {
@@ -81,6 +84,9 @@ class StackService {
public function create($title, $boardId, $order) {
$this->permissionService->checkPermission(null, $boardId, Acl::PERMISSION_MANAGE);
+ if($this->boardService->isArchived(null, $boardId)) {
+ throw new StatusException('Operation not allowed. This board is archived.');
+ }
$stack = new Stack();
$stack->setTitle($title);
$stack->setBoardId($boardId);
@@ -96,6 +102,9 @@ class StackService {
public function update($id, $title, $boardId, $order) {
$this->permissionService->checkPermission($this->stackMapper, $id, Acl::PERMISSION_MANAGE);
+ if($this->boardService->isArchived($this->stackMapper, $id)) {
+ throw new StatusException('Operation not allowed. This board is archived.');
+ }
$stack = $this->stackMapper->find($id);
$stack->setTitle($title);
$stack->setBoardId($boardId);
diff --git a/templates/main.php b/templates/main.php
index a22fe8132..ed014caba 100644
--- a/templates/main.php
+++ b/templates/main.php
@@ -45,7 +45,7 @@ if(!\OC::$server->getConfig()->getSystemValue('debug', false)) {
'app' => ['App', 'Config', 'Run'],
'controller' => ['AppController', 'BoardController', 'CardController', 'ListController'],
'directive' => ['appnavigationentryutils', 'appPopoverMenuUtils', 'autofocusoninsert', 'avatar', 'elastic', 'search'],
- 'filters' => ['cardFilter', 'cardSearchFilter', 'iconWhiteFilter', 'lightenColorFilter', 'orderObjectBy', 'relativeDateFilter', 'textColorFilter'],
+ 'filters' => ['boardFilterAcl', 'cardFilter', 'cardSearchFilter', 'iconWhiteFilter', 'lightenColorFilter', 'orderObjectBy', 'relativeDateFilter', 'textColorFilter'],
'service' => ['ApiService', 'BoardService', 'CardService', 'LabelService', 'StackService', 'StatusService'],
];
foreach($js as $folder=>$files) {
@@ -58,7 +58,7 @@ if(!\OC::$server->getConfig()->getSystemValue('debug', false)) {
-
+
inc('part.navigation')); ?>
inc('part.settings')); */ ?>
diff --git a/templates/part.board.mainView.php b/templates/part.board.mainView.php
index 92875a26b..ede5fedb8 100644
--- a/templates/part.board.mainView.php
+++ b/templates/part.board.mainView.php
@@ -64,46 +64,40 @@
-
-
-
-
+
-
-
-
+
diff --git a/templates/part.board.sidebarView.php b/templates/part.board.sidebarView.php
index cb54dbfab..dbcb81767 100644
--- a/templates/part.board.sidebarView.php
+++ b/templates/part.board.sidebarView.php
@@ -9,6 +9,7 @@
{{ boardservice.getCurrent().title }}
+{{board=boardservice.getCurrent();""}}
diff --git a/templates/part.card.php b/templates/part.card.php
index 9257f8efe..c423a6859 100644
--- a/templates/part.card.php
+++ b/templates/part.card.php
@@ -31,6 +31,7 @@