Compare commits

..

47 Commits

Author SHA1 Message Date
Julius Härtl
8b4e7ec2bf Merge pull request #107 from nextcloud/release-0.1.3
Prepare 0.1.3 release
2017-05-01 10:04:13 +02:00
Julius Härtl
111a98ce88 Prepare 0.1.3 release
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2017-05-01 08:44:26 +02:00
Artem Anufrij
7fb0d2374d better behavior for comment field (#105)
* better behavior for comment field
* removed min-height

Signed-off-by: Artem Anufrij <artem.anufrij@live.de>
2017-05-01 08:35:31 +02:00
Nextcloud bot
491a73456b [tx-robot] updated from transifex 2017-05-01 00:10:29 +00:00
artemanufrij
62d671bdd5 hide sidebar when clicked outside (#102)
* hide sidebar when clicked outside

* fixed show/hide behavior

Signed-off-by: Artem Anufrij <artem.anufrij@live.de>
2017-04-30 21:23:48 +02:00
Julius Härtl
955c5eb864 Merge pull request #99 from nextcloud/bump-dependencies
Bump bower dependencies
2017-04-30 13:13:38 +02:00
Julius Härtl
49cd271cbc Add link to nightly builds 2017-04-30 13:07:49 +02:00
Julius Härtl
37bdbecdc2 Merge pull request #103 from nextcloud/nightly
Fix paths to use local bower/grunt installation
2017-04-30 13:03:25 +02:00
Julius Härtl
62af080e78 Fix paths to use local bower/grunt installation
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2017-04-30 12:57:10 +02:00
Julius Härtl
84d4b87bb8 Bump bower dependencies
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2017-04-30 09:37:04 +02:00
Julius Härtl
5ad515f42a Merge pull request #96 from nextcloud/fix-db-columns
Move text fields to clob
2017-04-29 13:20:16 +02:00
Julius Härtl
98b57fe0a4 Merge pull request #65 from nextcloud/test-behat
Add integration tests with behat
2017-04-29 13:08:18 +02:00
Julius Härtl
b961206da3 Include App integration test
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2017-04-29 13:04:00 +02:00
artemanufrij
d8d8137300 description marker (#98)
* description marker
* removed unused content
* removed unused content
* Changed Icon and removen unused .svg

Signed-off-by: Artem Anufrij <artem.anufrij@live.de>
2017-04-29 12:52:53 +02:00
Julius Haertl
56fcb081a4 Fix tests
Signed-off-by: Julius Haertl <jus@bitgrid.net>
2017-04-29 12:36:38 +02:00
Julius Haertl
2b9b22f2f2 Add behat tests to drone
Signed-off-by: Julius Haertl <jus@bitgrid.net>
2017-04-29 11:57:54 +02:00
Julius Haertl
084d892ce6 Add basic behat test structure
Signed-off-by: Julius Haertl <jus@bitgrid.net>
2017-04-29 11:57:54 +02:00
Julius Härtl
11610f306a Merge pull request #58 from nextcloud/displaynames
Implement relational mapping for users and groups to show displaynames
2017-04-29 11:57:21 +02:00
Julius Härtl
60e9cf7088 Apply maxlength to input fields and fix card header height
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2017-04-29 11:55:00 +02:00
Julius Härtl
7e345256cb Move text fields to clob
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2017-04-29 11:53:20 +02:00
Julius Härtl
52a7b8d560 Merge pull request #97 from pixelipo/fix-readme
Change app name in README.md
2017-04-28 15:31:47 +02:00
Marin Treselj
d98db33a02 Change app name in README.md
Signed-off-by: Marin Treselj <marin@pixelipo.com>
2017-04-28 13:26:13 +02:00
Julius Härtl
04f5e2c2e1 Don't show own user account
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2017-04-27 13:14:58 +02:00
Julius Härtl
74c8a6e848 Fix board acl type
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2017-04-27 13:08:25 +02:00
Julius Härtl
02eecb3a3f Use mapper classes for relational data
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2017-04-27 13:08:25 +02:00
Julius Haertl
1e9c86e158 Code cleaup
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2017-04-27 13:08:25 +02:00
Julius Haertl
2b05227f4b Add Group class
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2017-04-27 13:08:10 +02:00
Julius Haertl
3e304a9ff2 Use display name for users and groups
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2017-04-27 13:08:10 +02:00
Julius Härtl
000e447801 Merge pull request #95 from nextcloud/add-stack-header
Moved "Add a new Stack" into the top Bar [rebased]
2017-04-27 13:01:24 +02:00
Julius Härtl
1b6531e748 Fix positioning on Nextcloud 12
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2017-04-27 12:48:25 +02:00
artemanufrij
e2012dd2c5 Fixed css
Signed-off-by: Artem Anufrij <artem.anufrij@live.de>
2017-04-27 12:25:56 +02:00
artemanufrij
5346cc4042 Move add stack input to the header
Signed-off-by: Artem Anufrij <artem.anufrij@live.de>
2017-04-27 12:25:17 +02:00
Julius Härtl
22438f8f3e Merge pull request #94 from nextcloud/use-ocs-api
Use OCS API to search for users/groups
2017-04-27 12:18:52 +02:00
Julius Härtl
6033baca23 Remove old search endpoint
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2017-04-26 14:54:56 +02:00
Julius Härtl
c1ff005710 Use OCS API to search for users/groups
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2017-04-26 14:42:11 +02:00
Nextcloud bot
05adda1e03 [tx-robot] updated from transifex 2017-04-26 00:10:41 +00:00
Lukas Reschke
6ae6bdf74a Update issue_template.md 2017-04-18 12:40:53 +02:00
Nextcloud bot
35ea7255cb [tx-robot] updated from transifex 2017-04-15 00:10:36 +00:00
Nextcloud bot
c80deaaaee [tx-robot] updated from transifex 2017-04-14 00:10:39 +00:00
Nextcloud bot
ab60f4e3ab [tx-robot] updated from transifex 2017-04-07 00:12:08 +00:00
Nextcloud bot
965708e784 [tx-robot] updated from transifex 2017-04-06 00:11:00 +00:00
Nextcloud bot
1b364b4f85 [tx-robot] updated from transifex 2017-04-05 00:11:20 +00:00
Nextcloud bot
aad1cb4fb5 [tx-robot] updated from transifex 2017-04-04 00:10:27 +00:00
Nextcloud bot
99725765fc [tx-robot] updated from transifex 2017-03-31 00:11:14 +00:00
Nextcloud bot
f313a9ba6b [tx-robot] updated from transifex 2017-03-30 00:10:28 +00:00
Julius Härtl
6bbe0a3161 Merge pull request #80 from nextcloud/drone-only-master
Only build master branch
2017-03-28 14:02:26 +02:00
Julius Härtl
4414fdebd4 Only build master branch
Signed-off-by: Julius Härtl <jus@bitgrid.net>
2017-03-28 13:49:19 +02:00
61 changed files with 1758 additions and 468 deletions

View File

@@ -125,10 +125,29 @@ pipeline:
- cd ../server/
- php occ app:enable deck
- cd apps/$APP_NAME
- make test
- phpunit -c tests/phpunit.xml --coverage-clover build/php-unit.coverage.xml
- phpunit -c tests/phpunit.integration.xml --coverage-clover build/php-integration.coverage.xml
when:
matrix:
TESTS: php7.1
integration:
image: nextcloudci/integration-php7.0:integration-php7.0-3
environment:
- APP_NAME=deck
- CORE_BRANCH=master
- DB=sqlite
commands:
# Pre-setup steps
- wget https://raw.githubusercontent.com/nextcloud/travis_ci/master/before_install.sh
- bash ./before_install.sh $APP_NAME $CORE_BRANCH $DB
- cd ../server/
- php occ app:enable deck
- cd apps/$APP_NAME
- cd tests/integration
- ./run.sh
when:
matrix:
TESTS: integration
jsbuild:
image: mhart/alpine-node:6.8.0
commands:
@@ -149,3 +168,6 @@ matrix:
- TESTS: php7.0
- TESTS: php7.1
- TESTS: jsbuild
- TESTS: integration
branches: [ master, stable* ]

2
.gitignore vendored
View File

@@ -2,3 +2,5 @@ js/node_modules/*
js/vendor/
build/
js/public/
tests/integration/vendor/
tests/integration/composer.lock

View File

@@ -26,7 +26,7 @@ before_script:
- cd apps/deck
script:
- make test
- make test-unit
after_failure:
- cat ../../data/nextcloud.log

View File

@@ -1,3 +1,19 @@
## 0.1.3 - 2017-05-01
### Added
- Icon to show if a card has a description
### Changed
- Use OCS API to get users/groups for sharing
- Various UI improvements
- Show display name instead of uid
- Fix bugs with limited field length
- Automatically hide sidebar when clicking the board view
- Start editing from everywhere in the description section
## 0.1.2
### Added
- Add translations

View File

@@ -23,7 +23,7 @@ clean-dist:
install-deps:
cd js && npm install --deps
cd js && bower install
cd js && ./node_modules/.bin/bower install
build: build-js
@@ -69,8 +69,9 @@ appstore: clean-build build
echo $(appstore_package_name).tar.gz
test: test-unit test-integration
test:
test-unit:
mkdir -p build/
ifeq (, $(shell which phpunit 2> /dev/null))
@echo "No phpunit command available, downloading a copy from the web"
@@ -83,6 +84,9 @@ else
phpunit -c tests/phpunit.integration.xml --coverage-clover build/php-integration.coverage.xml
endif
test-js:
cd js && run test
test-integration:
cd tests/integration && ./run.sh
test-js: install-deps
cd js && run test

View File

@@ -13,7 +13,7 @@ Deck is a kanban style organization tool aimed at personal planning and project
![Deck - Manage cards on your board](https://bitgrid.net/~jus/deck_1.png)
:boom: This is still alpha software: it may not be stable enough for production
:boom: This is still alpha software: it may not be stable enough for production
### Planned features
@@ -31,7 +31,7 @@ This app is supposed to work on Nextcloud version 11 or later.
You can download and install the latest release from the [Nextcloud app store](https://apps.nextcloud.com/apps/deck)
### Install from git
### Install from git
If you want to run the latest development version from git source, you need to clone the repo to your apps folder:
@@ -44,6 +44,10 @@ make
Please make sure you have installed the following dependencies: `make, which, tar, npm, curl`
### Install the nightly builds
Instead of setting everything up manually, you can just [download the nightly builds](https://download.bitgrid.net/nextcloud/deck/nightly/) instead. These builds are updated every 24 hours, and are pre-configured with all the needed dependencies.
## Developing
### PHP
@@ -59,9 +63,9 @@ Make sure you have installed the dependencies with ```make install-deps```.
You can use the provided Makefile to run all tests by using:
make test
## Contribution Guidelines
Please read the [Code of Conduct](https://nextcloud.com/community/code-of-conduct/). This document offers some guidance to ensure Nextcloud participants can cooperate effectively in a positive and inspiring atmosphere, and to explain how together we can strengthen and support each other.
@@ -73,7 +77,7 @@ For more information please review the [guidelines for contributing](https://git
All contributions to this repository are considered to be licensed under
the GNU AGPLv3 or any later version.
Contributors to the Spreed app retain their copyright. Therefore we recommend
Contributors to the Deck app retain their copyright. Therefore we recommend
to add following line to the header of a file, if you changed it substantially:
```

View File

@@ -19,7 +19,7 @@
<name>title</name>
<type>text</type>
<notnull>true</notnull>
<length>64</length>
<length>100</length>
</field>
<field>
<name>owner</name>
@@ -55,7 +55,7 @@
<name>title</name>
<type>text</type>
<notnull>true</notnull>
<length>64</length>
<length>100</length>
</field>
<field>
<name>board_id</name>
@@ -97,14 +97,13 @@
<field>
<name>title</name>
<type>text</type>
<length>100</length>
<notnull>true</notnull>
<length>64</length>
</field>
<field>
<name>description</name>
<type>text</type>
<type>clob</type>
<notnull>false</notnull>
<length>4096</length>
</field>
<field>
<name>stack_id</name>
@@ -187,7 +186,7 @@
<name>title</name>
<type>text</type>
<notnull>true</notnull>
<length>64</length>
<length>100</length>
</field>
<field>
<name>card_id</name>
@@ -239,7 +238,7 @@
<name>title</name>
<type>text</type>
<notnull>false</notnull>
<length>64</length>
<length>100</length>
</field>
<field>
<name>color</name>

View File

@@ -16,7 +16,7 @@
💥 This is still alpha software: it may not be stable enough for production!
</description>
<version>0.1.2</version>
<version>0.1.3</version>
<licence>agpl</licence>
<author>Julius Härtl</author>
<namespace>Deck</namespace>

View File

@@ -25,9 +25,6 @@ return [
'routes' => [
['name' => 'page#index', 'url' => '/', 'verb' => 'GET'],
// share
['name' => 'share#searchUser', 'url' => '/share/search/{search}', 'verb' => 'GET'],
// boards
['name' => 'board#index', 'url' => '/boards', 'verb' => 'GET'],
['name' => 'board#create', 'url' => '/boards', 'verb' => 'POST'],

View File

@@ -2,6 +2,7 @@
* @copyright Copyright (c) 2016 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
* @author Artem Anufrij <artem.anufrij@live.de>
*
* @license GNU AGPL version 3 or any later version
*
@@ -134,7 +135,6 @@ button:hover {
position: relative;
color: #888;
margin: 8px;
}
#board-actions .filter .filter-button {
@@ -176,7 +176,6 @@ button:hover {
.board-actions button {
border: none;
background-color: transparent;
color: #fff;
}
.board-action-button {
@@ -186,6 +185,28 @@ button:hover {
margin-left: 10px;
}
#stack-add {
float: right;
margin: 8px;
background-color: #ffffff;
border: 1px solid #bbb;
border-radius: 4px;
}
#stack-add input {
padding: 4px 5px;
margin: 0px;
border: 0px none transparent;
display: table-cell;
vertical-align: middle;
min-height: initial;
}
#stack-add button {
display: table-cell;
vertical-align: middle;
}
.stack {
width: 320px;
margin-right: 10px;
@@ -397,7 +418,6 @@ button:hover {
*/
#card-header {
height: 44px;
}
#card-header h2 {
@@ -415,7 +435,9 @@ button:hover {
}
#card-meta {
padding-top: 0;
height: 100%;
display: flex;
flex-direction: column;
}
#card-dates {
@@ -431,6 +453,12 @@ button:hover {
#card-dates span {
}
#card-description {
height: 100%;
display: flex;
flex-direction: column;
}
#card-description > h3 {
border-bottom: 1px solid #333333;
font-weight: 600;
@@ -438,14 +466,8 @@ button:hover {
padding: 5px;
}
.save-indicator {
background-color: #009900;
color: #ffffff;
border-radius: 3px;
float: right;
padding: 1px 10px;
font-size: 8pt !important;
display: none;
#card-description > div {
height: 100%;
}
#card-description textarea {
@@ -460,6 +482,16 @@ button:hover {
background-color: white;
}
.save-indicator {
background-color: #009900;
color: #ffffff;
border-radius: 3px;
float: right;
padding: 1px 10px;
font-size: 8pt !important;
display: none;
}
#sidebar-header,
.card-block {
padding: 15px;
@@ -485,6 +517,8 @@ button:hover {
border-left: none;
width: 500px;
border-left: 1px solid #eeeeee;
display:flex;
flex-direction: column;
}
#app-sidebar.details-visible {
@@ -866,3 +900,16 @@ button:hover {
.icon-details-white {
background-image: url('../img/details-white.svg');
}
.icon-description-marker {
position: absolute;
top: 2px;
right: 2px;
}
/**
* Hotfix for https://github.com/angular-ui/ui-select/issues/1652
*/
.ui-select-dropdown.select2-drop-active {
opacity: 1 !important;
}

View File

@@ -1,7 +1,6 @@
### Steps to reproduce
1.
2.
2.
3.
### Expected behaviour
@@ -11,6 +10,10 @@ Tell us what should happen
Tell us what happens instead
### Server configuration
<!--
You can use the Issue Template application to prefill most of the required information: https://apps.nextcloud.com/apps/issuetemplate
-->
**Operating system**:
**Web server:**
@@ -19,17 +22,15 @@ Tell us what happens instead
**PHP version:**
**Server version:** (see your admin page)
**Nextcloud version:** (see Nextcloud admin page)
**Deck version:** (see the apps page)
**Updated from an older installed version or fresh install:**
**Where did you install Nextcloud from:**
**Signing status:**
```
Login as admin user into your cloud and access
http://example.com/index.php/settings/integrity/failed
Login as admin user into your Nextcloud and access
http://example.com/index.php/settings/integrity/failed
paste the results here.
```
@@ -38,57 +39,34 @@ paste the results here.
```
If you have access to your command line run e.g.:
sudo -u www-data php occ app:list
from within your instance's installation folder
from within your Nextcloud installation folder
```
**The content of config/config.php:**
**Nextcloud configuration:**
```
If you have access to your command line run e.g.:
sudo -u www-data php occ config:list system
from within your instance's installation folder
from within your Nextcloud installation folder
or
or
Insert your config.php content here
(Without the database password, passwordsalt and secret)
Make sure to remove all sensitive content such as passwords. (e.g. database password, passwordsalt, secret, smtp password, …)
```
**Are you using external storage, if yes which one:** local/smb/sftp/...
**Are you using encryption:** yes/no
**Are you using an external user-backend, if yes which one:** LDAP/ActiveDirectory/Webdav/...
#### LDAP configuration (delete this part if not used)
```
With access to your command line run e.g.:
sudo -u www-data php occ ldap:show-config
from within your instance's installation folder
Without access to your command line download the data/owncloud.db to your local
computer or access your SQL server remotely and run the select query:
SELECT * FROM `oc_appconfig` WHERE `appid` = 'user_ldap';
Eventually replace sensitive data as the name/IP-address of your LDAP server or groups.
```
### Client configuration
**Browser:**
**Operating system:**
### Logs
#### Web server error log
```
Insert your webserver log here
```
#### Log file (data/nextcloud.log)
#### Nextcloud log (data/nextcloud.log)
```
Insert your nextcloud.log file here
Insert your Nextcloud log here
```
#### Browser log
@@ -96,6 +74,6 @@ Insert your nextcloud.log file here
Insert your browser log here, this could for example include:
a) The javascript console log
b) The network log
b) The network log
c) ...
```

View File

@@ -1,10 +1,10 @@
build:
grunt build
./node_modules/.bin/grunt build
watch:
grunt watch
./node_modules/.bin/grunt watch
install:
npm install
bower install
./node_modules/.bin/bower install

View File

@@ -7,8 +7,8 @@
"angular-mocks": "~1.6.1",
"angular-sanitize": "~1.6.1",
"angular-animate": "~1.6.1",
"ng-sortable": "1.3.6",
"jquery": "3.1.x",
"ng-sortable": "1.3.8",
"jquery": "3.2.x",
"es6-shim": "~0.*",
"js-url": "~2.*",
"angular-ui-select": "~0.19.6",

View File

@@ -119,12 +119,7 @@ app.controller('BoardController', function ($rootScope, $scope, $stateParams, St
$scope.statusservice.setError('Error occured', error);
});
BoardService.searchUsers('%25');
$scope.searchForUser = function (search) {
if (search == "") {
search = "%25";
}
BoardService.searchUsers(search);
};

View File

@@ -26,50 +26,101 @@ app.factory('BoardService', function(ApiService, $http, $q){
};
BoardService.prototype = angular.copy(ApiService.prototype);
BoardService.prototype.searchUsers = function(search) {
var url = OC.generateUrl('/apps/deck/share/search/'+search);
var deferred = $q.defer();
var self = this;
$http.get(url).then(function (response) {
BoardService.prototype.searchUsers = function (search) {
var deferred = $q.defer();
var self = this;
var searchData = {
format: 'json',
perPage: 4,
itemType: [0, 1]
};
if (search !== "") {
searchData.search = search;
}
$http({
method: 'GET',
url: OC.linkToOCS('apps/files_sharing/api/v1') + 'sharees',
params: searchData
})
.then(function (result) {
var response = result.data;
if (response.ocs.meta.statuscode !== 100) {
deferred.reject('Error while searching for sharees');
return;
}
self.sharees = [];
self.sharees = [];
// filter out everyone who is already in the share list
angular.forEach(response.data, function(item) {
var exists = false;
angular.forEach(self.getCurrent().acl, function(acl) {
if (acl.participant === item.participant) {
exists = true;
}
});
if(!exists) {
self.sharees.push(item);
}
});
var users = response.ocs.data.exact.users.concat(response.ocs.data.users);
var groups = response.ocs.data.exact.groups.concat(response.ocs.data.groups);
deferred.resolve(response.data);
}, function (error) {
deferred.reject('Error while update ' + self.endpoint);
});
return deferred.promise;
};
// filter out everyone who is already in the share list
angular.forEach(users, function (item) {
var acl = self.generateAcl('user', item);
var exists = false;
angular.forEach(self.getCurrent().acl, function (acl) {
if (acl.participant.primaryKey === item.value.shareWith) {
exists = true;
}
});
if (!exists && OC.getCurrentUser().uid !== item.value.shareWith) {
self.sharees.push(acl);
}
});
angular.forEach(groups, function (item) {
var acl = self.generateAcl('group', item);
var exists = false;
angular.forEach(self.getCurrent().acl, function (acl) {
if (acl.participant.primaryKey === item.value.shareWith) {
exists = true;
}
});
if (!exists) {
self.sharees.push(acl);
}
});
BoardService.prototype.addAcl = function(acl) {
var board = this.getCurrent();
var deferred = $q.defer();
var self = this;
var _acl = acl;
$http.post(this.baseUrl + '/' + acl.boardId + '/acl', _acl).then(function (response) {
if(!board.acl) {
board.acl = {};
}
board.acl[response.data.id] = response.data;
deferred.resolve(response.data);
}, function (error) {
deferred.reject('Error creating ACL ' + _acl);
});
acl = null;
return deferred.promise;
};
deferred.resolve(self.sharees);
}, function () {
deferred.reject('Error while searching for sharees');
});
return deferred.promise;
};
BoardService.prototype.generateAcl = function(type, ocsItem) {
return {
boardId: null,
id: null,
owner: false,
participant: {
primaryKey: ocsItem.value.shareWith,
uid: ocsItem.value.shareWith,
displayname: ocsItem.label
},
permissionEdit: true,
permissionManage: true,
permissionShare: true,
type: type
}
};
BoardService.prototype.addAcl = function (acl) {
var board = this.getCurrent();
var deferred = $q.defer();
var self = this;
var _acl = acl;
$http.post(this.baseUrl + '/' + acl.boardId + '/acl', _acl).then(function (response) {
if (!board.acl || board.acl.length === 0) {
board.acl = {};
}
board.acl[response.data.id] = response.data;
deferred.resolve(response.data);
}, function (error) {
deferred.reject('Error creating ACL ' + _acl);
});
acl = null;
return deferred.promise;
};
BoardService.prototype.deleteAcl = function(acl) {
var board = this.getCurrent();
@@ -105,7 +156,6 @@ app.factory('BoardService', function(ApiService, $http, $q){
var deferred = $q.defer();
$http.get(this.baseUrl + '/' + board.id + '/permissions').then(function (response) {
board.permissions = response.data;
console.log(board.permissions);
deferred.resolve(response.data);
}, function (error) {
deferred.reject('Error fetching board permissions ' + board);

26
l10n/ca.js Normal file
View File

@@ -0,0 +1,26 @@
OC.L10N.register(
"deck",
{
"Finished" : "Acabat",
"To review" : "Per revisar",
"Action needed" : "Acció necessaria",
"Later" : "Més tard",
"Archive" : "Arxiu",
"Delete" : "Esborra",
"Sharing" : "Compartir",
"Labels" : "Etiqueta",
"Share" : "Comparteix",
"Edit" : "Edita",
"Create a new label" : "Crea una nova etiqueta",
"Members" : "Membres",
"Modified:" : "Modificat:",
"Created:" : "Creat:",
"by" : "per",
"Saved" : "Desat",
"Comments" : "Comentaris",
"History" : "Història",
"Shared with you" : "Us han compartit",
"edit" : "edita",
"delete" : "esborra"
},
"nplurals=2; plural=(n != 1);");

24
l10n/ca.json Normal file
View File

@@ -0,0 +1,24 @@
{ "translations": {
"Finished" : "Acabat",
"To review" : "Per revisar",
"Action needed" : "Acció necessaria",
"Later" : "Més tard",
"Archive" : "Arxiu",
"Delete" : "Esborra",
"Sharing" : "Compartir",
"Labels" : "Etiqueta",
"Share" : "Comparteix",
"Edit" : "Edita",
"Create a new label" : "Crea una nova etiqueta",
"Members" : "Membres",
"Modified:" : "Modificat:",
"Created:" : "Creat:",
"by" : "per",
"Saved" : "Desat",
"Comments" : "Comentaris",
"History" : "Història",
"Shared with you" : "Us han compartit",
"edit" : "edita",
"delete" : "esborra"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
}

30
l10n/el.js Normal file
View File

@@ -0,0 +1,30 @@
OC.L10N.register(
"deck",
{
"Finished" : "Ολοκληρώθηκε",
"To review" : "Προς επισκόπηση",
"Action needed" : "Απαιτείται ενέργεια",
"Later" : "Αργότερα",
"Show archived cards" : "Εμφάνιση καρτελών που αρχειοθετήθηκαν",
"Hide archived cards" : "Απόκρυψη καρτελών που αρχειοθετήθηκαν",
"Archive" : "Αρχειοθέτηση",
"Unarchive" : "Αναίρεση αρχειοθέτησης",
"Delete" : "Διαγραφή",
"Labels" : "Ετικέτες",
"Share" : "Διαμοιρασμός",
"Edit" : "Επεξεργασία",
"Manage" : "Διαχείριση",
"Create a new label" : "Δημιουργία νέας ετικέτας",
"Members" : "Μέλη",
"Modified:" : "Τροποποιήθηκε:",
"Created:" : "Δημιουργήθηκε:",
"by" : "από",
"Saved" : "Αποθηκεύτηκε",
"Add a card description ..." : "Προσθήκη περιγραφής καρτέλας ...",
"Attachments" : "Συνημμένα",
"Comments" : "Σχόλια",
"History" : "Ιστορικό",
"edit" : "επεξεργασία",
"delete" : "διαγραφή"
},
"nplurals=2; plural=(n != 1);");

28
l10n/el.json Normal file
View File

@@ -0,0 +1,28 @@
{ "translations": {
"Finished" : "Ολοκληρώθηκε",
"To review" : "Προς επισκόπηση",
"Action needed" : "Απαιτείται ενέργεια",
"Later" : "Αργότερα",
"Show archived cards" : "Εμφάνιση καρτελών που αρχειοθετήθηκαν",
"Hide archived cards" : "Απόκρυψη καρτελών που αρχειοθετήθηκαν",
"Archive" : "Αρχειοθέτηση",
"Unarchive" : "Αναίρεση αρχειοθέτησης",
"Delete" : "Διαγραφή",
"Labels" : "Ετικέτες",
"Share" : "Διαμοιρασμός",
"Edit" : "Επεξεργασία",
"Manage" : "Διαχείριση",
"Create a new label" : "Δημιουργία νέας ετικέτας",
"Members" : "Μέλη",
"Modified:" : "Τροποποιήθηκε:",
"Created:" : "Δημιουργήθηκε:",
"by" : "από",
"Saved" : "Αποθηκεύτηκε",
"Add a card description ..." : "Προσθήκη περιγραφής καρτέλας ...",
"Attachments" : "Συνημμένα",
"Comments" : "Σχόλια",
"History" : "Ιστορικό",
"edit" : "επεξεργασία",
"delete" : "διαγραφή"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
}

44
l10n/es_MX.js Normal file
View File

@@ -0,0 +1,44 @@
OC.L10N.register(
"deck",
{
"Deck" : "Deck",
"Finished" : "Terminado",
"To review" : "Para revisar",
"Action needed" : "Acción requerida",
"Later" : "Después",
"Show archived cards" : "Mostrar tarjetas archivadas",
"Hide archived cards" : "Ocultar tarjetas archivadas",
"Board details" : "Detalles del tablero",
"Archive" : "Archivar",
"Unarchive" : "Desarchivar",
"Delete" : "Borrar",
"Enter a card title" : "Ingrese el títilo de la tarjeta",
"Sharing" : "Compartiendo",
"Labels" : "Etiquetas",
"Select users or groups to share with" : "Seleccione los usuarios o grupos con los cuales compartir",
"No matching user or group found." : "No se encontraron coincidencias de usuarios o grupos.",
"Share" : "Compartir",
"Edit" : "Editar",
"Manage" : "Administrar",
"Discard share" : "Descartar elemento compartido",
"Create a new label" : "Crear nueva etiqueta",
"Board title" : "Título del tablero",
"Members" : "Miembros",
"Create new board" : "Crear un nuevo tablero",
"New board title" : "Nuevo título de tablero",
"Modified:" : "Modificado:",
"Created:" : "Creado:",
"by" : "por",
"Saved" : "Guardado",
"Add a card description ..." : "Agregar una descripción de la tarjeta ...",
"Attachments" : "Adjuntos",
"Comments" : "Comentarios",
"History" : "Histórico",
"All Boards" : "Todos los Tablero",
"Shared with you" : "Compartido con usted",
"edit" : "editar",
"delete" : "borrar",
"remove share" : "eliminar elemento compartido",
"Create a new board" : "Crear nuevo tablero"
},
"nplurals=2; plural=(n != 1);");

42
l10n/es_MX.json Normal file
View File

@@ -0,0 +1,42 @@
{ "translations": {
"Deck" : "Deck",
"Finished" : "Terminado",
"To review" : "Para revisar",
"Action needed" : "Acción requerida",
"Later" : "Después",
"Show archived cards" : "Mostrar tarjetas archivadas",
"Hide archived cards" : "Ocultar tarjetas archivadas",
"Board details" : "Detalles del tablero",
"Archive" : "Archivar",
"Unarchive" : "Desarchivar",
"Delete" : "Borrar",
"Enter a card title" : "Ingrese el títilo de la tarjeta",
"Sharing" : "Compartiendo",
"Labels" : "Etiquetas",
"Select users or groups to share with" : "Seleccione los usuarios o grupos con los cuales compartir",
"No matching user or group found." : "No se encontraron coincidencias de usuarios o grupos.",
"Share" : "Compartir",
"Edit" : "Editar",
"Manage" : "Administrar",
"Discard share" : "Descartar elemento compartido",
"Create a new label" : "Crear nueva etiqueta",
"Board title" : "Título del tablero",
"Members" : "Miembros",
"Create new board" : "Crear un nuevo tablero",
"New board title" : "Nuevo título de tablero",
"Modified:" : "Modificado:",
"Created:" : "Creado:",
"by" : "por",
"Saved" : "Guardado",
"Add a card description ..." : "Agregar una descripción de la tarjeta ...",
"Attachments" : "Adjuntos",
"Comments" : "Comentarios",
"History" : "Histórico",
"All Boards" : "Todos los Tablero",
"Shared with you" : "Compartido con usted",
"edit" : "editar",
"delete" : "borrar",
"remove share" : "eliminar elemento compartido",
"Create a new board" : "Crear nuevo tablero"
},"pluralForm" :"nplurals=2; plural=(n != 1);"
}

View File

@@ -2,6 +2,7 @@ OC.L10N.register(
"deck",
{
"Finished" : "Kész",
"Action needed" : "Művelet szükséges",
"Later" : "Később",
"Delete" : "Törlés",
"Sharing" : "Megosztás",

View File

@@ -1,5 +1,6 @@
{ "translations": {
"Finished" : "Kész",
"Action needed" : "Művelet szükséges",
"Later" : "Később",
"Delete" : "Törlés",
"Sharing" : "Megosztás",

44
l10n/ko.js Normal file
View File

@@ -0,0 +1,44 @@
OC.L10N.register(
"deck",
{
"Deck" : "덱",
"Finished" : "완료됨",
"To review" : "리뷰할 항목",
"Action needed" : "동작 필요",
"Later" : "나중에",
"Show archived cards" : "보관된 카드 보기",
"Hide archived cards" : "보관된 카드 숨기기",
"Board details" : "게시판 정보",
"Archive" : "보관",
"Unarchive" : "보관 해제",
"Delete" : "삭제",
"Enter a card title" : "카드 제목 입력",
"Sharing" : "공유",
"Labels" : "이름표",
"Select users or groups to share with" : "공유할 사용자나 그룹 선택",
"No matching user or group found." : "일치하는 사용자나 그룹이 없습니다.",
"Share" : "공유",
"Edit" : "편집",
"Manage" : "관리",
"Discard share" : "공유 무시",
"Create a new label" : "새 이름표 만들기",
"Board title" : "게시판 제목",
"Members" : "구성원",
"Create new board" : "새 게시판 만들기",
"New board title" : "새 게시판 제목",
"Modified:" : "수정한 날짜:",
"Created:" : "만든 날짜:",
"by" : "by",
"Saved" : "저장됨",
"Add a card description ..." : "카드 설명 추가 ...",
"Attachments" : "첨부",
"Comments" : "댓글",
"History" : "과거 기록",
"All Boards" : "모든 게시판",
"Shared with you" : "내게 공유됨",
"edit" : "편집",
"delete" : "삭제",
"remove share" : "공유 삭제",
"Create a new board" : "새 게시판 만들기"
},
"nplurals=1; plural=0;");

42
l10n/ko.json Normal file
View File

@@ -0,0 +1,42 @@
{ "translations": {
"Deck" : "덱",
"Finished" : "완료됨",
"To review" : "리뷰할 항목",
"Action needed" : "동작 필요",
"Later" : "나중에",
"Show archived cards" : "보관된 카드 보기",
"Hide archived cards" : "보관된 카드 숨기기",
"Board details" : "게시판 정보",
"Archive" : "보관",
"Unarchive" : "보관 해제",
"Delete" : "삭제",
"Enter a card title" : "카드 제목 입력",
"Sharing" : "공유",
"Labels" : "이름표",
"Select users or groups to share with" : "공유할 사용자나 그룹 선택",
"No matching user or group found." : "일치하는 사용자나 그룹이 없습니다.",
"Share" : "공유",
"Edit" : "편집",
"Manage" : "관리",
"Discard share" : "공유 무시",
"Create a new label" : "새 이름표 만들기",
"Board title" : "게시판 제목",
"Members" : "구성원",
"Create new board" : "새 게시판 만들기",
"New board title" : "새 게시판 제목",
"Modified:" : "수정한 날짜:",
"Created:" : "만든 날짜:",
"by" : "by",
"Saved" : "저장됨",
"Add a card description ..." : "카드 설명 추가 ...",
"Attachments" : "첨부",
"Comments" : "댓글",
"History" : "과거 기록",
"All Boards" : "모든 게시판",
"Shared with you" : "내게 공유됨",
"edit" : "편집",
"delete" : "삭제",
"remove share" : "공유 삭제",
"Create a new board" : "새 게시판 만들기"
},"pluralForm" :"nplurals=1; plural=0;"
}

View File

@@ -12,10 +12,10 @@ OC.L10N.register(
"Archive" : "Arquivar",
"Unarchive" : "Desarquivar",
"Delete" : "Excluir",
"Enter a card title" : "Digite um título do cartão",
"Enter a card title" : "Digite um título de cartão",
"Sharing" : "Compartilhar",
"Labels" : "Etiquetas",
"Select users or groups to share with" : "Selecionar usuários ou grupos para compartilhar com",
"Select users or groups to share with" : "Selecionar usuários ou grupos para compartilhar",
"No matching user or group found." : "Uusuário ou grupo não encontrado.",
"Share" : "Compartilhar",
"Edit" : "Editar",
@@ -30,7 +30,7 @@ OC.L10N.register(
"Created:" : "Criado:",
"by" : "por",
"Saved" : "Salvo",
"Add a card description ..." : "Adicionar uma descrição de cartão",
"Add a card description ..." : "Adicionar uma descrição de cartão...",
"Attachments" : "Anexos",
"Comments" : "Comentários",
"History" : "Histórico",
@@ -38,7 +38,7 @@ OC.L10N.register(
"Shared with you" : "Compartilhado com você",
"edit" : "editar",
"delete" : "excluir",
"remove share" : "remover compartilhamento",
"remove share" : "excluir compartilhamento",
"Create a new board" : "Criar um novo quadro"
},
"nplurals=2; plural=(n > 1);");

View File

@@ -10,10 +10,10 @@
"Archive" : "Arquivar",
"Unarchive" : "Desarquivar",
"Delete" : "Excluir",
"Enter a card title" : "Digite um título do cartão",
"Enter a card title" : "Digite um título de cartão",
"Sharing" : "Compartilhar",
"Labels" : "Etiquetas",
"Select users or groups to share with" : "Selecionar usuários ou grupos para compartilhar com",
"Select users or groups to share with" : "Selecionar usuários ou grupos para compartilhar",
"No matching user or group found." : "Uusuário ou grupo não encontrado.",
"Share" : "Compartilhar",
"Edit" : "Editar",
@@ -28,7 +28,7 @@
"Created:" : "Criado:",
"by" : "por",
"Saved" : "Salvo",
"Add a card description ..." : "Adicionar uma descrição de cartão",
"Add a card description ..." : "Adicionar uma descrição de cartão...",
"Attachments" : "Anexos",
"Comments" : "Comentários",
"History" : "Histórico",
@@ -36,7 +36,7 @@
"Shared with you" : "Compartilhado com você",
"edit" : "editar",
"delete" : "excluir",
"remove share" : "remover compartilhamento",
"remove share" : "excluir compartilhamento",
"Create a new board" : "Criar um novo quadro"
},"pluralForm" :"nplurals=2; plural=(n > 1);"
}

View File

@@ -130,13 +130,13 @@ class BoardController extends Controller {
* @param $boardId
* @param $type
* @param $participant
* @param $edit
* @param $share
* @param $manage
* @param $permissionEdit
* @param $permissionShare
* @param $permissionManage
* @return \OCP\AppFramework\Db\Entity
*/
public function addAcl($boardId, $type, $participant, $edit, $share, $manage) {
return $this->boardService->addAcl($boardId, $type, $participant, $edit, $share, $manage);
public function addAcl($boardId, $type, $participant, $permissionEdit, $permissionShare, $permissionManage) {
return $this->boardService->addAcl($boardId, $type, $participant, $permissionEdit, $permissionShare, $permissionManage);
}
/**

View File

@@ -1,85 +0,0 @@
<?php
/**
* @copyright Copyright (c) 2016 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\BoardService;
use OCP\IGroupManager;
use OCP\IRequest;
use OCP\AppFramework\Controller;
use OCP\IUserManager;
class ShareController extends Controller {
private $userManager;
private $groupManager;
private $boardService;
private $userId;
public function __construct($appName, IRequest $request, IUserManager $userManager, IGroupManager $groupManager, BoardService $boardService, $userId) {
parent::__construct($appName, $request);
$this->userManager = $userManager;
$this->groupManager = $groupManager;
$this->userId = $userId;
$this->boardService = $boardService;
}
/**
* @NoAdminRequired
* @param $search
* @return array
*/
public function searchUser($search) {
$limit = 3;
$offset = null;
$result = [];
foreach ($this->groupManager->search($search, $limit, $offset) as $idx => $group) {
$acl = new Acl();
$acl->setType('group');
$acl->setParticipant($group->getGID());
$acl->setPermissionEdit(true);
$acl->setPermissionShare(true);
$acl->setPermissionManage(true);
$result[] = $acl;
}
$limit = 10;
foreach ($this->userManager->searchDisplayName($search, $limit, $offset) as $idx => $user) {
if ($user->getUID() === $this->userId) {
continue;
}
$acl = new Acl();
$acl->setType('user');
$acl->setParticipant($user->getUID());
$acl->setPermissionEdit(true);
$acl->setPermissionShare(true);
$acl->setPermissionManage(true);
$result[] = $acl;
}
return $result;
}
}

View File

@@ -48,12 +48,10 @@ class Acl extends RelationalEntity implements \JsonSerializable {
$this->addType('permissionEdit', 'boolean');
$this->addType('permissionShare', 'boolean');
$this->addType('permissionManage', 'boolean');
$this->addType('owner', 'boolean');
$this->addType('type', 'integer');
$this->addType('owner', 'boolean');
$this->addRelation('owner');
$this->setPermissionEdit(false);
$this->setPermissionShare(false);
$this->setPermissionManage(false);
$this->addResolvable('participant');
}
public function getPermission($permission) {
@@ -93,4 +91,5 @@ class Acl extends RelationalEntity implements \JsonSerializable {
$this->markFieldUpdated('type');
$this->type = $typeInt;
}
}

View File

@@ -25,7 +25,6 @@ namespace OCA\Deck\Db;
use OCP\IDBConnection;
class AclMapper extends DeckMapper implements IPermissionMapper {
public function __construct(IDBConnection $db) {

View File

@@ -32,8 +32,8 @@ class Board extends RelationalEntity implements JsonSerializable {
protected $owner;
protected $color;
protected $archived = false;
protected $labels;
protected $acl;
protected $labels = [];
protected $acl = [];
protected $shared;
public function __construct() {
@@ -43,6 +43,7 @@ class Board extends RelationalEntity implements JsonSerializable {
$this->addRelation('labels');
$this->addRelation('acl');
$this->addRelation('shared');
$this->addResolvable('owner');
$this->shared = -1;
}
@@ -51,6 +52,7 @@ class Board extends RelationalEntity implements JsonSerializable {
if ($this->shared === -1) {
unset($json['shared']);
}
$json['owner'] = $this->resolveOwner();
return $json;
}

View File

@@ -24,19 +24,31 @@
namespace OCA\Deck\Db;
use OCP\IDBConnection;
use OCP\IUserManager;
use OCP\IGroupManager;
class BoardMapper extends DeckMapper implements IPermissionMapper {
private $labelMapper;
private $aclMapper;
private $stackMapper;
private $userManager;
private $groupManager;
public function __construct(IDBConnection $db, LabelMapper $labelMapper, AclMapper $aclMapper, StackMapper $stackMapper) {
public function __construct(
IDBConnection $db,
LabelMapper $labelMapper,
AclMapper $aclMapper,
StackMapper $stackMapper,
IUserManager $userManager,
IGroupManager $groupManager
) {
parent::__construct($db, 'deck_boards', '\OCA\Deck\Db\Board');
$this->labelMapper = $labelMapper;
$this->aclMapper = $aclMapper;
$this->stackMapper = $stackMapper;
$this->userManager = $userManager;
$this->groupManager = $groupManager;
}
@@ -149,5 +161,29 @@ class BoardMapper extends DeckMapper implements IPermissionMapper {
return $id;
}
public function mapAcl(Acl &$acl) {
$userManager = $this->userManager;
$groupManager = $this->groupManager;
$acl->resolveRelation('participant', function($participant) use (&$acl, &$userManager, &$groupManager) {
if($acl->getType() === Acl::PERMISSION_TYPE_USER) {
return new User($userManager->get($acl->getParticipant($participant)));
}
if($acl->getType() === Acl::PERMISSION_TYPE_GROUP) {
return new Group($groupManager->get($acl->getParticipant($participant)));
}
throw new \Exception('Unknown permission type for mapping Acl');
});
}
/**
* @param Board $board
*/
public function mapOwner(Board &$board) {
$userManager = $this->userManager;
$board->resolveRelation('owner', function($owner) use (&$userManager) {
return new User($userManager->get($owner));
});
}
}

View File

@@ -48,6 +48,7 @@ class Card extends RelationalEntity implements JsonSerializable {
$this->addType('createdAt', 'integer');
$this->addType('archived', 'boolean');
$this->addRelation('labels');
$this->addResolvable('owner');
}
}

View File

@@ -25,15 +25,17 @@ namespace OCA\Deck\Db;
use OCP\AppFramework\Db\Entity;
use OCP\IDBConnection;
use OCP\IUserManager;
class CardMapper extends DeckMapper implements IPermissionMapper {
private $labelMapper;
public function __construct(IDBConnection $db, LabelMapper $labelMapper) {
public function __construct(IDBConnection $db, LabelMapper $labelMapper, IUserManager $userManager) {
parent::__construct($db, 'deck_cards', '\OCA\Deck\Db\Card');
$this->labelMapper = $labelMapper;
$this->userManager = $userManager;
}
public function insert(Entity $entity) {
@@ -57,6 +59,7 @@ class CardMapper extends DeckMapper implements IPermissionMapper {
$card = $this->findEntity($sql, [$id]);
$labels = $this->labelMapper->findAssignedLabelsForCard($card->id);
$card->setLabels($labels);
$this->mapOwner($card);
return $card;
}
@@ -125,5 +128,12 @@ class CardMapper extends DeckMapper implements IPermissionMapper {
return $row['id'];
}
public function mapOwner(Card &$card) {
$userManager = $this->userManager;
$card->resolveRelation('owner', function($owner) use (&$userManager) {
return new User($userManager->get($owner));
});
}
}

41
lib/Db/Group.php Normal file
View File

@@ -0,0 +1,41 @@
<?php
/**
* @copyright Copyright (c) 2017 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\IGroup;
class Group extends RelationalObject {
public function __construct(IGroup $group) {
$primaryKey = $group->getGID();
parent::__construct($primaryKey, $group);
}
public function getObjectSerialization() {
return [
'uid' => $this->object->getGID(),
'displayname' => $this->object->getDisplayName()
];
}
}

View File

@@ -1,6 +1,6 @@
<?php
/**
* @copyright Copyright (c) 2016 Julius Härtl <jus@bitgrid.net>
* @copyright Copyright (c) 2017 Julius Härtl <jus@bitgrid.net>
*
* @author Julius Härtl <jus@bitgrid.net>
*
@@ -23,20 +23,31 @@
namespace OCA\Deck\Db;
use OCP\AppFramework\Db\Entity;
class RelationalEntity extends \OCP\AppFramework\Db\Entity implements \JsonSerializable {
class RelationalEntity extends Entity implements \JsonSerializable {
private $_relations = array();
private $_resolvedProperties = [];
/**
* Mark a property as relation so it will not get updated using Mapper::update
* @param string $property Name of the property
* @param $property string Name of the property
*/
public function addRelation($property) {
if (!in_array($property, $this->_relations)) {
$this->_relations[] = $property;
}
}
/**
* Mark a property as resolvable via resolveRelation()
* @param $property string Name of the property
*/
public function addResolvable($property) {
$this->_resolvedProperties[$property] = null;
}
/**
* Mark am attribute as updated
* overwritten from \OCP\AppFramework\Db\Entity to avoid writing relational attributes
@@ -56,14 +67,75 @@ class RelationalEntity extends \OCP\AppFramework\Db\Entity implements \JsonSeria
$properties = get_object_vars($this);
$reflection = new \ReflectionClass($this);
$json = [];
foreach($properties as $property=>$value) {
if(substr($property, 0, 1) !== '_' && $reflection->hasProperty($property)) {
foreach ($properties as $property => $value) {
if (substr($property, 0, 1) !== '_' && $reflection->hasProperty($property)) {
$propertyReflection = $reflection->getProperty($property);
if(!$propertyReflection->isPrivate()) {
if (!$propertyReflection->isPrivate()) {
$json[$property] = $this->getter($property);
}
}
}
foreach ($this->_resolvedProperties as $property => $value) {
if($value !== null) {
$json[$property] = $value;
}
}
return $json;
}
/*
* Resolve relational data from external methods
*
* example usage:
*
* in Board::__construct()
* $this->addResolvable('owner')
*
* in BoardMapper
* $board->resolveRelation('owner', function($owner) use (&$userManager) {
* return new \OCA\Deck\Db\User($userManager->get($owner));
* });
*
* resolved values can be obtained by calling resolveProperty
* e.g. $board->resolveOwner()
*
* @param string $property name of the property
* @param callable $resolver anonymous function to resolve relational
* data defined by $property as unique identifier
* @throws \Exception
*/
public function resolveRelation($property, $resolver) {
$result = null;
if($property !== null && $this->$property !== null) {
$result = $resolver($this->$property);
}
if($result instanceof RelationalObject || $result === null) {
$this->_resolvedProperties[$property] = $result;
} else {
throw new \Exception('resolver must return an instance of RelationalObject');
}
}
public function __call($methodName, $args){
$attr = lcfirst( substr($methodName, 7) );
if(strpos($methodName, 'resolve') === 0 && array_key_exists($attr, $this->_resolvedProperties)) {
if($this->_resolvedProperties[$attr] !== null) {
return $this->_resolvedProperties[$attr];
} else {
return $this->getter($attr);
}
}
$attr = lcfirst( substr($methodName, 3) );
if(strpos($methodName, 'set') === 0 && array_key_exists($attr, $this->_resolvedProperties)) {
if(!is_scalar($args[0])) {
$args[0] = $args[0]['primaryKey'];
}
parent::setter($attr, $args);
return null;
}
return parent::__call($methodName, $args);
}
}

View File

@@ -0,0 +1,59 @@
<?php
/**
* @copyright Copyright (c) 2017 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 RelationalObject implements \JsonSerializable {
private $primaryKey;
/**
* RelationalObject constructor.
*
* @param $primaryKey string
* @param $object
*/
public function __construct($primaryKey, $object) {
$this->primaryKey = $primaryKey;
$this->object = $object;
}
public function jsonSerialize() {
return array_merge(
['primaryKey' => $this->primaryKey],
$this->getObjectSerialization()
);
}
/**
* This method should be overwritten if object doesn't implement \JsonSerializable
*/
public function getObjectSerialization() {
if($this->object instanceof \JsonSerializable) {
$this->object->jsonSerialize();
} else {
throw new \Exception('jsonSerialize is not implemented on ' . get_class($this));
}
}
}

41
lib/Db/User.php Normal file
View File

@@ -0,0 +1,41 @@
<?php
/**
* @copyright Copyright (c) 2017 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\IUser;
class User extends RelationalObject {
public function __construct(IUser $user) {
$primaryKey = $user->getUID();
parent::__construct($primaryKey, $user);
}
public function getObjectSerialization() {
return [
'uid' => $this->object->getUID(),
'displayname' => $this->object->getDisplayName()
];
}
}

View File

@@ -26,10 +26,7 @@ namespace OCA\Deck\Service;
use OCA\Deck\Db\Acl;
use OCA\Deck\Db\AclMapper;
use OCA\Deck\Db\Label;
use OCP\IL10N;
use OCA\Deck\Db\Board;
use OCA\Deck\Db\BoardMapper;
use OCA\Deck\Db\LabelMapper;
@@ -55,14 +52,36 @@ class BoardService {
$userBoards = $this->boardMapper->findAllByUser($userInfo['user']);
$groupBoards = $this->boardMapper->findAllByGroups($userInfo['user'], $userInfo['groups']);
$complete = array_merge($userBoards, $groupBoards);
return array_map("unserialize", array_unique(array_map("serialize", $complete)));
$result = [];
foreach($complete as &$item) {
if(!array_key_exists($item->getId(), $result)) {
$this->boardMapper->mapOwner($item);
if($item->getAcl() !== null) {
foreach ($item->getAcl() as &$acl) {
$this->boardMapper->mapAcl($acl);
}
}
$result[$item->getId()] = $item;
}
}
return array_values($result);
}
public function find($boardId) {
$this->permissionService->checkPermission($this->boardMapper, $boardId, Acl::PERMISSION_READ);
return $this->boardMapper->find($boardId, true, true);
/** @var Board $board */
$board = $this->boardMapper->find($boardId, true, true);
$this->boardMapper->mapOwner($board);
foreach ($board->getAcl() as &$acl) {
if($acl !== null) {
$this->boardMapper->mapAcl($acl);
}
}
return $board;
}
public function create($title, $userId, $color) {
$board = new Board();
$board->setTitle($title);
@@ -86,6 +105,7 @@ class BoardService {
$labels[] = $this->labelMapper->insert($label);
}
$new_board->setLabels($labels);
$this->boardMapper->mapOwner($new_board);
return $new_board;
}
@@ -100,6 +120,7 @@ class BoardService {
$board = $this->find($id);
$board->setTitle($title);
$board->setColor($color);
$this->boardMapper->mapOwner($board);
return $this->boardMapper->update($board);
}
@@ -113,21 +134,27 @@ class BoardService {
$acl->setPermissionEdit($edit);
$acl->setPermissionShare($share);
$acl->setPermissionManage($manage);
return $this->aclMapper->insert($acl);
$newAcl = $this->aclMapper->insert($acl);
$this->boardMapper->mapAcl($newAcl);
return $newAcl;
}
public function updateAcl($id, $edit, $share, $manage) {
$this->permissionService->checkPermission($this->aclMapper, $id, Acl::PERMISSION_SHARE);
/** @var Acl $acl */
$acl = $this->aclMapper->find($id);
$acl->setPermissionEdit($edit);
$acl->setPermissionShare($share);
$acl->setPermissionManage($manage);
$this->boardMapper->mapAcl($acl);
return $this->aclMapper->update($acl);
}
public function deleteAcl($id) {
$this->permissionService->checkPermission($this->aclMapper, $id, Acl::PERMISSION_SHARE);
/** @var Acl $acl */
$acl = $this->aclMapper->find($id);
$this->boardMapper->mapAcl($acl);
return $this->aclMapper->delete($acl);
}

View File

@@ -42,7 +42,8 @@ class CardService {
public function find($cardId) {
$this->permissionService->checkPermission($this->cardMapper, $cardId, Acl::PERMISSION_READ);
return $this->cardMapper->find($cardId);
$card = $this->cardMapper->find($cardId);
return $card;
}
/**

View File

@@ -9,14 +9,26 @@
<h1>
{{ boardservice.getCurrent().title }}
</h1>
<div id="board-actions">
<div class="board-action-button" ng-if="filter!='archive'"><a ng-click="switchFilter('archive')" style="opacity:0.5;" title="<?php p($l->t('Show archived cards')); ?>"><i class="icon icon-archive{{ boardservice.getCurrent().color | iconWhiteFilter }}"></i></a></div>
<div class="board-action-button" ng-if="filter=='archive'"><a ng-click="switchFilter('')" title="<?php p($l->t('Hide archived cards')); ?>"><i class="icon icon-archive{{ boardservice.getCurrent().color | iconWhiteFilter }}"></i></a></div>
<div class="board-action-button"><a ui-sref="board.detail({ id: id })" title="<?php p($l->t('Board details')); ?>"><i class="icon icon-details{{ boardservice.getCurrent().color | iconWhiteFilter }}"></i></a>
<div id="board-actions">
<div class="board-action-button" ng-if="filter!='archive'"><a ng-click="switchFilter('archive')" style="opacity:0.5;" title="<?php p($l->t('Show archived cards')); ?>"><i class="icon icon-archive{{ boardservice.getCurrent().color | iconWhiteFilter }}"></i></a></div>
<div class="board-action-button" ng-if="filter=='archive'"><a ng-click="switchFilter('')" title="<?php p($l->t('Hide archived cards')); ?>"><i class="icon icon-archive{{ boardservice.getCurrent().color | iconWhiteFilter }}"></i></a></div>
<div class="board-action-button"><a ui-sref="board.detail({ id: id })" title="<?php p($l->t('Board details')); ?>"><i class="icon icon-details{{ boardservice.getCurrent().color | iconWhiteFilter }}"></i></a>
</div>
</div>
</div>
<div id="board" class="scroll-container">
<div id="stack-add" ng-if="boardservice.canEdit() && checkCanEdit()">
<form class="ng-pristine ng-valid" ng-submit="createStack()">
<input type="text" placeholder="Add a new stack"
ng-focus="status.addStack=true"
ng-blur="status.addStack=false"
ng-model="newStack.title" required
maxlength="100" />
<button class="icon icon-add" style="opacity: {{status.addStack ? 1: 0.5}};"
type="submit"></button>
</form>
</div>
</div>
<div id="board" class="scroll-container" ng-click="sidebar.show=false" ui-sref="board">
<search on-search="search" class="ng-hide"></search>
@@ -29,7 +41,7 @@
<input type="text" placeholder="Add a new stack"
ng-blur="s.status.editStack=false" ng-model="s.title"
ng-if="s.status.editStack" autofocus-on-insert
required/>
required maxlength="100"/>
</form>
<div class="stack-actions">
<button class="icon icon-confirm" ng-if="s.status.editStack"
@@ -45,10 +57,12 @@
<li class="card as-sortable-item"
ng-repeat="c in s.cards"
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 }">
<div data-as-sortable-item-handle>
<div class="card-upper">
<i class="icon icon-filetype-text icon-description-marker" ng-if="c.description" title="{{ c.description }}"></i>
<h3>{{ c.title }}</h3>
<ul class="labels">
<li ng-repeat="label in c.labels"
@@ -103,6 +117,7 @@
ng-model="newCard.title"
ng-blur="status.addCard[s.id]=false"
style="color:{{ boardservice.getCurrent().color | textColorFilter }}; border-color:{{ boardservice.getCurrent().color | textColorFilter }};"
maxlength="100"
required placeholder="<?php p($l->t('Enter a card title')); ?>"/>
</h3>
</form>
@@ -111,18 +126,7 @@
</div>
</div>
</div>
<div class="stack" style="display: inline-block;" ng-if="boardservice.canEdit() && checkCanEdit()">
<form class="ng-pristine ng-valid" ng-submit="createStack()">
<h2>
<input type="text" placeholder="Add a new stack"
ng-focus="status.addStack=true"
ng-blur="status.addStack=false"
ng-model="newStack.title" required/>
<button class="icon icon-add" ng-show="status.addStack"
type="submit"></button>
</h2>
</form>
</div>
</div>
</div>
</div>

View File

@@ -17,12 +17,12 @@
<div class="tabsContainer">
<div id="commentsTabView" class="tab commentsTabView" ng-if="status.boardtab==0 || !status.boardtab">
<ui-select ng-if="boardservice.canShare()" ng-model="status.addSharee" theme="select2" style="width:100%;" title="Choose a user to assign" placeholder="Assign users ..." on-select="aclAdd(status.addSharee)">
<ui-select ng-if="boardservice.canShare()" ng-model="status.addSharee" theme="select2" style="width:100%;" title="Choose a user to assign" placeholder="Assign users ..." on-select="aclAdd(status.addSharee)" search-enabled="true">
<ui-select-match placeholder="<?php p($l->t('Select users or groups to share with')); ?>">
<span><i class="icon icon-{{$item.type}}"></i> {{ $item.participant }}</span>
<span><i class="icon icon-{{$item.type}}"></i> {{ $item.participant.displayname }}</span>
</ui-select-match>
<ui-select-choices refresh="searchForUser($select.search)" refresh-delay="0" repeat="sharee in boardservice.sharees">
<span><i class="icon icon-{{sharee.type}}"></i> {{ sharee.participant }}</span>
<span><i class="icon icon-{{sharee.type}}"></i> {{ sharee.participant.displayname }}</span>
</ui-select-choices>
<ui-select-no-choice>
<?php p($l->t('No matching user or group found.')); ?>
@@ -32,18 +32,18 @@
<ul id="shareWithList" class="shareWithList">
<li>
<span class="icon-loading-small" style="display:none;"></span>
<div class="avatardiv" avatar ng-attr-displayname="{{ boardservice.getCurrent().owner }}" ng-if="boardservice.id"></div>
<div class="avatardiv" avatar ng-attr-displayname="{{ boardservice.getCurrent().owner.uid }}" ng-if="boardservice.id"></div>
<span class="has-tooltip username">
{{ boardservice.getCurrent().owner }}
{{ boardservice.getCurrent().owner.displayname }}
</span>
</li>
<li ng-repeat="acl in boardservice.getCurrent().acl track by $index">
<span class="icon-loading-small" style="display:none;"></span>
<div class="avatardiv" avatar displayname="{{ acl.participant }}" ng-if="acl.type=='user'"></div>
<div class="avatardiv" avatar displayname="{{ acl.participant.uid }}" ng-if="acl.type=='user'"></div>
<div class="avatardiv" ng-if="acl.type=='group'"><i class="icon icon-{{acl.type}}"></i></div>
<span class="has-tooltip username">
{{ acl.participant }}
{{ acl.participant.displayname }}
</span>
<span class="sharingOptionsGroup">
<span class="shareOption" ng-if="boardservice.canManage()">
@@ -73,7 +73,7 @@
</span>
<span class="label-title" style="background-color:#{{label.color}}; color:{{ textColor(label.color) }}; width:178px;" ng-if="label.edit">
<form ng-submit="labelUpdate(label)">
<input type="text" ng-model="label.title" class="input-inline" style="background-color:#{{label.color}}; color:{{ label.color|textColorFilter }};" autofocus-on-insert />
<input type="text" ng-model="label.title" class="input-inline" style="background-color:#{{label.color}}; color:{{ label.color|textColorFilter }};" autofocus-on-insert maxlength="100"/>
</form>
</span>
<div class="colorselect" ng-if="label.edit">
@@ -87,7 +87,7 @@
<li ng-if="status.createLabel">
<form ng-submit="labelCreate(newLabel)">
<span class="label-title" style="background-color:#{{newLabel.color}}; color:{{ textColor(newLabel.color) }}; width:178px;">
<input type="text" class="input-inline" ng-model="newLabel.title" style="color:{{ newLabel.color|textColorFilter }};" autofocus-on-insert />
<input type="text" class="input-inline" ng-model="newLabel.title" style="color:{{ newLabel.color|textColorFilter }};" autofocus-on-insert maxlength="100" />
</span>
<div class="colorselect">
<div class="color" ng-repeat="c in defaultColors" style="background-color:#{{ c }};" ng-click="newLabel.color=c" ng-class="{'selected': (c == newLabel.color) }"><br /></div>

View File

@@ -17,11 +17,8 @@
<td><a href="#/board/{{b.id}}">{{ b.title }}</a></td>
<td>
<div id="assigned-users">
<div class="avatardiv" avatar
displayname="{{ b.owner }}"></div>
<div class="avatardiv" avatar
displayname="{{ acl.participant }}"
ng-repeat="acl in b.acl | limitTo: 7"></div>
<div class="avatardiv" avatar displayname="{{ b.owner.uid }}" title="{{ b.owner.displayname }}"></div>
<div class="avatardiv" avatar displayname="{{ acl.participant.uid }}" title="{{ acl.participant.uid }}" ng-repeat="acl in b.acl | limitTo: 7"></div>
</div>
</td>
</tr>
@@ -36,7 +33,7 @@
class="ng-pristine ng-valid" ng-submit="boardCreate()">
<input id="newTitle" class="edit ng-valid ng-empty"
type="text" placeholder="<?php p($l->t('New board title')); ?>"
autofocus-on-insert ng-model="newBoard.title">
autofocus-on-insert ng-model="newBoard.title" maxlength="100">
<div class="colorselect">
<div class="color" ng-repeat="c in colors"
style="background-color:#{{ c }};"

View File

@@ -13,7 +13,7 @@
<input class="input-inline" type="text" ng-if="status.cardRename"
ng-model="cardservice.getCurrent().title"
ng-blur="cardRename(cardservice.getCurrent())"
autofocus-on-insert required>
autofocus-on-insert required maxlength="100">
</form>
<div ng-click="cardRenameShow()" ng-show="!status.cardRename">
{{ cardservice.getCurrent().title }}
@@ -26,7 +26,7 @@
<?php p($l->t('Modified:')); ?> <span class="live-relative-timestamp" data-timestamp="{{cardservice.getCurrent().lastModified*1000}}">{{ cardservice.getCurrent().lastModified|relativeDateFilter }}</span>
<?php p($l->t('Created:')); ?> <span class="live-relative-timestamp" data-timestamp="{{cardservice.getCurrent().createdAt*1000}}">{{ cardservice.getCurrent().createdAt|relativeDateFilter }}</span>
<?php p($l->t('by')); ?>
<span>{{ cardservice.getCurrent().owner }}</span>
<span>{{ cardservice.getCurrent().owner.displayname }}</span>
</div>
<div id="labels">

View File

@@ -24,7 +24,7 @@
</div>
<div class="app-navigation-entry-edit" ng-show="b.status.edit">
<form ng-disabled="isAddingList" class="ng-pristine ng-valid" ng-submit="boardUpdate(b)">
<input id="newTitle" class="edit ng-valid ng-empty" type="text" autofocus-on-insert ng-model="b.title">
<input id="newTitle" class="edit ng-valid ng-empty" type="text" autofocus-on-insert ng-model="b.title" maxlength="100">
<input type="submit" value="" class="action icon-checkmark svg">
<div class="colorselect">
<div class="color" ng-repeat="c in colors" style="background-color:#{{ c }};" ng-click="b.color=c" ng-class="{'selected': (c == b.color) }"><br /></div>
@@ -39,7 +39,7 @@
</a>
<div class="app-navigation-entry-edit" ng-if="status.addBoard">
<form ng-disabled="isAddingList" class="ng-pristine ng-valid" ng-submit="boardCreate()">
<input id="newTitle" class="edit ng-valid ng-empty" type="text" placeholder="<?php p($l->t('Board title')); ?>" autofocus-on-insert ng-model="newBoard.title">
<input id="newTitle" class="edit ng-valid ng-empty" type="text" placeholder="<?php p($l->t('Board title')); ?>" autofocus-on-insert ng-model="newBoard.title" maxlength="100">
<input type="submit" value="" class="action icon-checkmark svg">
<div class="colorselect">
<div class="color" ng-repeat="c in colors" style="background-color:#{{ c }};" ng-click="selectColor(c)" ng-class="{'selected': (c == newBoard.color) }"><br /></div>

View File

@@ -0,0 +1,23 @@
{
"require-dev": {
"phpunit/phpunit": "~4.6",
"behat/behat": "^3.0",
"guzzlehttp/guzzle": "~5.0",
"jarnaiz/behat-junit-formatter": "^1.3",
"sabre/dav": "3.2"
},
"autoload": {
"files": [
"../../../../build/integration/features/bootstrap/Auth.php",
"../../../../build/integration/features/bootstrap/Provisioning.php",
"../../../../build/integration/features/bootstrap/Sharing.php",
"../../../../build/integration/features/bootstrap/Trashbin.php",
"../../../../build/integration/features/bootstrap/WebDav.php"
],
"psr-0": {
"": [
"features/bootstrap/"
]
}
}
}

View File

@@ -0,0 +1,12 @@
default:
suites:
test:
paths:
- %paths.base%/../features/
contexts:
- FeatureContext:
baseUrl: http://localhost:8080/index.php/ocs/
admin:
- admin
- admin
regular_user_password: 123456

View File

@@ -0,0 +1,36 @@
Feature: acl
Routes should check for permissions when a user sends a requests
Background:
Given user "admin" exists
And user "user0" exists
And user "user1" exists
And user "user2" exists
Given group "group0" exists
And group "group1" exists
Given user "user1" belongs to group "group1"
Scenario: Request the main frontend page
Given Logging in using web as "user0"
When Sending a "GET" to "/index.php/apps/deck" without requesttoken
Then the HTTP status code should be "200"
Scenario: Fetch the board list
Given Logging in using web as "user0"
When Sending a "GET" to "/index.php/apps/deck/boards" with requesttoken
Then the HTTP status code should be "200"
And the Content-Type should be "application/json; charset=utf-8"
Scenario: Fetch board details of owned board
Given Logging in using web as "admin"
And creates a board named "MyPrivateAdminBoard" with color "fafafa"
When "admin" fetches the board named "MyPrivateAdminBoard"
Then the HTTP status code should be "200"
And the Content-Type should be "application/json; charset=utf-8"
Scenario: Fetch board details of an other users board
Given Logging in using web as "admin"
And creates a board named "MyPrivateAdminBoard" with color "fafafa"
When "user0" fetches the board named "MyPrivateAdminBoard"
Then the HTTP status code should be "403"
And the Content-Type should be "application/json; charset=utf-8"

View File

@@ -0,0 +1,450 @@
<?php
/**
*
* @author Christoph Wurst <christoph@owncloud.com>
* @author Joas Schilling <coding@schilljs.com>
* @author Lukas Reschke <lukas@statuscode.ch>
* @author Sergio Bertolin <sbertolin@solidgear.es>
* @author Thomas Müller <thomas.mueller@tmit.eu>
*
* @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/>.
*
*/
use GuzzleHttp\Client;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Exception\ClientException;
use GuzzleHttp\Message\ResponseInterface;
require __DIR__ . '/../../vendor/autoload.php';
trait BasicStructure {
use Auth;
/** @var string */
private $currentUser = '';
/** @var string */
private $currentServer = '';
/** @var string */
private $baseUrl = '';
/** @var int */
private $apiVersion = 1;
/** @var ResponseInterface */
private $response = null;
/** @var CookieJar */
private $cookieJar;
/** @var string */
private $requestToken;
public function __construct($baseUrl, $admin, $regular_user_password) {
// Initialize your context here
$this->baseUrl = $baseUrl;
$this->adminUser = $admin;
$this->regularUser = $regular_user_password;
$this->localBaseUrl = $this->baseUrl;
$this->remoteBaseUrl = $this->baseUrl;
$this->currentServer = 'LOCAL';
$this->cookieJar = new CookieJar();
// in case of ci deployment we take the server url from the environment
$testServerUrl = getenv('TEST_SERVER_URL');
if ($testServerUrl !== false) {
$this->baseUrl = $testServerUrl;
$this->localBaseUrl = $testServerUrl;
}
// federated server url from the environment
$testRemoteServerUrl = getenv('TEST_SERVER_FED_URL');
if ($testRemoteServerUrl !== false) {
$this->remoteBaseUrl = $testRemoteServerUrl;
}
}
/**
* @Given /^using api version "(\d+)"$/
* @param string $version
*/
public function usingApiVersion($version) {
$this->apiVersion = (int) $version;
}
/**
* @Given /^As an "([^"]*)"$/
* @param string $user
*/
public function asAn($user) {
$this->currentUser = $user;
}
/**
* @Given /^Using server "(LOCAL|REMOTE)"$/
* @param string $server
* @return string Previous used server
*/
public function usingServer($server) {
$previousServer = $this->currentServer;
if ($server === 'LOCAL'){
$this->baseUrl = $this->localBaseUrl;
$this->currentServer = 'LOCAL';
return $previousServer;
} else {
$this->baseUrl = $this->remoteBaseUrl;
$this->currentServer = 'REMOTE';
return $previousServer;
}
}
/**
* @When /^sending "([^"]*)" to "([^"]*)"$/
* @param string $verb
* @param string $url
*/
public function sendingTo($verb, $url) {
$this->sendingToWith($verb, $url, null);
}
/**
* Parses the xml answer to get ocs response which doesn't match with
* http one in v1 of the api.
* @param ResponseInterface $response
* @return string
*/
public function getOCSResponse($response) {
return $response->xml()->meta[0]->statuscode;
}
/**
* This function is needed to use a vertical fashion in the gherkin tables.
* @param array $arrayOfArrays
* @return array
*/
public function simplifyArray($arrayOfArrays){
$a = array_map(function($subArray) { return $subArray[0]; }, $arrayOfArrays);
return $a;
}
/**
* @When /^sending "([^"]*)" to "([^"]*)" with$/
* @param string $verb
* @param string $url
* @param \Behat\Gherkin\Node\TableNode $body
*/
public function sendingToWith($verb, $url, $body) {
$fullUrl = $this->baseUrl . "v{$this->apiVersion}.php" . $url;
$client = new Client();
$options = [];
if ($this->currentUser === 'admin') {
$options['auth'] = $this->adminUser;
} else {
$options['auth'] = [$this->currentUser, $this->regularUser];
}
$options['headers'] = [
'OCS_APIREQUEST' => 'true'
];
if ($body instanceof \Behat\Gherkin\Node\TableNode) {
$fd = $body->getRowsHash();
$options['body'] = $fd;
}
// TODO: Fix this hack!
if ($verb === 'PUT' && $body === null) {
$options['body'] = [
'foo' => 'bar',
];
}
try {
$this->response = $client->send($client->createRequest($verb, $fullUrl, $options));
} catch (ClientException $ex) {
$this->response = $ex->getResponse();
}
}
/**
* @When /^sending "([^"]*)" with exact url to "([^"]*)"$/
* @param string $verb
* @param string $url
*/
public function sendingToDirectUrl($verb, $url) {
$this->sendingToWithDirectUrl($verb, $url, null);
}
public function sendingToWithDirectUrl($verb, $url, $body) {
$fullUrl = substr($this->baseUrl, 0, -5) . $url;
$client = new Client();
$options = [];
if ($this->currentUser === 'admin') {
$options['auth'] = $this->adminUser;
} else {
$options['auth'] = [$this->currentUser, $this->regularUser];
}
if ($body instanceof \Behat\Gherkin\Node\TableNode) {
$fd = $body->getRowsHash();
$options['body'] = $fd;
}
try {
$this->response = $client->send($client->createRequest($verb, $fullUrl, $options));
} catch (ClientException $ex) {
$this->response = $ex->getResponse();
}
}
public function isExpectedUrl($possibleUrl, $finalPart){
$baseUrlChopped = substr($this->baseUrl, 0, -4);
$endCharacter = strlen($baseUrlChopped) + strlen($finalPart);
return (substr($possibleUrl,0,$endCharacter) == "$baseUrlChopped" . "$finalPart");
}
/**
* @Then /^the OCS status code should be "([^"]*)"$/
* @param int $statusCode
*/
public function theOCSStatusCodeShouldBe($statusCode) {
PHPUnit_Framework_Assert::assertEquals($statusCode, $this->getOCSResponse($this->response));
}
/**
* @Then /^the HTTP status code should be "([^"]*)"$/
* @param int $statusCode
*/
public function theHTTPStatusCodeShouldBe($statusCode) {
PHPUnit_Framework_Assert::assertEquals($statusCode, $this->response->getStatusCode());
}
/**
* @Then /^the Content-Type should be "([^"]*)"$/
* @param string $contentType
*/
public function theContentTypeShouldbe($contentType) {
PHPUnit_Framework_Assert::assertEquals($contentType, $this->response->getHeader('Content-Type'));
}
/**
* @param ResponseInterface $response
*/
private function extracRequestTokenFromResponse(ResponseInterface $response) {
$this->requestToken = substr(preg_replace('/(.*)data-requesttoken="(.*)">(.*)/sm', '\2', $response->getBody()->getContents()), 0, 89);
}
/**
* @Given Logging in using web as :user
* @param string $user
*/
public function loggingInUsingWebAs($user) {
$loginUrl = substr($this->baseUrl, 0, -5) . '/login';
// Request a new session and extract CSRF token
$client = new Client();
$response = $client->get(
$loginUrl,
[
'cookies' => $this->cookieJar,
]
);
$this->extracRequestTokenFromResponse($response);
// Login and extract new token
$password = ($user === 'admin') ? 'admin' : '123456';
$client = new Client();
$response = $client->post(
$loginUrl,
[
'body' => [
'user' => $user,
'password' => $password,
'requesttoken' => $this->requestToken,
],
'cookies' => $this->cookieJar,
]
);
$this->extracRequestTokenFromResponse($response);
}
/**
* @When Sending a :method to :url with requesttoken
* @param string $method
* @param string $url
*/
public function sendingAToWithRequesttoken($method, $url) {
$baseUrl = substr($this->baseUrl, 0, -5);
$client = new Client();
$request = $client->createRequest(
$method,
$baseUrl . $url,
[
'cookies' => $this->cookieJar,
]
);
$request->addHeader('requesttoken', $this->requestToken);
try {
$this->response = $client->send($request);
} catch (ClientException $e) {
$this->response = $e->getResponse();
}
}
/**
* @When Sending a :method to :url without requesttoken
* @param string $method
* @param string $url
*/
public function sendingAToWithoutRequesttoken($method, $url) {
$baseUrl = substr($this->baseUrl, 0, -5);
$client = new Client();
$request = $client->createRequest(
$method,
$baseUrl . $url,
[
'cookies' => $this->cookieJar,
]
);
try {
$this->response = $client->send($request);
} catch (ClientException $e) {
$this->response = $e->getResponse();
}
}
public static function removeFile($path, $filename){
if (file_exists("$path" . "$filename")) {
unlink("$path" . "$filename");
}
}
/**
* @Given User :user modifies text of :filename with text :text
* @param string $user
* @param string $filename
* @param string $text
*/
public function modifyTextOfFile($user, $filename, $text) {
self::removeFile("../../data/$user/files", "$filename");
file_put_contents("../../data/$user/files" . "$filename", "$text");
}
public function createFileSpecificSize($name, $size) {
$file = fopen("work/" . "$name", 'w');
fseek($file, $size - 1 ,SEEK_CUR);
fwrite($file,'a'); // write a dummy char at SIZE position
fclose($file);
}
public function createFileWithText($name, $text){
$file = fopen("work/" . "$name", 'w');
fwrite($file, $text);
fclose($file);
}
/**
* @Given file :filename of size :size is created in local storage
* @param string $filename
* @param string $size
*/
public function fileIsCreatedInLocalStorageWithSize($filename, $size) {
$this->createFileSpecificSize("local_storage/$filename", $size);
}
/**
* @Given file :filename with text :text is created in local storage
* @param string $filename
* @param string $text
*/
public function fileIsCreatedInLocalStorageWithText($filename, $text) {
$this->createFileWithText("local_storage/$filename", $text);
}
/**
* @When Sleep for :seconds seconds
* @param int $seconds
*/
public function sleepForSeconds($seconds) {
sleep((int)$seconds);
}
/**
* @BeforeSuite
*/
public static function addFilesToSkeleton(){
for ($i=0; $i<5; $i++){
file_put_contents("../../core/skeleton/" . "textfile" . "$i" . ".txt", "Nextcloud test text file\n");
}
if (!file_exists("../../core/skeleton/FOLDER")) {
mkdir("../../core/skeleton/FOLDER", 0777, true);
}
if (!file_exists("../../core/skeleton/PARENT")) {
mkdir("../../core/skeleton/PARENT", 0777, true);
}
file_put_contents("../../core/skeleton/PARENT/" . "parent.txt", "Nextcloud test text file\n");
if (!file_exists("../../core/skeleton/PARENT/CHILD")) {
mkdir("../../core/skeleton/PARENT/CHILD", 0777, true);
}
file_put_contents("../../core/skeleton/PARENT/CHILD/" . "child.txt", "Nextcloud test text file\n");
}
/**
* @AfterSuite
*/
public static function removeFilesFromSkeleton(){
for ($i=0; $i<5; $i++){
self::removeFile("../../core/skeleton/", "textfile" . "$i" . ".txt");
}
if (is_dir("../../core/skeleton/FOLDER")) {
rmdir("../../core/skeleton/FOLDER");
}
self::removeFile("../../core/skeleton/PARENT/CHILD/", "child.txt");
if (is_dir("../../core/skeleton/PARENT/CHILD")) {
rmdir("../../core/skeleton/PARENT/CHILD");
}
self::removeFile("../../core/skeleton/PARENT/", "parent.txt");
if (is_dir("../../core/skeleton/PARENT")) {
rmdir("../../core/skeleton/PARENT");
}
}
/**
* @BeforeScenario @local_storage
*/
public static function removeFilesFromLocalStorageBefore(){
$dir = "./work/local_storage/";
$di = new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS);
$ri = new RecursiveIteratorIterator($di, RecursiveIteratorIterator::CHILD_FIRST);
foreach ( $ri as $file ) {
$file->isDir() ? rmdir($file) : unlink($file);
}
}
/**
* @AfterScenario @local_storage
*/
public static function removeFilesFromLocalStorageAfter(){
$dir = "./work/local_storage/";
$di = new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS);
$ri = new RecursiveIteratorIterator($di, RecursiveIteratorIterator::CHILD_FIRST);
foreach ( $ri as $file ) {
$file->isDir() ? rmdir($file) : unlink($file);
}
}
}

View File

@@ -0,0 +1,194 @@
<?php
use Behat\Behat\Context\Context;
use Behat\Gherkin\Node\TableNode;
use GuzzleHttp\Client;
use Behat\Behat\Tester\Exception\PendingException;
use Behat\Gherkin\Node\PyStringNode;
use GuzzleHttp\Exception\ClientException;
require_once __DIR__ . '/../../vendor/autoload.php';
class FeatureContext implements Context {
use WebDav;
/** @var string */
private $mappedUserId;
private $lastInsertIds = array();
/**
* @BeforeSuite
*/
public static function addFilesToSkeleton() {
}
/**
* @When :user requests the deck list
*/
/**
* @When Sending a :method to :url with JSON
*/
public function sendingAToWithJSON($method, $url, \Behat\Gherkin\Node\PyStringNode $data) {
$baseUrl = substr($this->baseUrl, 0, -5);
$client = new Client;
$request = $client->createRequest(
$method,
$baseUrl . $url,
[
'cookies' => $this->cookieJar,
'json' => json_decode($data)
]
);
$request->addHeader('requesttoken', $this->requestToken);
try {
$this->response = $client->send($request);
} catch (ClientException $e) {
$this->response = $e->getResponse();
}
}
/**
* @When :user creates a new deck with name :name
*/
public function createsANewDeckWithName($user, $content) {
$client = new GuzzleHttp\Client();
$this->response = $client->post(
'http://localhost:8080/index.php/apps/deck/boards',
[
'form_params' => [
'name' => $name,
],
'auth' => [
$this->mappedUserId,
'test',
],
]
);
}
/**
* @Then the response should have a status code :code
* @param string $code
* @throws InvalidArgumentException
*/
public function theResponseShouldHaveAStatusCode($code) {
$currentCode = $this->response->getStatusCode();
if ($currentCode !== (int)$code) {
throw new InvalidArgumentException(
sprintf(
'Expected %s as code got %s',
$code,
$currentCode
)
);
}
}
/**
* @Then the response should be a JSON array with the following mandatory values
* @param TableNode $table
* @throws InvalidArgumentException
*/
public function theResponseShouldBeAJsonArrayWithTheFollowingMandatoryValues(TableNode $table) {
$expectedValues = $table->getColumnsHash();
$realResponseArray = json_decode($this->response->getBody()->getContents(), true);
foreach ($expectedValues as $value) {
if ((string)$realResponseArray[$value['key']] !== (string)$value['value']) {
throw new InvalidArgumentException(
sprintf(
'Expected %s for key %s got %s',
(string)$value['value'],
$value['key'],
(string)$realResponseArray[$value['key']]
)
);
}
}
}
/**
* @Then the response should be a JSON array with a length of :length
* @param int $length
* @throws InvalidArgumentException
*/
public function theResponseShouldBeAJsonArrayWithALengthOf($length) {
$realResponseArray = json_decode($this->response->getBody()->getContents(), true);
PHPUnit_Framework_Assert::assertEquals($realResponseArray, "foo");
if((int)count($realResponseArray) !== (int)$length) {
throw new InvalidArgumentException(
sprintf(
'Expected %d as length got %d',
$length,
count($realResponseArray)
)
);
}
}
/**
* @Then /^I should get:$/
*
* @param PyStringNode $string
* @throws \Exception
*/
public function iShouldGet(PyStringNode $string)
{
if ((string) $string !== trim($this->cliOutput)) {
throw new Exception(sprintf(
'Expected "%s" but received "%s".',
$string,
$this->cliOutput
));
}
return;
}
private function sendJSONrequest($method, $url, $data) {
$baseUrl = substr($this->baseUrl, 0, -5);
$client = new Client;
$request = $client->createRequest(
$method,
$baseUrl . $url,
[
'cookies' => $this->cookieJar,
'json' => $data
]
);
$request->addHeader('requesttoken', $this->requestToken);
try {
$this->response = $client->send($request);
} catch (ClientException $e) {
$this->response = $e->getResponse();
}
}
/**
* @Given /^creates a board named "([^"]*)" with color "([^"]*)"$/
*/
public function createsABoardNamedWithColor($title, $color) {
$this->sendJSONrequest('POST', '/index.php/apps/deck/boards', [
'title' => $title,
'color' => $color
]
);
$response = json_decode($this->response->getBody()->getContents(), true);
$this->lastInsertIds[$title] = $response['id'];
}
/**
* @When /^"([^"]*)" fetches the board named "([^"]*)"$/
*/
public function fetchesTheBoardNamed($user, $boardName) {
$this->loggingInUsingWebAs($user);
$id = $this->lastInsertIds[$boardName];
$this->sendJSONrequest('GET', '/index.php/apps/deck/boards/'.$id, []);
}
}

View File

@@ -0,0 +1,37 @@
Feature: decks
Background:
Given user "user0" exists
Scenario: Request the main frontend page
Given Logging in using web as "admin"
When Sending a "GET" to "/index.php/apps/deck" without requesttoken
Then the HTTP status code should be "200"
Scenario: Fetch the board list
Given Logging in using web as "admin"
When Sending a "GET" to "/index.php/apps/deck/boards" with requesttoken
Then the HTTP status code should be "200"
And the Content-Type should be "application/json; charset=utf-8"
Scenario: Fetch board details of a nonexisting board
Given Logging in using web as "admin"
When Sending a "GET" to "/index.php/apps/deck/boards/13379" with requesttoken
Then the HTTP status code should be "403"
And the Content-Type should be "application/json; charset=utf-8"
Scenario: Create a new board
Given Logging in using web as "admin"
When Sending a "POST" to "/index.php/apps/deck/boards" with JSON
"""
{
"title": "MyBoard",
"color": "000000"
}
"""
Then the HTTP status code should be "200"
And the Content-Type should be "application/json; charset=utf-8"
And the response should be a JSON array with the following mandatory values
|key|value|
|title|MyBoard|
|color|000000|

45
tests/integration/run.sh Executable file
View File

@@ -0,0 +1,45 @@
#!/usr/bin/env bash
OC_PATH=../../../../
OCC=${OC_PATH}occ
SCENARIO_TO_RUN=$1
HIDE_OC_LOGS=$2
# Nextcloud integration tests composer
(
cd ${OC_PATH}build/integration
composer install
)
INSTALLED=$($OCC status | grep installed: | cut -d " " -f 5)
if [ "$INSTALLED" == "true" ]; then
$OCC app:enable deck
else
if [ "$SCENARIO_TO_RUN" != "setup_features/setup.feature" ]; then
echo "Nextcloud instance needs to be installed" >&2
exit 1
fi
fi
composer install
composer dump-autoload
# avoid port collision on jenkins - use $EXECUTOR_NUMBER
if [ -z "$EXECUTOR_NUMBER" ]; then
EXECUTOR_NUMBER=0
fi
PORT=$((8080 + $EXECUTOR_NUMBER))
echo $PORT
php -S localhost:$PORT -t $OC_PATH &
PHPPID=$!
echo $PHPPID
export TEST_SERVER_URL="http://localhost:$PORT/ocs/"
vendor/bin/behat
RESULT=$?
kill $PHPPID
echo "runsh: Exit code: $RESULT"
exit $RESULT

View File

@@ -1,7 +1,10 @@
<phpunit bootstrap="bootstrap.php" colors="true">
<testsuites>
<testsuite name="integration">
<directory>./integration</directory>
<testsuite name="integration-database">
<directory>./integration/database</directory>
</testsuite>
<testsuite name="integration-app">
<directory>./integration/app</directory>
</testsuite>
</testsuites>
<filter>

View File

@@ -23,6 +23,8 @@
namespace OCA\Deck\Db;
use OCP\IGroupManager;
use OCP\IUserManager;
use Test\AppFramework\Db\MapperTestUtility;
/**
@@ -33,6 +35,8 @@ class AclMapperTest extends MapperTestUtility {
private $dbConnection;
private $aclMapper;
private $boardMapper;
private $userManager;
private $groupManager;
// Data
private $acls;
@@ -43,11 +47,16 @@ class AclMapperTest extends MapperTestUtility {
$this->dbConnection = \OC::$server->getDatabaseConnection();
$this->aclMapper = new AclMapper($this->dbConnection);
$this->userManager = $this->createMock(IUserManager::class);
$this->groupManager = $this->createMock(IGroupManager::class);
$this->boardMapper = new BoardMapper(
$this->dbConnection,
\OC::$server->query(LabelMapper::class),
$this->aclMapper,
\OC::$server->query(StackMapper::class));
\OC::$server->query(StackMapper::class),
$this->userManager,
$this->groupManager
);
$this->boards = [
$this->boardMapper->insert($this->getBoard('MyBoard 1', 'user1')),

View File

@@ -23,7 +23,8 @@
namespace OCA\Deck\Db;
class AclTest extends \PHPUnit_Framework_TestCase {
class AclTest extends \Test\TestCase {
private function createAclUser() {
$acl = new Acl();
$acl->setId(1);
@@ -35,6 +36,7 @@ class AclTest extends \PHPUnit_Framework_TestCase {
$acl->setPermissionManage(true);
return $acl;
}
private function createAclGroup() {
$acl = new Acl();
$acl->setId(1);
@@ -46,6 +48,7 @@ class AclTest extends \PHPUnit_Framework_TestCase {
$acl->setPermissionManage(true);
return $acl;
}
public function testJsonSerialize() {
$acl = $this->createAclUser();
$this->assertEquals([
@@ -70,6 +73,7 @@ class AclTest extends \PHPUnit_Framework_TestCase {
'owner' => false
], $acl->jsonSerialize());
}
public function testSetOwner() {
$acl = $this->createAclUser();
$acl->setOwner(1);

View File

@@ -23,6 +23,8 @@
namespace OCA\Deck\Db;
use OCP\IGroupManager;
use OCP\IUserManager;
use Test\AppFramework\Db\MapperTestUtility;
/**
@@ -33,6 +35,8 @@ class BoardMapperTest extends MapperTestUtility {
private $dbConnection;
private $aclMapper;
private $boardMapper;
private $userManager;
private $groupManager;
// Data
private $acls;
@@ -41,12 +45,17 @@ class BoardMapperTest extends MapperTestUtility {
public function setup(){
parent::setUp();
$this->userManager = $this->createMock(IUserManager::class);
$this->groupManager = $this->createMock(IGroupManager::class);
$this->dbConnection = \OC::$server->getDatabaseConnection();
$this->boardMapper = new BoardMapper(
$this->dbConnection,
\OC::$server->query(LabelMapper::class),
\OC::$server->query(AclMapper::class),
\OC::$server->query(StackMapper::class)
\OC::$server->query(StackMapper::class),
$this->userManager,
$this->groupManager
);
$this->aclMapper = \OC::$server->query(AclMapper::class);
$this->labelMapper = \OC::$server->query(LabelMapper::class);

View File

@@ -20,8 +20,8 @@ class BoardTest extends \PHPUnit_Framework_TestCase {
'title' => "My Board",
'owner' => "admin",
'color' => "000000",
'labels' => null,
'acl' => null,
'labels' => array(),
'acl' => array(),
'archived' => false
], $board->jsonSerialize());
}
@@ -35,7 +35,7 @@ class BoardTest extends \PHPUnit_Framework_TestCase {
'owner' => "admin",
'color' => "000000",
'labels' => array("foo", "bar"),
'acl' => null,
'acl' => array(),
'archived' => false
], $board->jsonSerialize());
}
@@ -55,8 +55,8 @@ class BoardTest extends \PHPUnit_Framework_TestCase {
'title' => "My Board",
'owner' => "admin",
'color' => "000000",
'labels' => null,
'acl' => null,
'labels' => array(),
'acl' => array(),
'archived' => false,
'shared' => 1,
], $board->jsonSerialize());

View File

@@ -29,34 +29,35 @@ use OCA\Deck\Db\AclMapper;
use OCA\Deck\Db\Board;
use OCA\Deck\Db\BoardMapper;
use OCA\Deck\Db\LabelMapper;
use OCA\Deck\Db\User;
use OCP\IGroupManager;
use OCP\ILogger;
use OCP\IUser;
use OCP\IUserManager;
class BoardServiceTest extends \PHPUnit_Framework_TestCase {
class BoardServiceTest extends \Test\TestCase {
/** @var BoardService */
private $service;
private $logger;
/** @var L10N */
private $l10n;
/** @var LabelMapper */
private $labelMapper;
/** @var AclMapper */
private $aclMapper;
/** @var BoardMapper */
private $boardMapper;
private $groupManager;
/** @var PermissionService */
private $permissionService;
private $userId = 'admin';
public function setUp() {
$this->l10n = $this->getMockBuilder(L10N::class)
->disableOriginalConstructor()
->getMock();
$this->aclMapper = $this->getMockBuilder(AclMapper::class)
->disableOriginalConstructor()->getMock();
$this->boardMapper = $this->getMockBuilder(BoardMapper::class)
->disableOriginalConstructor()->getMock();
$this->labelMapper = $this->getMockBuilder(LabelMapper::class)
->disableOriginalConstructor()->getMock();
$this->permissionService = $this->getMockBuilder(PermissionService::class)
->disableOriginalConstructor()->getMock();
$this->l10n = $this->createMock(L10N::class);
$this->aclMapper = $this->createMock(AclMapper::class);
$this->boardMapper = $this->createMock(BoardMapper::class);
$this->labelMapper = $this->createMock(LabelMapper::class);
$this->permissionService = $this->createMock(PermissionService::class);
$this->service = new BoardService(
$this->boardMapper,
@@ -65,32 +66,43 @@ class BoardServiceTest extends \PHPUnit_Framework_TestCase {
$this->aclMapper,
$this->permissionService
);
$user = $this->createMock(IUser::class);
$user->method('getUID')->willReturn('admin');
}
public function testFindAll() {
$b1 = new Board();
$b1->setId(1);
$b2 = new Board();
$b2->setId(2);
$b3 = new Board();
$b3->setId(3);
$this->boardMapper->expects($this->once())
->method('findAllByUser')
->with('admin')
->willReturn([1,2,3,6,7]);
->willReturn([$b1, $b2]);
$this->boardMapper->expects($this->once())
->method('findAllByGroups')
->with('admin', ['a', 'b', 'c'])
->willReturn([4,5,6,7,8]);
->willReturn([$b2, $b3]);
$userinfo = [
'user' => 'admin',
'groups' => ['a', 'b', 'c']
];
$result = $this->service->findAll($userinfo);
sort($result);
$this->assertEquals([1,2,3,4,5,6,7,8], $result);
$this->assertEquals([$b1, $b2, $b3], $result);
}
public function testFind() {
$b1 = new Board();
$b1->setId(1);
$this->boardMapper->expects($this->once())
->method('find')
->with(123)
->willReturn(1);
$this->assertEquals(1, $this->service->find(123));
->with(1)
->willReturn($b1);
$this->assertEquals($b1, $this->service->find(1));
}
public function testCreate() {
@@ -130,9 +142,11 @@ class BoardServiceTest extends \PHPUnit_Framework_TestCase {
}
public function testDelete() {
$board = new Board();
$board->setOwner('admin');
$this->boardMapper->expects($this->once())
->method('find')
->willReturn(new Board());
->willReturn($board);
$this->boardMapper->expects($this->once())
->method('delete')
->willReturn(1);
@@ -140,6 +154,8 @@ class BoardServiceTest extends \PHPUnit_Framework_TestCase {
}
public function testAddAcl() {
$user = $this->createMock(IUser::class);
$user->method('getUID')->willReturn('admin');
$acl = new Acl();
$acl->setBoardId(123);
$acl->setType('user');
@@ -147,6 +163,9 @@ class BoardServiceTest extends \PHPUnit_Framework_TestCase {
$acl->setPermissionEdit(true);
$acl->setPermissionShare(true);
$acl->setPermissionManage(true);
$acl->resolveRelation('participant', function($participant) use (&$user) {
return null;
});
$this->aclMapper->expects($this->once())
->method('insert')
->with($acl)

View File

@@ -1,151 +0,0 @@
<?php
/**
* @copyright Copyright (c) 2016 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 OCP\IGroup;
use OCP\IUser;
class ShareControllerTest extends \PHPUnit_Framework_TestCase {
private $controller;
private $request;
private $userManager;
private $groupManager;
private $boardService;
private $permissionService;
private $userId = 'user';
public function setUp() {
$this->l10n = $this->request = $this->getMockBuilder(
'\OCP\IL10n')
->disableOriginalConstructor()
->getMock();
$this->request = $this->getMockBuilder(
'\OCP\IRequest')
->disableOriginalConstructor()
->getMock();
$this->userManager = $this->getMockBuilder(
'\OCP\IUserManager')
->disableOriginalConstructor()
->getMock();
$this->groupManager = $this->getMockBuilder(
'\OCP\IGroupManager')
->disableOriginalConstructor()
->getMock();
$this->boardService = $this->getMockBuilder(
'\OCA\Deck\Service\BoardService')
->disableOriginalConstructor()
->getMock();
$this->groupManager->method('getUserGroupIds')
->willReturn(['admin', 'group1', 'group2']);
$this->userManager->method('get')
->with($this->userId)
->willReturn('user');
$this->controller = new ShareController(
'deck',
$this->request,
$this->userManager,
$this->groupManager,
$this->boardService,
$this->userId
);
}
public function testSearchGroup() {
$group = $this->getMockBuilder(IGroup::class)
->disableOriginalConstructor()
->getMock();
$group->expects($this->once())
->method('getGID')
->willReturn('foo');
$groups = [$group];
$this->groupManager->expects($this->once())
->method('search')
->with('foo')
->willReturn($groups);
$this->userManager->expects($this->once())
->method('searchDisplayName')
->willReturn([]);
$actual = $this->controller->searchUser('foo');
$acl = new Acl();
$acl->setType('group');
$acl->setParticipant('foo');
$acl->setPermissionEdit(true);
$acl->setPermissionShare(true);
$acl->setPermissionManage(true);
$this->assertEquals([$acl], $actual);
}
public function testSearchUser() {
$user = $this->getMockBuilder(IUser::class)
->disableOriginalConstructor()
->getMock();
$user->expects($this->any())
->method('getUID')
->willReturn('foo');
$users = [$user];
$this->groupManager->expects($this->once())
->method('search')
->willReturn([]);
$this->userManager->expects($this->once())
->method('searchDisplayName')
->with('foo')
->willReturn($users);
$actual = $this->controller->searchUser('foo');
$acl = new Acl();
$acl->setType('user');
$acl->setParticipant('foo');
$acl->setPermissionEdit(true);
$acl->setPermissionShare(true);
$acl->setPermissionManage(true);
$this->assertEquals([$acl], $actual);
}
public function testSearchUserExcludeOwn() {
$user = $this->getMockBuilder(IUser::class)
->disableOriginalConstructor()
->getMock();
$user->expects($this->any())
->method('getUID')
->willReturn('user');
$users = [$user];
$this->groupManager->expects($this->once())
->method('search')
->willReturn([]);
$this->userManager->expects($this->once())
->method('searchDisplayName')
->with('user')
->willReturn($users);
$actual = $this->controller->searchUser('user');
$this->assertEquals([], $actual);
}
}