Compare commits
487 Commits
folderCont
...
v8.0.6beta
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7cd4425b71 | ||
|
|
9036a46145 | ||
|
|
d64a6115f0 | ||
|
|
2b0de142ab | ||
|
|
535e4296b5 | ||
|
|
558f1ec1ec | ||
|
|
f13ff381ae | ||
|
|
96b252195b | ||
|
|
ad6a321266 | ||
|
|
28ce93aa02 | ||
|
|
7e7e2f2099 | ||
|
|
f01ade72cd | ||
|
|
65e33be707 | ||
|
|
c81823bb2d | ||
|
|
3fe4f53a1f | ||
|
|
affe4d7a38 | ||
|
|
3561a1572b | ||
|
|
e47bf07b5e | ||
|
|
97b3a82581 | ||
|
|
751092d6dc | ||
|
|
f97b21da11 | ||
|
|
8b0b6924f5 | ||
|
|
97ac85164e | ||
|
|
e9262dbe2f | ||
|
|
4ec2e6c357 | ||
|
|
a24b6a352a | ||
|
|
c9e77c7de4 | ||
|
|
ecaad05c63 | ||
|
|
b94657a554 | ||
|
|
e0bf1e4f6e | ||
|
|
3bd5f5eb4c | ||
|
|
918ba60aae | ||
|
|
0a1b90929a | ||
|
|
229635d65a | ||
|
|
6029b41760 | ||
|
|
62fa899fd2 | ||
|
|
a20487907b | ||
|
|
0d9ad14165 | ||
|
|
bbd6f42c70 | ||
|
|
3906951e91 | ||
|
|
43be73b9b4 | ||
|
|
78dc8ca702 | ||
|
|
7ec91b9177 | ||
|
|
9eaa716667 | ||
|
|
40ab639013 | ||
|
|
f8e6800cd4 | ||
|
|
5785378951 | ||
|
|
57789e15a9 | ||
|
|
a9a5ac2fd1 | ||
|
|
4e85a426a4 | ||
|
|
8a1cfa4229 | ||
|
|
747cc45e9c | ||
|
|
adf1b2b5f3 | ||
|
|
eb76504df6 | ||
|
|
698274491a | ||
|
|
8538aabfb5 | ||
|
|
4cbf10f392 | ||
|
|
5b1b844867 | ||
|
|
f3cddc9ec2 | ||
|
|
59bf55ca7b | ||
|
|
4e467c0b3c | ||
|
|
b828d2885d | ||
|
|
cc0b1e4bf6 | ||
|
|
1c36875429 | ||
|
|
5e555f18b1 | ||
|
|
f4e0b344c4 | ||
|
|
d6733e9ca7 | ||
|
|
c000eb479b | ||
|
|
6bd4bff834 | ||
|
|
dc0713941e | ||
|
|
6aeee4fbc8 | ||
|
|
abf9f79185 | ||
|
|
1744333f5f | ||
|
|
1bdd3f7dc3 | ||
|
|
6464f6b25b | ||
|
|
4191844af5 | ||
|
|
b3d2232028 | ||
|
|
74ce46a8c8 | ||
|
|
a6b8902fd9 | ||
|
|
850e0750b9 | ||
|
|
7bd7056a4c | ||
|
|
9dd746489a | ||
|
|
48c17d2745 | ||
|
|
ac8b748b88 | ||
|
|
c9fcc429e4 | ||
|
|
855ae3f5d2 | ||
|
|
7759a9118c | ||
|
|
2972585ba1 | ||
|
|
0f7f5bdb34 | ||
|
|
dead1acba8 | ||
|
|
6ea7e89649 | ||
|
|
af4e04602a | ||
|
|
36e8162111 | ||
|
|
ad3f9dbb74 | ||
|
|
64d502d602 | ||
|
|
75c7554f62 | ||
|
|
083356fe9d | ||
|
|
14c760124f | ||
|
|
c391f74c2e | ||
|
|
952e774a0e | ||
|
|
000f3a5f26 | ||
|
|
7dabbf9340 | ||
|
|
1a8aae7282 | ||
|
|
cb18bdd771 | ||
|
|
1754c9a989 | ||
|
|
2c118c3938 | ||
|
|
8cd359a182 | ||
|
|
28c6c2f4d4 | ||
|
|
70bcb1a1c1 | ||
|
|
4c4c12d905 | ||
|
|
b2548b01df | ||
|
|
0c2ce1b495 | ||
|
|
a040a5b08e | ||
|
|
eeace88beb | ||
|
|
5fcab24e59 | ||
|
|
5865a3af8c | ||
|
|
783188d683 | ||
|
|
7213be29c9 | ||
|
|
0b38c13891 | ||
|
|
fe1de11b23 | ||
|
|
f81ba63a52 | ||
|
|
ae89153701 | ||
|
|
533bb85a85 | ||
|
|
bffcbe4895 | ||
|
|
d1f99f1003 | ||
|
|
e56fb7e368 | ||
|
|
1854c7f590 | ||
|
|
8c6e5907d1 | ||
|
|
30bc7a7418 | ||
|
|
1fb44bc7a7 | ||
|
|
72e576e529 | ||
|
|
7b5493ea21 | ||
|
|
6867d45ffc | ||
|
|
a035198f4b | ||
|
|
960ca7b1a8 | ||
|
|
c08c4684be | ||
|
|
43b7ac6ad2 | ||
|
|
52d09386d4 | ||
|
|
2c56909604 | ||
|
|
2396ddf9e9 | ||
|
|
bf69107c25 | ||
|
|
080b837bc8 | ||
|
|
b2e9f800a3 | ||
|
|
8b635cd7b7 | ||
|
|
2d24ad53c6 | ||
|
|
cd311c1ac1 | ||
|
|
f9e258437f | ||
|
|
d5d3cbd50d | ||
|
|
777a6c454e | ||
|
|
e1492d9e0b | ||
|
|
a3b53a7738 | ||
|
|
94f7b3e25c | ||
|
|
609da64c3b | ||
|
|
6eb8bdb5c9 | ||
|
|
d24d30f96b | ||
|
|
3bc312ef9a | ||
|
|
68b2a85ed0 | ||
|
|
71a88d5f1f | ||
|
|
0c231f5ac6 | ||
|
|
7015dd74ce | ||
|
|
80afbd3e4f | ||
|
|
488c4a92da | ||
|
|
524045cdbd | ||
|
|
bb79a6b4f0 | ||
|
|
4e44d2ecd6 | ||
|
|
23b8277c27 | ||
|
|
1d35c01b19 | ||
|
|
a5466fc909 | ||
|
|
15c8501483 | ||
|
|
8c4322ef5a | ||
|
|
6fa4ee8e86 | ||
|
|
485d415ed1 | ||
|
|
62e7696aed | ||
|
|
a97dae9065 | ||
|
|
f50fc14935 | ||
|
|
dbaaae071b | ||
|
|
21c4331983 | ||
|
|
b2903485c0 | ||
|
|
df9de41767 | ||
|
|
3be30babf8 | ||
|
|
30ed561110 | ||
|
|
714ca7a3a2 | ||
|
|
7c0c34f682 | ||
|
|
eded1f05fc | ||
|
|
3f4eba1acb | ||
|
|
8e59d4c64b | ||
|
|
ecf8343905 | ||
|
|
5733878d66 | ||
|
|
45a36feec6 | ||
|
|
92ae7c284b | ||
|
|
f3a6adff41 | ||
|
|
927eb763c3 | ||
|
|
2e0c3665b0 | ||
|
|
b1a9e26c45 | ||
|
|
3dbe7e195c | ||
|
|
5b5bf27653 | ||
|
|
c501ca9ba4 | ||
|
|
739e5f9fd6 | ||
|
|
67ee02574f | ||
|
|
6a6acd2a46 | ||
|
|
a2af0aae5e | ||
|
|
1aeb4c0e8c | ||
|
|
66f1495538 | ||
|
|
053ac11273 | ||
|
|
5230854dc8 | ||
|
|
c668a7c665 | ||
|
|
8d51e8eb72 | ||
|
|
b453cccf51 | ||
|
|
5be2aa4426 | ||
|
|
84287feb08 | ||
|
|
a4961afa7e | ||
|
|
aacfe93fa2 | ||
|
|
f87d492118 | ||
|
|
337c541c0c | ||
|
|
d32a5d5e8c | ||
|
|
ad2738f4f8 | ||
|
|
ba46469202 | ||
|
|
e296709815 | ||
|
|
f6df1546f4 | ||
|
|
2e694edba2 | ||
|
|
3f2069bd66 | ||
|
|
0fb207a926 | ||
|
|
3b7d285c0a | ||
|
|
15f54c263e | ||
|
|
1f1e6f2e49 | ||
|
|
92febbf574 | ||
|
|
69ae83eca7 | ||
|
|
ac062bd0aa | ||
|
|
8939d1307f | ||
|
|
2eced9ea99 | ||
|
|
3fe909305d | ||
|
|
f98a624532 | ||
|
|
fed8ff8504 | ||
|
|
01328b4622 | ||
|
|
0f314ce455 | ||
|
|
273719f620 | ||
|
|
6e041088b8 | ||
|
|
da23ab0dd6 | ||
|
|
b7ab280d8e | ||
|
|
76eadfa8dc | ||
|
|
d3761e02f5 | ||
|
|
68d37afa3b | ||
|
|
823f2d205d | ||
|
|
80461daa28 | ||
|
|
1e60931119 | ||
|
|
9dac69b21b | ||
|
|
e4f0892831 | ||
|
|
105b285874 | ||
|
|
adfc8c79c4 | ||
|
|
06ab387509 | ||
|
|
7a3b4b464e | ||
|
|
486e7e5ed1 | ||
|
|
f914be3e07 | ||
|
|
3f6db4c1ab | ||
|
|
628bb3fb3e | ||
|
|
dfd302178a | ||
|
|
351a1ccaad | ||
|
|
3d45d0bd1d | ||
|
|
142694d095 | ||
|
|
94ada409bb | ||
|
|
8357a253b3 | ||
|
|
ea647ede73 | ||
|
|
4905a4cd4a | ||
|
|
48082931d1 | ||
|
|
e94152b226 | ||
|
|
b1688311c8 | ||
|
|
86d3f85379 | ||
|
|
c1f52508a5 | ||
|
|
03473c51c2 | ||
|
|
c8c8d61431 | ||
|
|
ddcb4e1b94 | ||
|
|
5e3203348e | ||
|
|
a538f14b4a | ||
|
|
baa2e4f995 | ||
|
|
ae59df8492 | ||
|
|
a64c3fb3a2 | ||
|
|
5964cd3751 | ||
|
|
893cbc917d | ||
|
|
48ceaa9502 | ||
|
|
944b301837 | ||
|
|
29c56fbfb2 | ||
|
|
3b69354d19 | ||
|
|
5ee843cd59 | ||
|
|
93ae742ad8 | ||
|
|
cd47e72e88 | ||
|
|
b896be821f | ||
|
|
e90ead2a8d | ||
|
|
bc7676089c | ||
|
|
3232dc76d4 | ||
|
|
c0bcaa4980 | ||
|
|
e12c76ed66 | ||
|
|
722faee4c6 | ||
|
|
d04ad4be26 | ||
|
|
ba9446a1b8 | ||
|
|
9bc1f0a67a | ||
|
|
f7f14708a2 | ||
|
|
78c348381d | ||
|
|
4c92aafc19 | ||
|
|
e4d8dc7825 | ||
|
|
6e26b117b9 | ||
|
|
4149fc40c2 | ||
|
|
e2ea175ea2 | ||
|
|
b960fa83ef | ||
|
|
31de51eacc | ||
|
|
bda7b5c446 | ||
|
|
58ad3fac06 | ||
|
|
88a180fadb | ||
|
|
09631f691b | ||
|
|
95d81c36ff | ||
|
|
625cbc63a4 | ||
|
|
5720211f70 | ||
|
|
9d0ea7fa11 | ||
|
|
02f00c9980 | ||
|
|
4774d648bd | ||
|
|
3d7ed01135 | ||
|
|
c28356fffc | ||
|
|
3dad31d6df | ||
|
|
c9bafe5c7a | ||
|
|
637503a1ac | ||
|
|
9ad48e0fe9 | ||
|
|
97a65e153d | ||
|
|
5ad226cedb | ||
|
|
6843f1690f | ||
|
|
04809b6037 | ||
|
|
5538c27322 | ||
|
|
35abb4d71e | ||
|
|
bc5ca78816 | ||
|
|
f15d41e185 | ||
|
|
27df0a1735 | ||
|
|
171974e43c | ||
|
|
03ef085a4c | ||
|
|
d9f6971d0b | ||
|
|
b081bb24e5 | ||
|
|
aa63a16f25 | ||
|
|
27990b5360 | ||
|
|
9383856ca5 | ||
|
|
78b2c8b0be | ||
|
|
4cfa4ecc28 | ||
|
|
92a024b2fd | ||
|
|
a1c414c197 | ||
|
|
e8ee079aa7 | ||
|
|
769f666663 | ||
|
|
c6136de551 | ||
|
|
4bf3d2907d | ||
|
|
1f1078120c | ||
|
|
096ccb7a7b | ||
|
|
e416b469de | ||
|
|
228d4087ac | ||
|
|
b95405e09a | ||
|
|
8a5ef62b60 | ||
|
|
f59b286a59 | ||
|
|
7e87cdab4a | ||
|
|
5effc4a53a | ||
|
|
1540d8fad1 | ||
|
|
81ae4b3687 | ||
|
|
79f827cf40 | ||
|
|
8781608ca7 | ||
|
|
5c10c05c1f | ||
|
|
9b573ee8c9 | ||
|
|
5d53314e8f | ||
|
|
625bb3c4d5 | ||
|
|
1374f25a0b | ||
|
|
ed954bd941 | ||
|
|
a03d39b1ad | ||
|
|
2bfd03e483 | ||
|
|
c100d90793 | ||
|
|
8db687a1cd | ||
|
|
5def2c0998 | ||
|
|
10d0f0d9b3 | ||
|
|
c477e24034 | ||
|
|
36bed7c2f6 | ||
|
|
860c59a347 | ||
|
|
f6ceb0b0d5 | ||
|
|
e5aefc8bda | ||
|
|
ba1748bd80 | ||
|
|
6d5e60bd9b | ||
|
|
a7fc0fc07b | ||
|
|
c032b94b77 | ||
|
|
8961967fec | ||
|
|
c0cfd2ddbb | ||
|
|
00d0120e22 | ||
|
|
3672ec39dd | ||
|
|
2f6eaa3832 | ||
|
|
8f23742ca6 | ||
|
|
c0a4affe00 | ||
|
|
efe635f0d5 | ||
|
|
fa64ba356a | ||
|
|
eb2ac86c5d | ||
|
|
598c4fdcae | ||
|
|
fe9e2e9945 | ||
|
|
716e0e6645 | ||
|
|
266a655107 | ||
|
|
2e85c1fb9b | ||
|
|
8c529adf85 | ||
|
|
fb8569499c | ||
|
|
1d9d0f653d | ||
|
|
846fdecbcc | ||
|
|
95cfc4185a | ||
|
|
c78b5453ff | ||
|
|
75cae3b252 | ||
|
|
a796021143 | ||
|
|
83dd98426c | ||
|
|
0ec73a58e9 | ||
|
|
d29234382f | ||
|
|
411cd5b2d5 | ||
|
|
6d8d4ea546 | ||
|
|
9f6a640e73 | ||
|
|
810ac0fca7 | ||
|
|
c77fd2eef6 | ||
|
|
3356abe5c7 | ||
|
|
5faf9f8192 | ||
|
|
c8d61ddad8 | ||
|
|
80e3337bad | ||
|
|
17635053ab | ||
|
|
e82f30caae | ||
|
|
ca7acd8461 | ||
|
|
c0550ed953 | ||
|
|
b0a6a54651 | ||
|
|
b057ae2da1 | ||
|
|
e277a9b3dd | ||
|
|
5c7157e0af | ||
|
|
1188c74fe5 | ||
|
|
06742fe24b | ||
|
|
9e38e2c1a9 | ||
|
|
2e361618a3 | ||
|
|
b63a6a4fc4 | ||
|
|
bc14181563 | ||
|
|
c6c888c8a1 | ||
|
|
ced104c206 | ||
|
|
5fef637f87 | ||
|
|
2ed3c7af27 | ||
|
|
673c8a7531 | ||
|
|
089ad7c242 | ||
|
|
ee83fa673e | ||
|
|
033635c9fb | ||
|
|
3c51f5ff38 | ||
|
|
62029c3541 | ||
|
|
9735fbb0f4 | ||
|
|
c8f55ae5dd | ||
|
|
9e4be2909c | ||
|
|
d8b676f485 | ||
|
|
08e1ae11d5 | ||
|
|
88f62bf4ea | ||
|
|
e1985647d5 | ||
|
|
8e55425c93 | ||
|
|
42c6963acc | ||
|
|
cb3060c940 | ||
|
|
cd75ac2e2c | ||
|
|
968dc81a74 | ||
|
|
fc4bb1ae88 | ||
|
|
f4f5097b00 | ||
|
|
f558ee5802 | ||
|
|
2740bdc1d9 | ||
|
|
1378c17b55 | ||
|
|
033873c6cc | ||
|
|
159d1e4ce7 | ||
|
|
5642f1b97a | ||
|
|
b25bfb2796 | ||
|
|
4b8d1e8dbd | ||
|
|
95aa9a8890 | ||
|
|
05e56711f3 | ||
|
|
5dd63bdd04 | ||
|
|
8bd2f2eb23 | ||
|
|
676c91d4a3 | ||
|
|
a719f022fb | ||
|
|
47ceebbfc5 | ||
|
|
ae89229815 | ||
|
|
2885a84373 | ||
|
|
def1ffad23 | ||
|
|
948ee0e398 | ||
|
|
4e9fd632ea | ||
|
|
cc243e1296 | ||
|
|
d6c7f6f413 | ||
|
|
662ebc6c80 | ||
|
|
4f1f68ead8 | ||
|
|
ab456bed98 | ||
|
|
4825a4ca1e | ||
|
|
36cb41d15f | ||
|
|
61eaf0e832 | ||
|
|
e95d274f17 | ||
|
|
1740fb236e | ||
|
|
de0c16789e | ||
|
|
f99ca64adc | ||
|
|
46186dc896 | ||
|
|
d0fd28c97c | ||
|
|
c5a87c2a18 | ||
|
|
85d695dbe8 |
@@ -1,4 +1,4 @@
|
||||
# Version: 8.0.0
|
||||
# Version: 8.0.6
|
||||
<IfModule mod_fcgid.c>
|
||||
<IfModule mod_setenvif.c>
|
||||
<IfModule mod_headers.c>
|
||||
@@ -13,6 +13,8 @@ php_value post_max_size 513M
|
||||
php_value memory_limit 512M
|
||||
php_value mbstring.func_overload 0
|
||||
php_value always_populate_raw_post_data -1
|
||||
php_value default_charset 'UTF-8'
|
||||
php_value output_buffering off
|
||||
<IfModule mod_env.c>
|
||||
SetEnv htaccessWorking true
|
||||
</IfModule>
|
||||
|
||||
@@ -3,3 +3,5 @@ post_max_size=513M
|
||||
memory_limit=512M
|
||||
mbstring.func_overload=0
|
||||
always_populate_raw_post_data=-1
|
||||
default_charset='UTF-8'
|
||||
output_buffering=off
|
||||
|
||||
2
3rdparty
2
3rdparty
Submodule 3rdparty updated: a32d3924bd...4a43dcef48
@@ -5,7 +5,7 @@ OCP\JSON::checkLoggedIn();
|
||||
$l = \OC::$server->getL10N('files');
|
||||
|
||||
// Load the files
|
||||
$dir = isset($_GET['dir']) ? $_GET['dir'] : '';
|
||||
$dir = isset($_GET['dir']) ? (string)$_GET['dir'] : '';
|
||||
$dir = \OC\Files\Filesystem::normalizePath($dir);
|
||||
|
||||
try {
|
||||
@@ -22,10 +22,32 @@ try {
|
||||
|
||||
$sortAttribute = isset($_GET['sort']) ? $_GET['sort'] : 'name';
|
||||
$sortDirection = isset($_GET['sortdirection']) ? ($_GET['sortdirection'] === 'desc') : false;
|
||||
$mimetypeFilters = isset($_GET['mimetypes']) ? json_decode($_GET['mimetypes']) : '';
|
||||
|
||||
// make filelist
|
||||
$files = [];
|
||||
// Clean up duplicates from array
|
||||
if (is_array($mimetypeFilters) && count($mimetypeFilters)) {
|
||||
$mimetypeFilters = array_unique($mimetypeFilters);
|
||||
|
||||
if (!in_array('httpd/unix-directory', $mimetypeFilters)) {
|
||||
// append folder filter to be able to browse folders
|
||||
$mimetypeFilters[] = 'httpd/unix-directory';
|
||||
}
|
||||
|
||||
// create filelist with mimetype filter - as getFiles only supports on
|
||||
// mimetype filter at once we will filter this folder for each
|
||||
// mimetypeFilter
|
||||
foreach ($mimetypeFilters as $mimetypeFilter) {
|
||||
$files = array_merge($files, \OCA\Files\Helper::getFiles($dir, $sortAttribute, $sortDirection, $mimetypeFilter));
|
||||
}
|
||||
|
||||
// sort the files accordingly
|
||||
$files = \OCA\Files\Helper::sortFiles($files, $sortAttribute, $sortDirection);
|
||||
} else {
|
||||
// create file list without mimetype filter
|
||||
$files = \OCA\Files\Helper::getFiles($dir, $sortAttribute, $sortDirection);
|
||||
}
|
||||
|
||||
$files = \OCA\Files\Helper::getFiles($dir, $sortAttribute, $sortDirection);
|
||||
$files = \OCA\Files\Helper::populateTags($files);
|
||||
$data['directory'] = $dir;
|
||||
$data['files'] = \OCA\Files\Helper::formatFileInfos($files);
|
||||
|
||||
@@ -51,6 +51,10 @@ if (empty($_POST['dirToken'])) {
|
||||
|
||||
// The token defines the target directory (security reasons)
|
||||
$path = \OC\Files\Filesystem::getPath($linkItem['file_source']);
|
||||
if($path === null) {
|
||||
OCP\JSON::error(array('data' => array_merge(array('message' => $l->t('Unable to set upload directory.')))));
|
||||
die();
|
||||
}
|
||||
$dir = sprintf(
|
||||
"/%s/%s",
|
||||
$path,
|
||||
|
||||
@@ -63,7 +63,6 @@ class ApiController extends Controller {
|
||||
* replace the actual tag selection.
|
||||
*
|
||||
* @NoAdminRequired
|
||||
* @CORS
|
||||
*
|
||||
* @param string $path path
|
||||
* @param array $tags array of tags
|
||||
@@ -91,7 +90,6 @@ class ApiController extends Controller {
|
||||
* Returns a list of all files tagged with the given tag.
|
||||
*
|
||||
* @NoAdminRequired
|
||||
* @CORS
|
||||
*
|
||||
* @param array $tagName tag name to filter by
|
||||
* @return DataResponse
|
||||
@@ -102,7 +100,11 @@ class ApiController extends Controller {
|
||||
foreach ($fileInfos as &$fileInfo) {
|
||||
$file = \OCA\Files\Helper::formatFileInfo($fileInfo);
|
||||
$parts = explode('/', dirname($fileInfo->getPath()), 4);
|
||||
$file['path'] = '/' . $parts[3];
|
||||
if(isset($parts[3])) {
|
||||
$file['path'] = '/' . $parts[3];
|
||||
} else {
|
||||
$file['path'] = '/';
|
||||
}
|
||||
$file['tags'] = array($tagName);
|
||||
$files[] = $file;
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ $ftype=\OC_Helper::getSecureMimeType(\OC\Files\Filesystem::getMimeType( $filenam
|
||||
header('Content-Type:'.$ftype);
|
||||
OCP\Response::setContentDispositionHeader(basename($filename), 'attachment');
|
||||
OCP\Response::disableCaching();
|
||||
header('Content-Length: '.\OC\Files\Filesystem::filesize($filename));
|
||||
OCP\Response::setContentLengthHeader(\OC\Files\Filesystem::filesize($filename));
|
||||
|
||||
OC_Util::obEnd();
|
||||
\OC\Files\Filesystem::readfile( $filename );
|
||||
|
||||
@@ -635,6 +635,8 @@
|
||||
* @param filesArray array of file data (map)
|
||||
*/
|
||||
setFiles: function(filesArray) {
|
||||
var self = this;
|
||||
|
||||
// detach to make adding multiple rows faster
|
||||
this.files = filesArray;
|
||||
|
||||
@@ -655,7 +657,10 @@
|
||||
this.updateSelectionSummary();
|
||||
$(window).scrollTop(0);
|
||||
|
||||
this.$fileList.trigger(jQuery.Event("updated"));
|
||||
this.$fileList.trigger(jQuery.Event('updated'));
|
||||
_.defer(function() {
|
||||
self.$el.closest('#app-content').trigger(jQuery.Event('apprendered'));
|
||||
});
|
||||
},
|
||||
/**
|
||||
* Creates a new table row element using the given file data.
|
||||
@@ -947,7 +952,7 @@
|
||||
mime: mime,
|
||||
etag: fileData.etag,
|
||||
callback: function(url) {
|
||||
iconDiv.css('background-image', 'url(' + url + ')');
|
||||
iconDiv.css('background-image', 'url("' + url + '")');
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -959,7 +964,7 @@
|
||||
};
|
||||
var previewUrl = this.generatePreviewUrl(urlSpec);
|
||||
previewUrl = previewUrl.replace('(', '%28').replace(')', '%29');
|
||||
iconDiv.css('background-image', 'url(' + previewUrl + ')');
|
||||
iconDiv.css('background-image', 'url("' + previewUrl + '")');
|
||||
}
|
||||
}
|
||||
return tr;
|
||||
|
||||
@@ -124,7 +124,7 @@
|
||||
var $target = $(ev.target);
|
||||
var itemId = $target.closest('li').attr('data-id');
|
||||
this.setActiveItem(itemId);
|
||||
return false;
|
||||
ev.preventDefault();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -170,10 +170,11 @@ class Helper
|
||||
* @param string $dir path to the directory
|
||||
* @param string $sortAttribute attribute to sort on
|
||||
* @param bool $sortDescending true for descending sort, false otherwise
|
||||
* @param string $mimetypeFilter limit returned content to this mimetype or mimepart
|
||||
* @return \OCP\Files\FileInfo[] files
|
||||
*/
|
||||
public static function getFiles($dir, $sortAttribute = 'name', $sortDescending = false) {
|
||||
$content = \OC\Files\Filesystem::getDirectoryContent($dir);
|
||||
public static function getFiles($dir, $sortAttribute = 'name', $sortDescending = false, $mimetypeFilter = '') {
|
||||
$content = \OC\Files\Filesystem::getDirectoryContent($dir, $mimetypeFilter);
|
||||
|
||||
return self::sortFiles($content, $sortAttribute, $sortDescending);
|
||||
}
|
||||
|
||||
243
apps/files/tests/controller/apicontrollertest.php
Normal file
243
apps/files/tests/controller/apicontrollertest.php
Normal file
@@ -0,0 +1,243 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) 2015 Lukas Reschke <lukas@owncloud.com>
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later.
|
||||
* See the COPYING-README file.
|
||||
*/
|
||||
|
||||
namespace OCA\Files\Controller;
|
||||
|
||||
use OC\Files\FileInfo;
|
||||
use OCP\AppFramework\Http;
|
||||
use OC\Preview;
|
||||
use OCP\Files\NotFoundException;
|
||||
use OCP\Files\StorageNotAvailableException;
|
||||
use Test\TestCase;
|
||||
use OCP\IRequest;
|
||||
use OCA\Files\Service\TagService;
|
||||
use OCP\AppFramework\Http\DataResponse;
|
||||
|
||||
/**
|
||||
* Class ApiController
|
||||
*
|
||||
* @package OCA\Files\Controller
|
||||
*/
|
||||
class ApiControllerTest extends TestCase {
|
||||
/** @var string */
|
||||
private $appName = 'files';
|
||||
/** @var IRequest */
|
||||
private $request;
|
||||
/** @var TagService */
|
||||
private $tagService;
|
||||
/** @var ApiController */
|
||||
private $apiController;
|
||||
|
||||
public function setUp() {
|
||||
$this->request = $this->getMockBuilder('\OCP\IRequest')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
$this->tagService = $this->getMockBuilder('\OCA\Files\Service\TagService')
|
||||
->disableOriginalConstructor()
|
||||
->getMock();
|
||||
|
||||
$this->apiController = new ApiController(
|
||||
$this->appName,
|
||||
$this->request,
|
||||
$this->tagService
|
||||
);
|
||||
}
|
||||
|
||||
public function testGetFilesByTagEmpty() {
|
||||
$tagName = 'MyTagName';
|
||||
$this->tagService->expects($this->once())
|
||||
->method('getFilesByTag')
|
||||
->with($this->equalTo([$tagName]))
|
||||
->will($this->returnValue([]));
|
||||
|
||||
$expected = new DataResponse(['files' => []]);
|
||||
$this->assertEquals($expected, $this->apiController->getFilesByTag([$tagName]));
|
||||
}
|
||||
|
||||
public function testGetFilesByTagSingle() {
|
||||
$tagName = 'MyTagName';
|
||||
$fileInfo = new FileInfo(
|
||||
'/root.txt',
|
||||
$this->getMockBuilder('\OC\Files\Storage\Storage')
|
||||
->disableOriginalConstructor()
|
||||
->getMock(),
|
||||
'/var/www/root.txt',
|
||||
[
|
||||
'mtime' => 55,
|
||||
'mimetype' => 'application/pdf',
|
||||
'size' => 1234,
|
||||
'etag' => 'MyEtag',
|
||||
],
|
||||
$this->getMockBuilder('\OCP\Files\Mount\IMountPoint')
|
||||
->disableOriginalConstructor()
|
||||
->getMock()
|
||||
);
|
||||
$this->tagService->expects($this->once())
|
||||
->method('getFilesByTag')
|
||||
->with($this->equalTo([$tagName]))
|
||||
->will($this->returnValue([$fileInfo]));
|
||||
|
||||
$expected = new DataResponse([
|
||||
'files' => [
|
||||
[
|
||||
'id' => null,
|
||||
'parentId' => null,
|
||||
'date' => \OCP\Util::formatDate(55),
|
||||
'mtime' => 55000,
|
||||
'icon' => \OCA\Files\Helper::determineIcon($fileInfo),
|
||||
'name' => 'root.txt',
|
||||
'permissions' => null,
|
||||
'mimetype' => 'application/pdf',
|
||||
'size' => 1234,
|
||||
'type' => 'file',
|
||||
'etag' => 'MyEtag',
|
||||
'path' => '/',
|
||||
'tags' => [
|
||||
[
|
||||
'MyTagName'
|
||||
]
|
||||
],
|
||||
],
|
||||
],
|
||||
]);
|
||||
$this->assertEquals($expected, $this->apiController->getFilesByTag([$tagName]));
|
||||
}
|
||||
|
||||
public function testGetFilesByTagMultiple() {
|
||||
$tagName = 'MyTagName';
|
||||
$fileInfo1 = new FileInfo(
|
||||
'/root.txt',
|
||||
$this->getMockBuilder('\OC\Files\Storage\Storage')
|
||||
->disableOriginalConstructor()
|
||||
->getMock(),
|
||||
'/var/www/root.txt',
|
||||
[
|
||||
'mtime' => 55,
|
||||
'mimetype' => 'application/pdf',
|
||||
'size' => 1234,
|
||||
'etag' => 'MyEtag',
|
||||
],
|
||||
$this->getMockBuilder('\OCP\Files\Mount\IMountPoint')
|
||||
->disableOriginalConstructor()
|
||||
->getMock()
|
||||
);
|
||||
$fileInfo2 = new FileInfo(
|
||||
'/root.txt',
|
||||
$this->getMockBuilder('\OC\Files\Storage\Storage')
|
||||
->disableOriginalConstructor()
|
||||
->getMock(),
|
||||
'/var/www/some/sub.txt',
|
||||
[
|
||||
'mtime' => 999,
|
||||
'mimetype' => 'application/binary',
|
||||
'size' => 9876,
|
||||
'etag' => 'SubEtag',
|
||||
],
|
||||
$this->getMockBuilder('\OCP\Files\Mount\IMountPoint')
|
||||
->disableOriginalConstructor()
|
||||
->getMock()
|
||||
);
|
||||
$this->tagService->expects($this->once())
|
||||
->method('getFilesByTag')
|
||||
->with($this->equalTo([$tagName]))
|
||||
->will($this->returnValue([$fileInfo1, $fileInfo2]));
|
||||
|
||||
$expected = new DataResponse([
|
||||
'files' => [
|
||||
[
|
||||
'id' => null,
|
||||
'parentId' => null,
|
||||
'date' => \OCP\Util::formatDate(55),
|
||||
'mtime' => 55000,
|
||||
'icon' => \OCA\Files\Helper::determineIcon($fileInfo1),
|
||||
'name' => 'root.txt',
|
||||
'permissions' => null,
|
||||
'mimetype' => 'application/pdf',
|
||||
'size' => 1234,
|
||||
'type' => 'file',
|
||||
'etag' => 'MyEtag',
|
||||
'path' => '/',
|
||||
'tags' => [
|
||||
[
|
||||
'MyTagName'
|
||||
]
|
||||
],
|
||||
],
|
||||
[
|
||||
'id' => null,
|
||||
'parentId' => null,
|
||||
'date' => \OCP\Util::formatDate(999),
|
||||
'mtime' => 999000,
|
||||
'icon' => \OCA\Files\Helper::determineIcon($fileInfo2),
|
||||
'name' => 'root.txt',
|
||||
'permissions' => null,
|
||||
'mimetype' => 'application/binary',
|
||||
'size' => 9876,
|
||||
'type' => 'file',
|
||||
'etag' => 'SubEtag',
|
||||
'path' => '/',
|
||||
'tags' => [
|
||||
[
|
||||
'MyTagName'
|
||||
]
|
||||
],
|
||||
]
|
||||
],
|
||||
]);
|
||||
$this->assertEquals($expected, $this->apiController->getFilesByTag([$tagName]));
|
||||
}
|
||||
|
||||
public function testUpdateFileTagsEmpty() {
|
||||
$expected = new DataResponse([]);
|
||||
$this->assertEquals($expected, $this->apiController->updateFileTags('/path.txt'));
|
||||
}
|
||||
|
||||
public function testUpdateFileTagsWorking() {
|
||||
$this->tagService->expects($this->once())
|
||||
->method('updateFileTags')
|
||||
->with('/path.txt', ['Tag1', 'Tag2']);
|
||||
|
||||
$expected = new DataResponse([
|
||||
'tags' => [
|
||||
'Tag1',
|
||||
'Tag2'
|
||||
],
|
||||
]);
|
||||
$this->assertEquals($expected, $this->apiController->updateFileTags('/path.txt', ['Tag1', 'Tag2']));
|
||||
}
|
||||
|
||||
public function testUpdateFileTagsNotFoundException() {
|
||||
$this->tagService->expects($this->once())
|
||||
->method('updateFileTags')
|
||||
->with('/path.txt', ['Tag1', 'Tag2'])
|
||||
->will($this->throwException(new NotFoundException('My error message')));
|
||||
|
||||
$expected = new DataResponse('My error message', Http::STATUS_NOT_FOUND);
|
||||
$this->assertEquals($expected, $this->apiController->updateFileTags('/path.txt', ['Tag1', 'Tag2']));
|
||||
}
|
||||
|
||||
public function testUpdateFileTagsStorageNotAvailableException() {
|
||||
$this->tagService->expects($this->once())
|
||||
->method('updateFileTags')
|
||||
->with('/path.txt', ['Tag1', 'Tag2'])
|
||||
->will($this->throwException(new StorageNotAvailableException('My error message')));
|
||||
|
||||
$expected = new DataResponse('My error message', Http::STATUS_SERVICE_UNAVAILABLE);
|
||||
$this->assertEquals($expected, $this->apiController->updateFileTags('/path.txt', ['Tag1', 'Tag2']));
|
||||
}
|
||||
|
||||
public function testUpdateFileTagsStorageGenericException() {
|
||||
$this->tagService->expects($this->once())
|
||||
->method('updateFileTags')
|
||||
->with('/path.txt', ['Tag1', 'Tag2'])
|
||||
->will($this->throwException(new \Exception('My error message')));
|
||||
|
||||
$expected = new DataResponse('My error message', Http::STATUS_NOT_FOUND);
|
||||
$this->assertEquals($expected, $this->apiController->updateFileTags('/path.txt', ['Tag1', 'Tag2']));
|
||||
}
|
||||
}
|
||||
@@ -65,7 +65,7 @@ class TagServiceTest extends \Test\TestCase {
|
||||
->withAnyParameters()
|
||||
->will($this->returnValue($user));
|
||||
|
||||
$this->root = \OC::$server->getUserFolder();
|
||||
$this->root = \OC::$server->getUserFolder($this->user);
|
||||
|
||||
$this->tagger = \OC::$server->getTagManager()->load('files');
|
||||
$this->tagService = new TagService(
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
<types>
|
||||
<filesystem/>
|
||||
</types>
|
||||
<ocsid>166047</ocsid>
|
||||
<dependencies>
|
||||
<lib>openssl</lib>
|
||||
</dependencies>
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.7.1
|
||||
0.7.2
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<?php
|
||||
$TRANSLATIONS = array(
|
||||
"Saving..." => "Spašavam..."
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);";
|
||||
@@ -1,5 +0,0 @@
|
||||
<?php
|
||||
$TRANSLATIONS = array(
|
||||
"Saving..." => "Simpan..."
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=1; plural=0;";
|
||||
@@ -1,5 +0,0 @@
|
||||
<?php
|
||||
$TRANSLATIONS = array(
|
||||
"Saving..." => "Enregistra..."
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n > 1);";
|
||||
@@ -1,5 +0,0 @@
|
||||
<?php
|
||||
$TRANSLATIONS = array(
|
||||
"personal settings" => "వ్యక్తిగత అమరికలు"
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
|
||||
@@ -478,7 +478,7 @@ class Hooks {
|
||||
|
||||
list($ownerNew, $pathNew) = $util->getUidAndFilename($params['newpath']);
|
||||
|
||||
if ($util->isSystemWideMountPoint($pathNew)) {
|
||||
if ($util->isSystemWideMountPoint($pathNew, $ownerNew)) {
|
||||
$newKeysPath = 'files_encryption/keys/' . $pathNew;
|
||||
} else {
|
||||
$newKeysPath = $ownerNew . '/files_encryption/keys/' . $pathNew;
|
||||
|
||||
@@ -187,7 +187,7 @@ class Keymanager {
|
||||
$filePath_f = ltrim($filename, '/');
|
||||
|
||||
// in case of system wide mount points the keys are stored directly in the data directory
|
||||
if ($util->isSystemWideMountPoint($filename)) {
|
||||
if ($util->isSystemWideMountPoint($filename, $owner)) {
|
||||
$keyPath = self::$keys_base_dir . $filePath_f . '/';
|
||||
} else {
|
||||
$keyPath = '/' . $owner . self::$keys_base_dir . $filePath_f . '/';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?php
|
||||
/**
|
||||
/**
|
||||
* ownCloud
|
||||
*
|
||||
* @copyright (C) 2014 ownCloud, Inc.
|
||||
@@ -35,6 +35,7 @@ class Migration {
|
||||
|
||||
public function __construct() {
|
||||
$this->view = new \OC\Files\View();
|
||||
$this->view->getUpdater()->disable();
|
||||
$this->public_share_key_id = Helper::getPublicShareKeyId();
|
||||
$this->recovery_key_id = Helper::getRecoveryKeyId();
|
||||
}
|
||||
@@ -50,7 +51,7 @@ class Migration {
|
||||
$this->reorganizeFolderStructureForUser($user);
|
||||
}
|
||||
$offset += $limit;
|
||||
} while(count($users) >= $limit);
|
||||
} while (count($users) >= $limit);
|
||||
}
|
||||
|
||||
public function reorganizeSystemFolderStructure() {
|
||||
@@ -74,6 +75,10 @@ class Migration {
|
||||
$this->view->deleteAll('/owncloud_private_key');
|
||||
$this->view->deleteAll('/files_encryption/share-keys');
|
||||
$this->view->deleteAll('/files_encryption/keyfiles');
|
||||
$storage = $this->view->getMount('')->getStorage();
|
||||
$storage->getScanner()->scan('files_encryption');
|
||||
$storage->getCache()->remove('owncloud_private_key');
|
||||
$storage->getCache()->remove('public-keys');
|
||||
}
|
||||
|
||||
|
||||
@@ -96,6 +101,7 @@ class Migration {
|
||||
}
|
||||
// delete old folders
|
||||
$this->deleteOldKeys($user);
|
||||
$this->view->getMount('/' . $user)->getStorage()->getScanner()->scan('files_encryption');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +133,7 @@ class Migration {
|
||||
while (($oldPublicKey = readdir($dh)) !== false) {
|
||||
if (!\OC\Files\Filesystem::isIgnoredDir($oldPublicKey)) {
|
||||
$newPublicKey = substr($oldPublicKey, 0, strlen($oldPublicKey) - strlen('.public.key')) . '.publicKey';
|
||||
$this->view->rename('public-keys/' . $oldPublicKey , 'files_encryption/public_keys/' . $newPublicKey);
|
||||
$this->view->rename('public-keys/' . $oldPublicKey, 'files_encryption/public_keys/' . $newPublicKey);
|
||||
}
|
||||
}
|
||||
closedir($dh);
|
||||
@@ -141,7 +147,7 @@ class Migration {
|
||||
while (($oldPrivateKey = readdir($dh)) !== false) {
|
||||
if (!\OC\Files\Filesystem::isIgnoredDir($oldPrivateKey)) {
|
||||
$newPrivateKey = substr($oldPrivateKey, 0, strlen($oldPrivateKey) - strlen('.private.key')) . '.privateKey';
|
||||
$this->view->rename('owncloud_private_key/' . $oldPrivateKey , 'files_encryption/' . $newPrivateKey);
|
||||
$this->view->rename('owncloud_private_key/' . $oldPrivateKey, 'files_encryption/' . $newPrivateKey);
|
||||
}
|
||||
}
|
||||
closedir($dh);
|
||||
@@ -149,10 +155,10 @@ class Migration {
|
||||
}
|
||||
|
||||
private function renameUsersPrivateKey($user) {
|
||||
$oldPrivateKey = $user . '/files_encryption/' . $user . '.private.key';
|
||||
$newPrivateKey = substr($oldPrivateKey, 0, strlen($oldPrivateKey) - strlen('.private.key')) . '.privateKey';
|
||||
$oldPrivateKey = $user . '/files_encryption/' . $user . '.private.key';
|
||||
$newPrivateKey = substr($oldPrivateKey, 0, strlen($oldPrivateKey) - strlen('.private.key')) . '.privateKey';
|
||||
|
||||
$this->view->rename($oldPrivateKey, $newPrivateKey);
|
||||
$this->view->rename($oldPrivateKey, $newPrivateKey);
|
||||
}
|
||||
|
||||
private function getFileName($file, $trash) {
|
||||
@@ -186,7 +192,7 @@ class Migration {
|
||||
}
|
||||
|
||||
private function getFilePath($path, $user, $trash) {
|
||||
$offset = $trash ? strlen($user . '/files_trashbin/keyfiles') : strlen($user . '/files_encryption/keyfiles');
|
||||
$offset = $trash ? strlen($user . '/files_trashbin/keyfiles') : strlen($user . '/files_encryption/keyfiles');
|
||||
return substr($path, $offset);
|
||||
}
|
||||
|
||||
@@ -215,7 +221,7 @@ class Migration {
|
||||
$extension = $this->getExtension($file, $trash);
|
||||
$targetDir = $this->getTargetDir($user, $filePath, $filename, $extension, $trash);
|
||||
$this->createPathForKeys($targetDir);
|
||||
$this->view->copy($path . '/' . $file, $targetDir . '/fileKey');
|
||||
$this->view->rename($path . '/' . $file, $targetDir . '/fileKey');
|
||||
$this->renameShareKeys($user, $filePath, $filename, $targetDir, $trash);
|
||||
}
|
||||
}
|
||||
@@ -258,10 +264,10 @@ class Migration {
|
||||
if ($this->view->is_dir($oldShareKeyPath . '/' . $file)) {
|
||||
continue;
|
||||
} else {
|
||||
if (substr($file, 0, strlen($filename) +1) === $filename . '.') {
|
||||
if (substr($file, 0, strlen($filename) + 1) === $filename . '.') {
|
||||
|
||||
$uid = $this->getUidFromShareKey($file, $filename, $trash);
|
||||
$this->view->copy($oldShareKeyPath . '/' . $file, $target . '/' . $uid . '.shareKey');
|
||||
$this->view->rename($oldShareKeyPath . '/' . $file, $target . '/' . $uid . '.shareKey');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -299,7 +299,10 @@ class Proxy extends \OC_FileProxy {
|
||||
public function postGetFileInfo($path, $data) {
|
||||
|
||||
// if path is a folder do nothing
|
||||
if (\OCP\App::isEnabled('files_encryption') && $data !== false && array_key_exists('size', $data)) {
|
||||
if (\OCP\App::isEnabled('files_encryption') &&
|
||||
$data !== false &&
|
||||
array_key_exists('size', $data) &&
|
||||
$this->shouldEncrypt($path)) {
|
||||
|
||||
// Disable encryption proxy to prevent recursive calls
|
||||
$proxyStatus = \OC_FileProxy::$enabled;
|
||||
@@ -330,7 +333,8 @@ class Proxy extends \OC_FileProxy {
|
||||
// if encryption is no longer enabled or if the files aren't migrated yet
|
||||
// we return the default file size
|
||||
if(!\OCP\App::isEnabled('files_encryption') ||
|
||||
$util->getMigrationStatus() !== Util::MIGRATION_COMPLETED) {
|
||||
$util->getMigrationStatus() !== Util::MIGRATION_COMPLETED ||
|
||||
!$this->shouldEncrypt($path)) {
|
||||
return $size;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
*/
|
||||
|
||||
namespace OCA\Files_Encryption;
|
||||
use OC\User\NoUserException;
|
||||
|
||||
/**
|
||||
* Class for utilities relating to encrypted file storage system
|
||||
@@ -945,8 +946,14 @@ class Util {
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
$util = new Util($this->view, $user);
|
||||
return $util->ready();
|
||||
try {
|
||||
$util = new Util($this->view, $user);
|
||||
return $util->ready();
|
||||
} catch (NoUserException $e) {
|
||||
\OCP\Util::writeLog('Encryption library',
|
||||
'No User object for '.$user, \OCP\Util::DEBUG);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1610,15 +1617,16 @@ class Util {
|
||||
/**
|
||||
* check if the file is stored on a system wide mount point
|
||||
* @param string $path relative to /data/user with leading '/'
|
||||
* @param string $uid
|
||||
* @return boolean
|
||||
*/
|
||||
public function isSystemWideMountPoint($path) {
|
||||
public function isSystemWideMountPoint($path, $uid) {
|
||||
$normalizedPath = ltrim($path, '/');
|
||||
if (\OCP\App::isEnabled("files_external")) {
|
||||
$mounts = \OC_Mount_Config::getSystemMountPoints();
|
||||
foreach ($mounts as $mount) {
|
||||
if ($mount['mountpoint'] == substr($normalizedPath, 0, strlen($mount['mountpoint']))) {
|
||||
if ($this->isMountPointApplicableToUser($mount)) {
|
||||
if ($this->isMountPointApplicableToUser($mount, $uid)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -1631,10 +1639,10 @@ class Util {
|
||||
* check if mount point is applicable to user
|
||||
*
|
||||
* @param array $mount contains $mount['applicable']['users'], $mount['applicable']['groups']
|
||||
* @param string $uid
|
||||
* @return boolean
|
||||
*/
|
||||
protected function isMountPointApplicableToUser($mount) {
|
||||
$uid = \OCP\User::getUser();
|
||||
protected function isMountPointApplicableToUser($mount, $uid) {
|
||||
$acceptedUids = array('all', $uid);
|
||||
// check if mount point is applicable for the user
|
||||
$intersection = array_intersect($acceptedUids, $mount['applicable']['users']);
|
||||
|
||||
@@ -30,6 +30,7 @@ class Hooks extends TestCase {
|
||||
|
||||
const TEST_ENCRYPTION_HOOKS_USER1 = "test-encryption-hooks-user1.dot";
|
||||
const TEST_ENCRYPTION_HOOKS_USER2 = "test-encryption-hooks-user2.dot";
|
||||
const TEST_ENCRYPTION_HOOKS_USER3 = "test-encryption-hooks-user3.dot";
|
||||
|
||||
/** @var \OC\Files\View */
|
||||
public $user1View; // view on /data/user1/files
|
||||
@@ -91,6 +92,7 @@ class Hooks extends TestCase {
|
||||
// cleanup test user
|
||||
\OC_User::deleteUser(self::TEST_ENCRYPTION_HOOKS_USER1);
|
||||
\OC_User::deleteUser(self::TEST_ENCRYPTION_HOOKS_USER2);
|
||||
\OC_User::deleteUser(self::TEST_ENCRYPTION_HOOKS_USER3);
|
||||
|
||||
parent::tearDownAfterClass();
|
||||
}
|
||||
@@ -407,31 +409,35 @@ class Hooks extends TestCase {
|
||||
$view = new \OC\Files\View();
|
||||
|
||||
// set user password for the first time
|
||||
\OCA\Files_Encryption\Hooks::postCreateUser(array('uid' => 'newUser', 'password' => 'newUserPassword'));
|
||||
\OC_User::createUser(self::TEST_ENCRYPTION_HOOKS_USER3, 'newUserPassword');
|
||||
\OCA\Files_Encryption\Hooks::postCreateUser(array(
|
||||
'uid' => self::TEST_ENCRYPTION_HOOKS_USER3,
|
||||
'password' => 'newUserPassword')
|
||||
);
|
||||
|
||||
$this->assertTrue($view->file_exists(\OCA\Files_Encryption\Keymanager::getPublicKeyPath() . '/newUser.publicKey'));
|
||||
$this->assertTrue($view->file_exists('newUser/files_encryption/newUser.privateKey'));
|
||||
$this->assertTrue($view->file_exists(\OCA\Files_Encryption\Keymanager::getPublicKeyPath() . '/'.self::TEST_ENCRYPTION_HOOKS_USER3.'.publicKey'));
|
||||
$this->assertTrue($view->file_exists(self::TEST_ENCRYPTION_HOOKS_USER3.'/files_encryption/'.self::TEST_ENCRYPTION_HOOKS_USER3.'.privateKey'));
|
||||
|
||||
// check if we are able to decrypt the private key
|
||||
$encryptedKey = \OCA\Files_Encryption\Keymanager::getPrivateKey($view, 'newUser');
|
||||
$encryptedKey = \OCA\Files_Encryption\Keymanager::getPrivateKey($view, self::TEST_ENCRYPTION_HOOKS_USER3);
|
||||
$privateKey = \OCA\Files_Encryption\Crypt::decryptPrivateKey($encryptedKey, 'newUserPassword');
|
||||
$this->assertTrue(is_string($privateKey));
|
||||
|
||||
// change the password before the user logged-in for the first time,
|
||||
// we can replace the encryption keys
|
||||
\OCA\Files_Encryption\Hooks::setPassphrase(array('uid' => 'newUser', 'password' => 'passwordChanged'));
|
||||
\OCA\Files_Encryption\Hooks::setPassphrase(array('uid' => self::TEST_ENCRYPTION_HOOKS_USER3, 'password' => 'passwordChanged'));
|
||||
|
||||
$encryptedKey = \OCA\Files_Encryption\Keymanager::getPrivateKey($view, 'newUser');
|
||||
$encryptedKey = \OCA\Files_Encryption\Keymanager::getPrivateKey($view, self::TEST_ENCRYPTION_HOOKS_USER3);
|
||||
$privateKey = \OCA\Files_Encryption\Crypt::decryptPrivateKey($encryptedKey, 'passwordChanged');
|
||||
$this->assertTrue(is_string($privateKey));
|
||||
|
||||
// now create a files folder to simulate a already used account
|
||||
$view->mkdir('/newUser/files');
|
||||
$view->mkdir('/'.self::TEST_ENCRYPTION_HOOKS_USER3.'/files');
|
||||
|
||||
// change the password after the user logged in, now the password should not change
|
||||
\OCA\Files_Encryption\Hooks::setPassphrase(array('uid' => 'newUser', 'password' => 'passwordChanged2'));
|
||||
\OCA\Files_Encryption\Hooks::setPassphrase(array('uid' => self::TEST_ENCRYPTION_HOOKS_USER3, 'password' => 'passwordChanged2'));
|
||||
|
||||
$encryptedKey = \OCA\Files_Encryption\Keymanager::getPrivateKey($view, 'newUser');
|
||||
$encryptedKey = \OCA\Files_Encryption\Keymanager::getPrivateKey($view, self::TEST_ENCRYPTION_HOOKS_USER3);
|
||||
$privateKey = \OCA\Files_Encryption\Crypt::decryptPrivateKey($encryptedKey, 'passwordChanged2');
|
||||
$this->assertFalse($privateKey);
|
||||
|
||||
|
||||
@@ -587,9 +587,8 @@ class Util extends TestCase {
|
||||
* @dataProvider dataProviderFortestIsMountPointApplicableToUser
|
||||
*/
|
||||
function testIsMountPointApplicableToUser($mount, $expectedResult) {
|
||||
self::loginHelper(self::TEST_ENCRYPTION_UTIL_USER1);
|
||||
$dummyClass = new DummyUtilClass($this->view, self::TEST_ENCRYPTION_UTIL_USER1);
|
||||
$result = $dummyClass->testIsMountPointApplicableToUser($mount);
|
||||
$result = $dummyClass->testIsMountPointApplicableToUser($mount, self::TEST_ENCRYPTION_UTIL_USER1);
|
||||
|
||||
$this->assertSame($expectedResult, $result);
|
||||
}
|
||||
@@ -663,7 +662,7 @@ class Util extends TestCase {
|
||||
* dummy class extends \OCA\Files_Encryption\Util to access protected methods for testing
|
||||
*/
|
||||
class DummyUtilClass extends \OCA\Files_Encryption\Util {
|
||||
public function testIsMountPointApplicableToUser($mount) {
|
||||
return $this->isMountPointApplicableToUser($mount);
|
||||
public function testIsMountPointApplicableToUser($mount, $uid) {
|
||||
return $this->isMountPointApplicableToUser($mount, $uid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,8 +72,16 @@ class Dropbox_OAuth_Curl extends Dropbox_OAuth {
|
||||
if (strtoupper($method) == 'POST') {
|
||||
curl_setopt($ch, CURLOPT_URL, $uri);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
// if (is_array($arguments))
|
||||
// $arguments=http_build_query($arguments);
|
||||
|
||||
//if (is_array($arguments))
|
||||
// $arguments=http_build_query($arguments);
|
||||
if(is_array($arguments)) {
|
||||
foreach ($arguments as $key => $value) {
|
||||
if ($value[0] === '@') {
|
||||
exit();
|
||||
}
|
||||
}
|
||||
}
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $arguments);
|
||||
// $httpHeaders['Content-Length']=strlen($arguments);
|
||||
} else {
|
||||
|
||||
11
apps/files_external/3rdparty/smb4php/smb.php
vendored
11
apps/files_external/3rdparty/smb4php/smb.php
vendored
@@ -112,6 +112,11 @@ class smb {
|
||||
|
||||
|
||||
function execute ($command, $purl, $regexp = NULL) {
|
||||
if (strpos($command,';') !== false) {
|
||||
trigger_error('Semicolon not supported in commands');
|
||||
exit();
|
||||
}
|
||||
|
||||
return smb::client ('-d 0 '
|
||||
. escapeshellarg ('//' . $purl['host'] . '/' . $purl['share'])
|
||||
. ' -c ' . escapeshellarg ($command), $purl, $regexp
|
||||
@@ -319,14 +324,14 @@ class smb {
|
||||
trigger_error('rename(): error in URL', E_USER_ERROR);
|
||||
}
|
||||
smb::clearstatcache ($url_from);
|
||||
$cmd = '';
|
||||
// check if target file exists
|
||||
if (smb::url_stat($url_to)) {
|
||||
// delete target file first
|
||||
$cmd = 'del "' . $to['path'] . '"; ';
|
||||
$cmd = 'del "' . $to['path'] . '"';
|
||||
smb::execute($cmd, $to);
|
||||
$replace = true;
|
||||
}
|
||||
$cmd .= 'rename "' . $from['path'] . '" "' . $to['path'] . '"';
|
||||
$cmd = 'rename "' . $from['path'] . '" "' . $to['path'] . '"';
|
||||
$result = smb::execute($cmd, $to);
|
||||
if ($replace) {
|
||||
// clear again, else the cache will return the info
|
||||
|
||||
@@ -17,8 +17,6 @@
|
||||
<types>
|
||||
<filesystem/>
|
||||
</types>
|
||||
<ocsid>166048</ocsid>
|
||||
|
||||
<dependencies>
|
||||
<owncloud min-version="8" />
|
||||
</dependencies>
|
||||
|
||||
@@ -100,7 +100,12 @@ class Dropbox extends \OC\Files\Storage\Common {
|
||||
return $contents;
|
||||
} else {
|
||||
try {
|
||||
$response = $this->dropbox->getMetaData($path, 'false');
|
||||
$requestPath = $path;
|
||||
if ($path === '.') {
|
||||
$requestPath = '';
|
||||
}
|
||||
|
||||
$response = $this->dropbox->getMetaData($requestPath, 'false');
|
||||
if (!isset($response['is_deleted']) || !$response['is_deleted']) {
|
||||
$this->metaData[$path] = $response;
|
||||
return $response;
|
||||
|
||||
@@ -37,13 +37,13 @@ class OwnCloud extends \OC\Files\Storage\DAV{
|
||||
$host = substr($host, 0, $hostSlashPos);
|
||||
}
|
||||
|
||||
if (substr($contextPath , 1) !== '/'){
|
||||
if (substr($contextPath, -1) !== '/'){
|
||||
$contextPath .= '/';
|
||||
}
|
||||
|
||||
if (isset($params['root'])){
|
||||
$root = $params['root'];
|
||||
if (substr($root, 1) !== '/'){
|
||||
if (substr($root, 0, 1) !== '/'){
|
||||
$root = '/' . $root;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,13 +18,19 @@ class SMB_OC extends \OC\Files\Storage\SMB {
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct($params) {
|
||||
if (isset($params['host']) && \OC::$server->getSession()->exists('smb-credentials')) {
|
||||
if (isset($params['host'])) {
|
||||
$host=$params['host'];
|
||||
$this->username_as_share = ($params['username_as_share'] === 'true');
|
||||
|
||||
$params_auth = json_decode(\OC::$server->getCrypto()->decrypt(\OC::$server->getSession()->get('smb-credentials')), true);
|
||||
$user = \OC::$server->getSession()->get('loginname');
|
||||
$password = $params_auth['password'];
|
||||
$user = 'foo';
|
||||
$password = 'bar';
|
||||
if (\OC::$server->getSession()->exists('smb-credentials')) {
|
||||
$params_auth = json_decode(\OC::$server->getCrypto()->decrypt(\OC::$server->getSession()->get('smb-credentials')), true);
|
||||
$user = \OC::$server->getSession()->get('loginname');
|
||||
$password = $params_auth['password'];
|
||||
} else {
|
||||
// assume we are testing from the admin section
|
||||
}
|
||||
|
||||
$root=isset($params['root'])?$params['root']:'/';
|
||||
$share = '';
|
||||
|
||||
@@ -68,6 +68,14 @@ class OwnCloudFunctions extends \Test\TestCase {
|
||||
),
|
||||
'http://testhost/testroot/remote.php/webdav/subdir/',
|
||||
),
|
||||
array(
|
||||
array(
|
||||
'host' => 'http://testhost/testroot/',
|
||||
'root' => '/subdir',
|
||||
'secure' => false
|
||||
),
|
||||
'http://testhost/testroot/remote.php/webdav/subdir/',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -38,8 +38,6 @@ $externalManager = new \OCA\Files_Sharing\External\Manager(
|
||||
\OC::$server->getUserSession()->getUser()->getUID()
|
||||
);
|
||||
|
||||
$name = OCP\Files::buildNotExistingFileName('/', $name);
|
||||
|
||||
// check for ssl cert
|
||||
if (substr($remote, 0, 5) === 'https' and !OC_Util::getUrlContent($remote)) {
|
||||
\OCP\JSON::error(array('data' => array('message' => $l->t('Invalid or untrusted SSL certificate'))));
|
||||
|
||||
@@ -46,6 +46,13 @@ $view = new \OC\Files\View('/' . $userId . '/files');
|
||||
|
||||
$pathId = $linkedItem['file_source'];
|
||||
$path = $view->getPath($pathId);
|
||||
|
||||
if($path === null) {
|
||||
\OC_Response::setStatus(\OC_Response::STATUS_NOT_FOUND);
|
||||
\OC_Log::write('core-preview', 'Could not resolve file for shared item', OC_Log::WARN);
|
||||
exit;
|
||||
}
|
||||
|
||||
$pathInfo = $view->getFileInfo($path);
|
||||
$sharedFile = null;
|
||||
|
||||
|
||||
@@ -335,7 +335,7 @@ class Local {
|
||||
if(isset($params['_put']['permissions'])) {
|
||||
return self::updatePermissions($share, $params);
|
||||
} elseif (isset($params['_put']['password'])) {
|
||||
return self::updatePassword($share, $params);
|
||||
return self::updatePassword($params['id'], (int)$share['share_type'], $params['_put']['password']);
|
||||
} elseif (isset($params['_put']['publicUpload'])) {
|
||||
return self::updatePublicUpload($share, $params);
|
||||
} elseif (isset($params['_put']['expireDate'])) {
|
||||
@@ -446,47 +446,22 @@ class Local {
|
||||
|
||||
/**
|
||||
* update password for public link share
|
||||
* @param array $share information about the share
|
||||
* @param array $params 'password'
|
||||
* @param int $shareId
|
||||
* @param int $shareType
|
||||
* @param string $password
|
||||
* @return \OC_OCS_Result
|
||||
*/
|
||||
private static function updatePassword($share, $params) {
|
||||
|
||||
$itemSource = $share['item_source'];
|
||||
$itemType = $share['item_type'];
|
||||
|
||||
if( (int)$share['share_type'] !== \OCP\Share::SHARE_TYPE_LINK) {
|
||||
private static function updatePassword($shareId, $shareType, $password) {
|
||||
if($shareType !== \OCP\Share::SHARE_TYPE_LINK) {
|
||||
return new \OC_OCS_Result(null, 400, "password protection is only supported for public shares");
|
||||
}
|
||||
|
||||
$shareWith = isset($params['_put']['password']) ? $params['_put']['password'] : null;
|
||||
|
||||
if($shareWith === '') {
|
||||
$shareWith = null;
|
||||
}
|
||||
|
||||
$items = \OCP\Share::getItemShared($itemType, $itemSource);
|
||||
|
||||
$checkExists = false;
|
||||
foreach ($items as $item) {
|
||||
if($item['share_type'] === \OCP\Share::SHARE_TYPE_LINK) {
|
||||
$checkExists = true;
|
||||
$permissions = $item['permissions'];
|
||||
}
|
||||
}
|
||||
|
||||
if (!$checkExists) {
|
||||
return new \OC_OCS_Result(null, 404, "share doesn't exists, can't change password");
|
||||
if($password === '') {
|
||||
$password = null;
|
||||
}
|
||||
|
||||
try {
|
||||
$result = \OCP\Share::shareItem(
|
||||
$itemType,
|
||||
$itemSource,
|
||||
\OCP\Share::SHARE_TYPE_LINK,
|
||||
$shareWith,
|
||||
$permissions
|
||||
);
|
||||
$result = \OCP\Share::setPassword($shareId, $password);
|
||||
} catch (\Exception $e) {
|
||||
return new \OC_OCS_Result(null, 403, $e->getMessage());
|
||||
}
|
||||
|
||||
@@ -64,8 +64,6 @@ class Server2Server {
|
||||
$shareWith
|
||||
);
|
||||
|
||||
$name = \OCP\Files::buildNotExistingFileName('/', $name);
|
||||
|
||||
try {
|
||||
$externalManager->addShare($remote, $token, '', $name, $owner, false, $shareWith, $remoteId);
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
<field>
|
||||
<name>remote_id</name>
|
||||
<type>integer</type>
|
||||
<default>-1</default>
|
||||
<notnull>true</notnull>
|
||||
<length>4</length>
|
||||
</field>
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.6.0
|
||||
0.6.2
|
||||
|
||||
@@ -42,7 +42,7 @@ class Application extends App {
|
||||
$server->getAppConfig(),
|
||||
$server->getConfig(),
|
||||
$c->query('URLGenerator'),
|
||||
$server->getUserManager(),
|
||||
$c->query('UserManager'),
|
||||
$server->getLogger(),
|
||||
$server->getActivityManager()
|
||||
);
|
||||
@@ -65,6 +65,9 @@ class Application extends App {
|
||||
$container->registerService('URLGenerator', function(SimpleContainer $c) use ($server){
|
||||
return $server->getUrlGenerator();
|
||||
});
|
||||
$container->registerService('UserManager', function(SimpleContainer $c) use ($server){
|
||||
return $server->getUserManager();
|
||||
});
|
||||
$container->registerService('IsIncomingShareEnabled', function(SimpleContainer $c) {
|
||||
return Helper::isIncomingServer2serverShareEnabled();
|
||||
});
|
||||
|
||||
@@ -51,7 +51,7 @@ thead {
|
||||
}
|
||||
|
||||
/* keep long file names in one line to not overflow download button on mobile */
|
||||
.directDownload #download {
|
||||
.directDownload #downloadFile {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
@@ -88,8 +88,8 @@ OCA.Sharing.PublicApp = {
|
||||
|
||||
// dynamically load image previews
|
||||
var params = {
|
||||
x: $(document).width() * window.devicePixelRatio,
|
||||
y: $(document).height() * window.devicePixelRatio,
|
||||
x: Math.floor($(document).width() * window.devicePixelRatio),
|
||||
y: Math.floor($(document).height() * window.devicePixelRatio),
|
||||
a: 'true',
|
||||
file: encodeURIComponent(this.initialDir + $('#filename').val()),
|
||||
t: $('#sharingToken').val(),
|
||||
@@ -147,6 +147,8 @@ OCA.Sharing.PublicApp = {
|
||||
|
||||
this.fileList.generatePreviewUrl = function (urlSpec) {
|
||||
urlSpec.t = $('#dirToken').val();
|
||||
urlSpec.y = Math.floor(36 * window.devicePixelRatio);
|
||||
urlSpec.x = Math.floor(36 * window.devicePixelRatio);
|
||||
return OC.generateUrl('/apps/files_sharing/ajax/publicpreview.php?') + $.param(urlSpec);
|
||||
};
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ OC.L10N.register(
|
||||
"Add remote share" : "Entfernte Freigabe hinzufügen",
|
||||
"No ownCloud installation (7 or higher) found at {remote}" : "Keine OwnCloud-Installation (7 oder höher) auf {remote} gefunden",
|
||||
"Invalid ownCloud url" : "Ungültige OwnCloud-URL",
|
||||
"Share" : "Share",
|
||||
"Share" : "Teilen",
|
||||
"Shared by" : "Geteilt von ",
|
||||
"A file or folder was shared from <strong>another server</strong>" : "Eine Datei oder ein Ordner wurde von <strong>einem anderen Server</strong> geteilt",
|
||||
"A public shared file or folder was <strong>downloaded</strong>" : "Eine öffentliche geteilte Datei oder ein öffentlicher geteilter Ordner wurde <strong>heruntergeladen</strong>",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
"Add remote share" : "Entfernte Freigabe hinzufügen",
|
||||
"No ownCloud installation (7 or higher) found at {remote}" : "Keine OwnCloud-Installation (7 oder höher) auf {remote} gefunden",
|
||||
"Invalid ownCloud url" : "Ungültige OwnCloud-URL",
|
||||
"Share" : "Share",
|
||||
"Share" : "Teilen",
|
||||
"Shared by" : "Geteilt von ",
|
||||
"A file or folder was shared from <strong>another server</strong>" : "Eine Datei oder ein Ordner wurde von <strong>einem anderen Server</strong> geteilt",
|
||||
"A public shared file or folder was <strong>downloaded</strong>" : "Eine öffentliche geteilte Datei oder ein öffentlicher geteilter Ordner wurde <strong>heruntergeladen</strong>",
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
namespace OC\Files\Cache;
|
||||
|
||||
use OC\User\NoUserException;
|
||||
use OCP\Share_Backend_Collection;
|
||||
|
||||
/**
|
||||
@@ -53,7 +54,12 @@ class Shared_Cache extends Cache {
|
||||
}
|
||||
$source = \OC_Share_Backend_File::getSource($target, $this->storage->getMountPoint(), $this->storage->getItemType());
|
||||
if (isset($source['path']) && isset($source['fileOwner'])) {
|
||||
\OC\Files\Filesystem::initMountPoints($source['fileOwner']);
|
||||
try {
|
||||
\OC\Files\Filesystem::initMountPoints($source['fileOwner']);
|
||||
} catch(NoUserException $e) {
|
||||
\OC::$server->getLogger()->warning('The user \'' . $source['uid_owner'] . '\' of a share can\'t be retrieved.', array('app' => 'files_sharing'));
|
||||
return false;
|
||||
}
|
||||
$mounts = \OC\Files\Filesystem::getMountByNumericId($source['storage']);
|
||||
if (is_array($mounts) and !empty($mounts)) {
|
||||
$fullPath = $mounts[0]->getMountPoint() . $source['path'];
|
||||
@@ -394,6 +400,28 @@ class Shared_Cache extends Cache {
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* update the folder size and the size of all parent folders
|
||||
*
|
||||
* @param string|boolean $path
|
||||
* @param array $data (optional) meta data of the folder
|
||||
*/
|
||||
public function correctFolderSize($path, $data = null) {
|
||||
$this->calculateFolderSize($path, $data);
|
||||
if ($path !== '') {
|
||||
$parent = dirname($path);
|
||||
if ($parent === '.' or $parent === '/') {
|
||||
$parent = '';
|
||||
}
|
||||
$this->correctFolderSize($parent);
|
||||
} else {
|
||||
// bubble up to source cache
|
||||
$sourceCache = $this->getSourceCache($path);
|
||||
$parent = dirname($this->files[$path]);
|
||||
$sourceCache->correctFolderSize($parent);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* get the size of a folder and set it in the cache
|
||||
*
|
||||
|
||||
@@ -17,12 +17,12 @@ use OC_Files;
|
||||
use OC_Util;
|
||||
use OCP;
|
||||
use OCP\Template;
|
||||
use OCP\JSON;
|
||||
use OCP\Share;
|
||||
use OCP\AppFramework\Controller;
|
||||
use OCP\IRequest;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\AppFramework\Http\RedirectResponse;
|
||||
use OCP\AppFramework\Http\NotFoundResponse;
|
||||
use OC\URLGenerator;
|
||||
use OC\AppConfig;
|
||||
use OCP\ILogger;
|
||||
@@ -60,7 +60,7 @@ class ShareController extends Controller {
|
||||
* @param AppConfig $appConfig
|
||||
* @param OCP\IConfig $config
|
||||
* @param URLGenerator $urlGenerator
|
||||
* @param OC\User\Manager $userManager
|
||||
* @param OCP\IUserManager $userManager
|
||||
* @param ILogger $logger
|
||||
* @param OCP\Activity\IManager $activityManager
|
||||
*/
|
||||
@@ -70,7 +70,7 @@ class ShareController extends Controller {
|
||||
AppConfig $appConfig,
|
||||
OCP\IConfig $config,
|
||||
URLGenerator $urlGenerator,
|
||||
OC\User\Manager $userManager,
|
||||
OCP\IUserManager $userManager,
|
||||
ILogger $logger,
|
||||
OCP\Activity\IManager $activityManager) {
|
||||
parent::__construct($appName, $request);
|
||||
@@ -113,7 +113,7 @@ class ShareController extends Controller {
|
||||
public function authenticate($token, $password = '') {
|
||||
$linkItem = Share::getShareByToken($token, false);
|
||||
if($linkItem === false) {
|
||||
return new TemplateResponse('core', '404', array(), 'guest');
|
||||
return new NotFoundResponse();
|
||||
}
|
||||
|
||||
$authenticate = Helper::authenticate($linkItem, $password);
|
||||
@@ -139,18 +139,11 @@ class ShareController extends Controller {
|
||||
// Check whether share exists
|
||||
$linkItem = Share::getShareByToken($token, false);
|
||||
if($linkItem === false) {
|
||||
return new TemplateResponse('core', '404', array(), 'guest');
|
||||
return new NotFoundResponse();
|
||||
}
|
||||
|
||||
$shareOwner = $linkItem['uid_owner'];
|
||||
$originalSharePath = null;
|
||||
$rootLinkItem = OCP\Share::resolveReShare($linkItem);
|
||||
if (isset($rootLinkItem['uid_owner'])) {
|
||||
OCP\JSON::checkUserExists($rootLinkItem['uid_owner']);
|
||||
OC_Util::tearDownFS();
|
||||
OC_Util::setupFS($rootLinkItem['uid_owner']);
|
||||
$originalSharePath = Filesystem::getPath($linkItem['file_source']);
|
||||
}
|
||||
$originalSharePath = $this->getPath($token);
|
||||
|
||||
// Share is password protected - check whether the user is permitted to access the share
|
||||
if (isset($linkItem['share_with']) && !Helper::authenticate($linkItem)) {
|
||||
@@ -161,11 +154,13 @@ class ShareController extends Controller {
|
||||
if (Filesystem::isReadable($originalSharePath . $path)) {
|
||||
$getPath = Filesystem::normalizePath($path);
|
||||
$originalSharePath .= $path;
|
||||
} else {
|
||||
throw new OCP\Files\NotFoundException();
|
||||
}
|
||||
|
||||
$file = basename($originalSharePath);
|
||||
|
||||
$shareTmpl = array();
|
||||
$shareTmpl = [];
|
||||
$shareTmpl['displayName'] = User::getDisplayName($shareOwner);
|
||||
$shareTmpl['filename'] = $file;
|
||||
$shareTmpl['directory_path'] = $linkItem['file_target'];
|
||||
@@ -204,6 +199,7 @@ class ShareController extends Controller {
|
||||
|
||||
$shareTmpl['downloadURL'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.downloadShare', array('token' => $token));
|
||||
$shareTmpl['maxSizeAnimateGif'] = $this->config->getSystemValue('max_filesize_animated_gifs_public_sharing', 10);
|
||||
$shareTmpl['previewEnabled'] = $this->config->getSystemValue('enable_previews', true);
|
||||
|
||||
return new TemplateResponse($this->appName, 'public', $shareTmpl, 'base');
|
||||
}
|
||||
@@ -230,26 +226,48 @@ class ShareController extends Controller {
|
||||
}
|
||||
}
|
||||
|
||||
$originalSharePath = self::getPath($token);
|
||||
|
||||
if (isset($originalSharePath) && Filesystem::isReadable($originalSharePath . $path)) {
|
||||
$originalSharePath = Filesystem::normalizePath($originalSharePath . $path);
|
||||
$type = \OC\Files\Filesystem::is_dir($originalSharePath) ? 'folder' : 'file';
|
||||
$args = $type === 'folder' ? array('dir' => $originalSharePath) : array('dir' => dirname($originalSharePath), 'scrollto' => basename($originalSharePath));
|
||||
$linkToFile = \OCP\Util::linkToAbsolute('files', 'index.php', $args);
|
||||
$subject = $type === 'folder' ? Activity::SUBJECT_PUBLIC_SHARED_FOLDER_DOWNLOADED : Activity::SUBJECT_PUBLIC_SHARED_FILE_DOWNLOADED;
|
||||
$this->activityManager->publishActivity(
|
||||
'files_sharing', $subject, array($originalSharePath), '', array(), $originalSharePath,
|
||||
$linkToFile, $linkItem['uid_owner'], Activity::TYPE_PUBLIC_LINKS, Activity::PRIORITY_MEDIUM);
|
||||
}
|
||||
|
||||
$files_list = null;
|
||||
if (!is_null($files)) { // download selected files
|
||||
$files_list = json_decode($files);
|
||||
// in case we get only a single file
|
||||
if ($files_list === NULL) {
|
||||
if ($files_list === null) {
|
||||
$files_list = array($files);
|
||||
}
|
||||
}
|
||||
|
||||
$originalSharePath = self::getPath($token);
|
||||
|
||||
// Create the activities
|
||||
if (isset($originalSharePath) && Filesystem::isReadable($originalSharePath . $path)) {
|
||||
$originalSharePath = Filesystem::normalizePath($originalSharePath . $path);
|
||||
$isDir = \OC\Files\Filesystem::is_dir($originalSharePath);
|
||||
|
||||
$activities = [];
|
||||
if (!$isDir) {
|
||||
// Single file public share
|
||||
$activities[$originalSharePath] = Activity::SUBJECT_PUBLIC_SHARED_FILE_DOWNLOADED;
|
||||
} else if (!empty($files_list)) {
|
||||
// Only some files are downloaded
|
||||
foreach ($files_list as $file) {
|
||||
$filePath = Filesystem::normalizePath($originalSharePath . '/' . $file);
|
||||
$isDir = \OC\Files\Filesystem::is_dir($filePath);
|
||||
$activities[$filePath] = ($isDir) ? Activity::SUBJECT_PUBLIC_SHARED_FOLDER_DOWNLOADED : Activity::SUBJECT_PUBLIC_SHARED_FILE_DOWNLOADED;
|
||||
}
|
||||
} else {
|
||||
// The folder is downloaded
|
||||
$activities[$originalSharePath] = Activity::SUBJECT_PUBLIC_SHARED_FOLDER_DOWNLOADED;
|
||||
}
|
||||
|
||||
foreach ($activities as $filePath => $subject) {
|
||||
$this->activityManager->publishActivity(
|
||||
'files_sharing', $subject, array($filePath), '', array(),
|
||||
$filePath, '', $linkItem['uid_owner'], Activity::TYPE_PUBLIC_LINKS, Activity::PRIORITY_MEDIUM
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// download selected files
|
||||
if (!is_null($files)) {
|
||||
// FIXME: The exit is required here because otherwise the AppFramework is trying to add headers as well
|
||||
// after dispatching the request which results in a "Cannot modify header information" notice.
|
||||
OC_Files::get($originalSharePath, $files_list, $_SERVER['REQUEST_METHOD'] == 'HEAD');
|
||||
@@ -263,22 +281,29 @@ class ShareController extends Controller {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $token
|
||||
* @return null|string
|
||||
* @param string $token
|
||||
* @return string Resolved file path of the token
|
||||
* @throws \Exception In case share could not get properly resolved
|
||||
*/
|
||||
private function getPath($token) {
|
||||
$linkItem = Share::getShareByToken($token, false);
|
||||
$path = null;
|
||||
if (is_array($linkItem) && isset($linkItem['uid_owner'])) {
|
||||
// seems to be a valid share
|
||||
$rootLinkItem = Share::resolveReShare($linkItem);
|
||||
if (isset($rootLinkItem['uid_owner'])) {
|
||||
JSON::checkUserExists($rootLinkItem['uid_owner']);
|
||||
if(!$this->userManager->userExists($rootLinkItem['uid_owner'])) {
|
||||
throw new \Exception('Owner of the share does not exist anymore');
|
||||
}
|
||||
OC_Util::tearDownFS();
|
||||
OC_Util::setupFS($rootLinkItem['uid_owner']);
|
||||
$path = Filesystem::getPath($linkItem['file_source']);
|
||||
|
||||
if(!empty($path) && Filesystem::isReadable($path)) {
|
||||
return $path;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $path;
|
||||
|
||||
throw new \Exception('No file found belonging to file.');
|
||||
}
|
||||
}
|
||||
|
||||
117
apps/files_sharing/lib/external/manager.php
vendored
117
apps/files_sharing/lib/external/manager.php
vendored
@@ -9,6 +9,7 @@
|
||||
namespace OCA\Files_Sharing\External;
|
||||
|
||||
use OC\Files\Filesystem;
|
||||
use OCP\Files;
|
||||
|
||||
class Manager {
|
||||
const STORAGE = '\OCA\Files_Sharing\External\Storage';
|
||||
@@ -29,7 +30,7 @@ class Manager {
|
||||
private $mountManager;
|
||||
|
||||
/**
|
||||
* @var \OC\Files\Storage\StorageFactory
|
||||
* @var \OCP\Files\Storage\IStorageFactory
|
||||
*/
|
||||
private $storageLoader;
|
||||
|
||||
@@ -41,12 +42,12 @@ class Manager {
|
||||
/**
|
||||
* @param \OCP\IDBConnection $connection
|
||||
* @param \OC\Files\Mount\Manager $mountManager
|
||||
* @param \OC\Files\Storage\StorageFactory $storageLoader
|
||||
* @param \OCP\Files\Storage\IStorageFactory $storageLoader
|
||||
* @param \OC\HTTPHelper $httpHelper
|
||||
* @param string $uid
|
||||
*/
|
||||
public function __construct(\OCP\IDBConnection $connection, \OC\Files\Mount\Manager $mountManager,
|
||||
\OC\Files\Storage\StorageFactory $storageLoader, \OC\HTTPHelper $httpHelper, $uid) {
|
||||
\OCP\Files\Storage\IStorageFactory $storageLoader, \OC\HTTPHelper $httpHelper, $uid) {
|
||||
$this->connection = $connection;
|
||||
$this->mountManager = $mountManager;
|
||||
$this->storageLoader = $storageLoader;
|
||||
@@ -65,33 +66,64 @@ class Manager {
|
||||
* @param boolean $accepted
|
||||
* @param string $user
|
||||
* @param int $remoteId
|
||||
* @return mixed
|
||||
* @return Mount|null
|
||||
*/
|
||||
public function addShare($remote, $token, $password, $name, $owner, $accepted=false, $user = null, $remoteId = -1) {
|
||||
|
||||
$user = $user ? $user : $this->uid;
|
||||
$accepted = $accepted ? 1 : 0;
|
||||
$name = Filesystem::normalizePath('/' . $name);
|
||||
|
||||
$mountPoint = Filesystem::normalizePath('/' . $name);
|
||||
if (!$accepted) {
|
||||
// To avoid conflicts with the mount point generation later,
|
||||
// we only use a temporary mount point name here. The real
|
||||
// mount point name will be generated when accepting the share,
|
||||
// using the original share item name.
|
||||
$tmpMountPointName = '{{TemporaryMountPointName#' . $name . '}}';
|
||||
$mountPoint = $tmpMountPointName;
|
||||
$hash = md5($tmpMountPointName);
|
||||
$data = [
|
||||
'remote' => $remote,
|
||||
'share_token' => $token,
|
||||
'password' => $password,
|
||||
'name' => $name,
|
||||
'owner' => $owner,
|
||||
'user' => $user,
|
||||
'mountpoint' => $mountPoint,
|
||||
'mountpoint_hash' => $hash,
|
||||
'accepted' => $accepted,
|
||||
'remote_id' => $remoteId,
|
||||
];
|
||||
|
||||
$i = 1;
|
||||
while (!$this->connection->insertIfNotExist('*PREFIX*share_external', $data, ['user', 'mountpoint_hash'])) {
|
||||
// The external share already exists for the user
|
||||
$data['mountpoint'] = $tmpMountPointName . '-' . $i;
|
||||
$data['mountpoint_hash'] = md5($data['mountpoint']);
|
||||
$i++;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
$mountPoint = Files::buildNotExistingFileName('/', $name);
|
||||
$mountPoint = Filesystem::normalizePath('/' . $mountPoint);
|
||||
$hash = md5($mountPoint);
|
||||
|
||||
$query = $this->connection->prepare('
|
||||
INSERT INTO `*PREFIX*share_external`
|
||||
(`remote`, `share_token`, `password`, `name`, `owner`, `user`, `mountpoint`, `mountpoint_hash`, `accepted`, `remote_id`)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
');
|
||||
$hash = md5($mountPoint);
|
||||
$query->execute(array($remote, $token, $password, $name, $owner, $user, $mountPoint, $hash, $accepted, $remoteId));
|
||||
|
||||
if ($accepted) {
|
||||
$options = array(
|
||||
'remote' => $remote,
|
||||
'token' => $token,
|
||||
'password' => $password,
|
||||
'mountpoint' => $mountPoint,
|
||||
'owner' => $owner
|
||||
);
|
||||
return $this->mountShare($options);
|
||||
}
|
||||
$options = array(
|
||||
'remote' => $remote,
|
||||
'token' => $token,
|
||||
'password' => $password,
|
||||
'mountpoint' => $mountPoint,
|
||||
'owner' => $owner
|
||||
);
|
||||
return $this->mountShare($options);
|
||||
}
|
||||
|
||||
private function setupMounts() {
|
||||
@@ -124,7 +156,7 @@ class Manager {
|
||||
*/
|
||||
private function getShare($id) {
|
||||
$getShare = $this->connection->prepare('
|
||||
SELECT `remote`, `share_token`
|
||||
SELECT `remote`, `remote_id`, `share_token`, `name`
|
||||
FROM `*PREFIX*share_external`
|
||||
WHERE `id` = ? AND `user` = ?');
|
||||
$result = $getShare->execute(array($id, $this->uid));
|
||||
@@ -142,12 +174,18 @@ class Manager {
|
||||
$share = $this->getShare($id);
|
||||
|
||||
if ($share) {
|
||||
$mountPoint = Files::buildNotExistingFileName('/', $share['name']);
|
||||
$mountPoint = Filesystem::normalizePath('/' . $mountPoint);
|
||||
$hash = md5($mountPoint);
|
||||
|
||||
$acceptShare = $this->connection->prepare('
|
||||
UPDATE `*PREFIX*share_external`
|
||||
SET `accepted` = ?
|
||||
SET `accepted` = ?,
|
||||
`mountpoint` = ?,
|
||||
`mountpoint_hash` = ?
|
||||
WHERE `id` = ? AND `user` = ?');
|
||||
$acceptShare->execute(array(1, $id, $this->uid));
|
||||
$this->sendFeedbackToRemote($share['remote'], $share['share_token'], $id, 'accept');
|
||||
$acceptShare->execute(array(1, $mountPoint, $hash, $id, $this->uid));
|
||||
$this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'accept');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,7 +202,7 @@ class Manager {
|
||||
$removeShare = $this->connection->prepare('
|
||||
DELETE FROM `*PREFIX*share_external` WHERE `id` = ? AND `user` = ?');
|
||||
$removeShare->execute(array($id, $this->uid));
|
||||
$this->sendFeedbackToRemote($share['remote'], $share['share_token'], $id, 'decline');
|
||||
$this->sendFeedbackToRemote($share['remote'], $share['share_token'], $share['remote_id'], 'decline');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,13 +211,13 @@ class Manager {
|
||||
*
|
||||
* @param string $remote
|
||||
* @param string $token
|
||||
* @param int $id
|
||||
* @param int $remoteId Share id on the remote host
|
||||
* @param string $feedback
|
||||
* @return boolean
|
||||
*/
|
||||
private function sendFeedbackToRemote($remote, $token, $id, $feedback) {
|
||||
private function sendFeedbackToRemote($remote, $token, $remoteId, $feedback) {
|
||||
|
||||
$url = $remote . \OCP\Share::BASE_PATH_TO_SHARE_API . '/' . $id . '/' . $feedback . '?format=' . \OCP\Share::RESPONSE_FORMAT;
|
||||
$url = rtrim($remote, '/') . \OCP\Share::BASE_PATH_TO_SHARE_API . '/' . $remoteId . '/' . $feedback . '?format=' . \OCP\Share::RESPONSE_FORMAT;
|
||||
$fields = array('token' => $token);
|
||||
|
||||
$result = $this->httpHelper->post($url, $fields);
|
||||
@@ -315,10 +353,29 @@ class Manager {
|
||||
* @return array list of open server-to-server shares
|
||||
*/
|
||||
public function getOpenShares() {
|
||||
$openShares = $this->connection->prepare('SELECT * FROM `*PREFIX*share_external` WHERE `accepted` = ? AND `user` = ?');
|
||||
$result = $openShares->execute(array(0, $this->uid));
|
||||
|
||||
return $result ? $openShares->fetchAll() : array();
|
||||
|
||||
return $this->getShares(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* return a list of shares for the user
|
||||
*
|
||||
* @param bool|null $accepted True for accepted only,
|
||||
* false for not accepted,
|
||||
* null for all shares of the user
|
||||
* @return array list of open server-to-server shares
|
||||
*/
|
||||
private function getShares($accepted) {
|
||||
$query = 'SELECT * FROM `*PREFIX*share_external` WHERE `user` = ?';
|
||||
$parameters = [$this->uid];
|
||||
if (!is_null($accepted)) {
|
||||
$query .= ' AND `accepted` = ?';
|
||||
$parameters[] = (int) $accepted;
|
||||
}
|
||||
$query .= ' ORDER BY `id` ASC';
|
||||
|
||||
$shares = $this->connection->prepare($query);
|
||||
$result = $shares->execute($parameters);
|
||||
|
||||
return $result ? $shares->fetchAll() : [];
|
||||
}
|
||||
}
|
||||
|
||||
5
apps/files_sharing/lib/external/storage.php
vendored
5
apps/files_sharing/lib/external/storage.php
vendored
@@ -70,7 +70,7 @@ class Storage extends DAV implements ISharedStorage {
|
||||
'host' => $host,
|
||||
'root' => $root,
|
||||
'user' => $options['token'],
|
||||
'password' => $options['password']
|
||||
'password' => (string)$options['password']
|
||||
));
|
||||
}
|
||||
|
||||
@@ -237,7 +237,8 @@ class Storage extends DAV implements ISharedStorage {
|
||||
$errorMessage = curl_error($ch);
|
||||
curl_close($ch);
|
||||
if (!empty($errorMessage)) {
|
||||
throw new \Exception($errorMessage);
|
||||
\OCP\Util::writeLog('files_sharing', 'Error getting remote share info: ' . $errorMessage, \OCP\Util::ERROR);
|
||||
throw new StorageNotAvailableException($errorMessage);
|
||||
}
|
||||
|
||||
switch ($status) {
|
||||
|
||||
@@ -257,8 +257,21 @@ class Helper {
|
||||
*/
|
||||
public static function getShareFolder() {
|
||||
$shareFolder = \OC::$server->getConfig()->getSystemValue('share_folder', '/');
|
||||
$shareFolder = \OC\Files\Filesystem::normalizePath($shareFolder);
|
||||
|
||||
if (!\OC\Files\Filesystem::file_exists($shareFolder)) {
|
||||
$dir = '';
|
||||
$subdirs = explode('/', $shareFolder);
|
||||
foreach ($subdirs as $subdir) {
|
||||
$dir = $dir . '/' . $subdir;
|
||||
if (!\OC\Files\Filesystem::is_dir($dir)) {
|
||||
\OC\Files\Filesystem::mkdir($dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $shareFolder;
|
||||
|
||||
return \OC\Files\Filesystem::normalizePath($shareFolder);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
namespace OCA\Files_Sharing\Middleware;
|
||||
|
||||
use OCP\App\IAppManager;
|
||||
use OCP\AppFramework\Http\NotFoundResponse;
|
||||
use OCP\AppFramework\Middleware;
|
||||
use OCP\AppFramework\Http\TemplateResponse;
|
||||
use OCP\IConfig;
|
||||
@@ -59,7 +60,7 @@ class SharingCheckMiddleware extends Middleware {
|
||||
* @return TemplateResponse
|
||||
*/
|
||||
public function afterException($controller, $methodName, \Exception $exception){
|
||||
return new TemplateResponse('core', '404', array(), 'guest');
|
||||
return new NotFoundResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* Copyright (c) 2014 Robin Appelman <icewind@owncloud.com>
|
||||
* This file is licensed under the Affero General Public License version 3 or
|
||||
* later.
|
||||
* See the COPYING-README file.
|
||||
*/
|
||||
|
||||
namespace OCA\Files_Sharing;
|
||||
|
||||
use OC\Files\Cache\Cache;
|
||||
|
||||
class ReadOnlyCache extends Cache {
|
||||
public function get($path) {
|
||||
$data = parent::get($path);
|
||||
$data['permissions'] &= (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_SHARE);
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getFolderContents($path) {
|
||||
$content = parent::getFolderContents($path);
|
||||
foreach ($content as &$data) {
|
||||
$data['permissions'] &= (\OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_SHARE);
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,9 @@
|
||||
|
||||
namespace OCA\Files_Sharing;
|
||||
|
||||
use OC\Files\Cache\Wrapper\CachePermissionsMask;
|
||||
use OC\Files\Storage\Wrapper\Wrapper;
|
||||
use OCP\Constants;
|
||||
|
||||
class ReadOnlyWrapper extends Wrapper {
|
||||
public function isUpdatable($path) {
|
||||
@@ -51,6 +53,7 @@ class ReadOnlyWrapper extends Wrapper {
|
||||
if (!$storage) {
|
||||
$storage = $this;
|
||||
}
|
||||
return new ReadOnlyCache($storage);
|
||||
$sourceCache = $this->storage->getCache($path, $storage);
|
||||
return new CachePermissionsMask($sourceCache, Constants::PERMISSION_READ | Constants::PERMISSION_SHARE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,8 +35,8 @@ class SharedMount extends MountPoint implements MoveableMount {
|
||||
$mountPoint = basename($share['file_target']);
|
||||
$parent = dirname($share['file_target']);
|
||||
|
||||
while (!\OC\Files\Filesystem::is_dir($parent)) {
|
||||
$parent = dirname($parent);
|
||||
if (!\OC\Files\Filesystem::is_dir($parent)) {
|
||||
$parent = Helper::getShareFolder();
|
||||
}
|
||||
|
||||
$newMountPoint = \OCA\Files_Sharing\Helper::generateUniqueTarget(
|
||||
|
||||
@@ -293,26 +293,34 @@ class Shared extends \OC\Files\Storage\Common implements ISharedStorage {
|
||||
// we need the paths relative to data/user/files
|
||||
$relPath1 = $this->getMountPoint() . '/' . $path1;
|
||||
$relPath2 = $this->getMountPoint() . '/' . $path2;
|
||||
$pathinfo = pathinfo($relPath1);
|
||||
|
||||
// check for update permissions on the share
|
||||
if ($this->isUpdatable('')) {
|
||||
|
||||
$pathinfo = pathinfo($relPath1);
|
||||
// for part files we need to ask for the owner and path from the parent directory because
|
||||
// the file cache doesn't return any results for part files
|
||||
if (isset($pathinfo['extension']) && $pathinfo['extension'] === 'part') {
|
||||
list($user1, $path1) = \OCA\Files_Sharing\Helper::getUidAndFilename($pathinfo['dirname']);
|
||||
$path1 = $path1 . '/' . $pathinfo['basename'];
|
||||
} else {
|
||||
list($user1, $path1) = \OCA\Files_Sharing\Helper::getUidAndFilename($relPath1);
|
||||
$isPartFile = (isset($pathinfo['extension']) && $pathinfo['extension'] === 'part');
|
||||
$targetExists = $this->file_exists($path2);
|
||||
$sameFolder = (dirname($relPath1) === dirname($relPath2));
|
||||
if ($targetExists || ($sameFolder && !$isPartFile)) {
|
||||
// note that renaming a share mount point is always allowed
|
||||
if (!$this->isUpdatable('')) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (!$this->isCreatable('')) {
|
||||
return false;
|
||||
}
|
||||
$targetFilename = basename($relPath2);
|
||||
list($user2, $path2) = \OCA\Files_Sharing\Helper::getUidAndFilename(dirname($relPath2));
|
||||
$rootView = new \OC\Files\View('');
|
||||
return $rootView->rename('/' . $user1 . '/files/' . $path1, '/' . $user2 . '/files/' . $path2 . '/' . $targetFilename);
|
||||
}
|
||||
|
||||
return false;
|
||||
// for part files we need to ask for the owner and path from the parent directory because
|
||||
// the file cache doesn't return any results for part files
|
||||
if ($isPartFile) {
|
||||
list($user1, $path1) = \OCA\Files_Sharing\Helper::getUidAndFilename($pathinfo['dirname']);
|
||||
$path1 = $path1 . '/' . $pathinfo['basename'];
|
||||
} else {
|
||||
list($user1, $path1) = \OCA\Files_Sharing\Helper::getUidAndFilename($relPath1);
|
||||
}
|
||||
$targetFilename = basename($relPath2);
|
||||
list($user2, $path2) = \OCA\Files_Sharing\Helper::getUidAndFilename(dirname($relPath2));
|
||||
$rootView = new \OC\Files\View('');
|
||||
return $rootView->rename('/' . $user1 . '/files/' . $path1, '/' . $user2 . '/files/' . $path2 . '/' . $targetFilename);
|
||||
}
|
||||
|
||||
public function copy($path1, $path2) {
|
||||
@@ -343,13 +351,25 @@ class Shared extends \OC\Files\Storage\Common implements ISharedStorage {
|
||||
case 'xb':
|
||||
case 'a':
|
||||
case 'ab':
|
||||
$exists = $this->file_exists($path);
|
||||
if ($exists && !$this->isUpdatable($path)) {
|
||||
$creatable = $this->isCreatable($path);
|
||||
$updatable = $this->isUpdatable($path);
|
||||
// if neither permissions given, no need to continue
|
||||
if (!$creatable && !$updatable) {
|
||||
return false;
|
||||
}
|
||||
if (!$exists && !$this->isCreatable(dirname($path))) {
|
||||
|
||||
$exists = $this->file_exists($path);
|
||||
// if a file exists, updatable permissions are required
|
||||
if ($exists && !$updatable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// part file is allowed if !$creatable but the final file is $updatable
|
||||
if (pathinfo($path, PATHINFO_EXTENSION) !== 'part') {
|
||||
if (!$exists && !$creatable) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
$info = array(
|
||||
'target' => $this->getMountPoint() . $path,
|
||||
|
||||
@@ -145,10 +145,10 @@ class Shared_Updater {
|
||||
$shareType = $params['shareType'];
|
||||
|
||||
if ($shareType === \OCP\Share::SHARE_TYPE_USER) {
|
||||
self::correctUsersFolder($shareWith, '/');
|
||||
self::correctUsersFolder($shareWith, $params['fileTarget']);
|
||||
} elseif ($shareType === \OCP\Share::SHARE_TYPE_GROUP) {
|
||||
foreach (\OC_Group::usersInGroup($shareWith) as $user) {
|
||||
self::correctUsersFolder($user, '/');
|
||||
self::correctUsersFolder($user, $params['fileTarget']);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,7 +40,8 @@ $server->addPlugin(new OC_Connector_Sabre_ExceptionLoggerPlugin('webdav'));
|
||||
// wait with registering these until auth is handled and the filesystem is setup
|
||||
$server->subscribeEvent('beforeMethod', function () use ($server, $objectTree, $authBackend) {
|
||||
$share = $authBackend->getShare();
|
||||
$owner = $share['uid_owner'];
|
||||
$rootShare = \OCP\Share::resolveReShare($share);
|
||||
$owner = $rootShare['uid_owner'];
|
||||
$isWritable = $share['permissions'] & (\OCP\Constants::PERMISSION_UPDATE | \OCP\Constants::PERMISSION_CREATE);
|
||||
$fileId = $share['file_source'];
|
||||
|
||||
|
||||
@@ -80,7 +80,7 @@ $previewSupported = OC\Preview::isMimeSupported($_['mimetype']) ? 'true' : 'fals
|
||||
<?php if (isset($_['folder'])): ?>
|
||||
<?php print_unescaped($_['folder']); ?>
|
||||
<?php else: ?>
|
||||
<?php if (substr($_['mimetype'], 0, strpos($_['mimetype'], '/')) == 'video'): ?>
|
||||
<?php if ($_['previewEnabled'] && substr($_['mimetype'], 0, strpos($_['mimetype'], '/')) == 'video'): ?>
|
||||
<div id="imgframe">
|
||||
<video tabindex="0" controls="" preload="none">
|
||||
<source src="<?php p($_['downloadURL']); ?>" type="<?php p($_['mimetype']); ?>" />
|
||||
@@ -91,7 +91,7 @@ $previewSupported = OC\Preview::isMimeSupported($_['mimetype']) ? 'true' : 'fals
|
||||
<div id="imgframe"></div>
|
||||
<?php endif; ?>
|
||||
<div class="directDownload">
|
||||
<a href="<?php p($_['downloadURL']); ?>" id="download" class="button">
|
||||
<a href="<?php p($_['downloadURL']); ?>" id="downloadFile" class="button">
|
||||
<img class="svg" alt="" src="<?php print_unescaped(OCP\image_path("core", "actions/download.svg")); ?>"/>
|
||||
<?php p($l->t('Download %s', array($_['filename'])))?> (<?php p($_['fileSize']) ?>)
|
||||
</a>
|
||||
|
||||
@@ -851,7 +851,6 @@ class Test_Files_Sharing_Api extends TestCase {
|
||||
$this->assertEquals('1', $newUserShare['permissions']);
|
||||
|
||||
// update password for link share
|
||||
|
||||
$this->assertTrue(empty($linkShare['share_with']));
|
||||
|
||||
$params = array();
|
||||
@@ -876,6 +875,29 @@ class Test_Files_Sharing_Api extends TestCase {
|
||||
$this->assertTrue(is_array($newLinkShare));
|
||||
$this->assertTrue(!empty($newLinkShare['share_with']));
|
||||
|
||||
// Remove password for link share
|
||||
$params = array();
|
||||
$params['id'] = $linkShare['id'];
|
||||
$params['_put'] = array();
|
||||
$params['_put']['password'] = '';
|
||||
|
||||
$result = \OCA\Files_Sharing\API\Local::updateShare($params);
|
||||
|
||||
$this->assertTrue($result->succeeded());
|
||||
|
||||
$items = \OCP\Share::getItemShared('file', $linkShare['file_source']);
|
||||
|
||||
$newLinkShare = null;
|
||||
foreach ($items as $item) {
|
||||
if ($item['share_type'] === \OCP\Share::SHARE_TYPE_LINK) {
|
||||
$newLinkShare = $item;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->assertTrue(is_array($newLinkShare));
|
||||
$this->assertTrue(empty($newLinkShare['share_with']));
|
||||
|
||||
\OCP\Share::unshare('file', $fileInfo['fileid'], \OCP\Share::SHARE_TYPE_USER,
|
||||
\Test_Files_Sharing_Api::TEST_FILES_SHARING_API_USER2);
|
||||
|
||||
@@ -1008,6 +1030,24 @@ class Test_Files_Sharing_Api extends TestCase {
|
||||
$this->assertTrue(is_array($updatedLinkShare));
|
||||
$this->assertEquals($dateWithinRange->format('Y-m-d') . ' 00:00:00', $updatedLinkShare['expiration']);
|
||||
|
||||
|
||||
// Try to remove expire date
|
||||
$params = array();
|
||||
$params['id'] = $linkShare['id'];
|
||||
$params['_put'] = ['expireDate' => ''];
|
||||
|
||||
$result = \OCA\Files_Sharing\API\Local::updateShare($params);
|
||||
|
||||
$this->assertFalse($result->succeeded());
|
||||
|
||||
$items = \OCP\Share::getItemShared('file', $linkShare['file_source']);
|
||||
|
||||
$updatedLinkShare = reset($items);
|
||||
|
||||
// date shouldn't be changed
|
||||
$this->assertTrue(is_array($updatedLinkShare));
|
||||
$this->assertEquals($dateWithinRange->format('Y-m-d') . ' 00:00:00', $updatedLinkShare['expiration']);
|
||||
|
||||
// cleanup
|
||||
$config->setAppValue('core', 'shareapi_default_expire_date', 'no');
|
||||
$config->setAppValue('core', 'shareapi_enforce_expire_date', 'no');
|
||||
|
||||
@@ -12,6 +12,7 @@ namespace OCA\Files_Sharing\Controllers;
|
||||
|
||||
use OC\Files\Filesystem;
|
||||
use OCA\Files_Sharing\Application;
|
||||
use OCP\AppFramework\Http\NotFoundResponse;
|
||||
use OCP\AppFramework\IAppContainer;
|
||||
use OCP\Files;
|
||||
use OCP\AppFramework\Http\RedirectResponse;
|
||||
@@ -49,6 +50,8 @@ class ShareControllerTest extends \PHPUnit_Framework_TestCase {
|
||||
->disableOriginalConstructor()->getMock();
|
||||
$this->container['URLGenerator'] = $this->getMockBuilder('\OC\URLGenerator')
|
||||
->disableOriginalConstructor()->getMock();
|
||||
$this->container['UserManager'] = $this->getMockBuilder('\OCP\IUserManager')
|
||||
->disableOriginalConstructor()->getMock();
|
||||
$this->urlGenerator = $this->container['URLGenerator'];
|
||||
$this->shareController = $this->container['ShareController'];
|
||||
|
||||
@@ -115,7 +118,7 @@ class ShareControllerTest extends \PHPUnit_Framework_TestCase {
|
||||
public function testAuthenticate() {
|
||||
// Test without a not existing token
|
||||
$response = $this->shareController->authenticate('ThisTokenShouldHopefullyNeverExistSoThatTheUnitTestWillAlwaysPass :)');
|
||||
$expectedResponse = new TemplateResponse('core', '404', array(), 'guest');
|
||||
$expectedResponse = new NotFoundResponse();
|
||||
$this->assertEquals($expectedResponse, $response);
|
||||
|
||||
// Test with a valid password
|
||||
@@ -130,9 +133,14 @@ class ShareControllerTest extends \PHPUnit_Framework_TestCase {
|
||||
}
|
||||
|
||||
public function testShowShare() {
|
||||
$this->container['UserManager']->expects($this->exactly(2))
|
||||
->method('userExists')
|
||||
->with($this->user)
|
||||
->will($this->returnValue(true));
|
||||
|
||||
// Test without a not existing token
|
||||
$response = $this->shareController->showShare('ThisTokenShouldHopefullyNeverExistSoThatTheUnitTestWillAlwaysPass :)');
|
||||
$expectedResponse = new TemplateResponse('core', '404', array(), 'guest');
|
||||
$expectedResponse = new NotFoundResponse();
|
||||
$this->assertEquals($expectedResponse, $response);
|
||||
|
||||
// Test with a password protected share and no authentication
|
||||
@@ -158,6 +166,7 @@ class ShareControllerTest extends \PHPUnit_Framework_TestCase {
|
||||
'fileSize' => '33 B',
|
||||
'nonHumanFileSize' => 33,
|
||||
'maxSizeAnimateGif' => 10,
|
||||
'previewEnabled' => true,
|
||||
);
|
||||
$expectedResponse = new TemplateResponse($this->container['AppName'], 'public', $sharedTmplParams, 'base');
|
||||
$this->assertEquals($expectedResponse, $response);
|
||||
@@ -170,4 +179,54 @@ class ShareControllerTest extends \PHPUnit_Framework_TestCase {
|
||||
array('token' => $this->token)));
|
||||
$this->assertEquals($expectedResponse, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Exception
|
||||
* @expectedExceptionMessage No file found belonging to file.
|
||||
*/
|
||||
public function testShowShareWithDeletedFile() {
|
||||
$this->container['UserManager']->expects($this->once())
|
||||
->method('userExists')
|
||||
->with($this->user)
|
||||
->will($this->returnValue(true));
|
||||
|
||||
$view = new View('/'. $this->user . '/files');
|
||||
$view->unlink('file1.txt');
|
||||
$linkItem = Share::getShareByToken($this->token, false);
|
||||
\OC::$server->getSession()->set('public_link_authenticated', $linkItem['id']);
|
||||
$this->shareController->showShare($this->token);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Exception
|
||||
* @expectedExceptionMessage No file found belonging to file.
|
||||
*/
|
||||
public function testDownloadShareWithDeletedFile() {
|
||||
$this->container['UserManager']->expects($this->once())
|
||||
->method('userExists')
|
||||
->with($this->user)
|
||||
->will($this->returnValue(true));
|
||||
|
||||
$view = new View('/'. $this->user . '/files');
|
||||
$view->unlink('file1.txt');
|
||||
$linkItem = Share::getShareByToken($this->token, false);
|
||||
\OC::$server->getSession()->set('public_link_authenticated', $linkItem['id']);
|
||||
$this->shareController->downloadShare($this->token);
|
||||
}
|
||||
|
||||
/**
|
||||
* @expectedException \Exception
|
||||
* @expectedExceptionMessage Owner of the share does not exist anymore
|
||||
*/
|
||||
public function testShowShareWithNotExistingUser() {
|
||||
$this->container['UserManager']->expects($this->once())
|
||||
->method('userExists')
|
||||
->with($this->user)
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$linkItem = Share::getShareByToken($this->token, false);
|
||||
\OC::$server->getSession()->set('public_link_authenticated', $linkItem['id']);
|
||||
$this->shareController->showShare($this->token);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
216
apps/files_sharing/tests/external/managertest.php
vendored
Normal file
216
apps/files_sharing/tests/external/managertest.php
vendored
Normal file
@@ -0,0 +1,216 @@
|
||||
<?php
|
||||
/**
|
||||
* ownCloud
|
||||
*
|
||||
* @author Joas Schilling
|
||||
* @copyright 2015 Joas Schilling <nickvergessen@owncloud.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This library 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 library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Files_Sharing\Tests\External;
|
||||
|
||||
use OC\Files\Storage\StorageFactory;
|
||||
use OCA\Files_Sharing\External\Manager;
|
||||
use OCA\Files_Sharing\Tests\TestCase;
|
||||
|
||||
class ManagerTest extends TestCase {
|
||||
|
||||
/** @var Manager **/
|
||||
private $manager;
|
||||
|
||||
/** @var \OC\Files\Mount\Manager */
|
||||
private $mountManager;
|
||||
|
||||
/** @var \PHPUnit_Framework_MockObject_MockObject */
|
||||
private $httpHelper;
|
||||
|
||||
private $uid;
|
||||
|
||||
protected function setUp() {
|
||||
parent::setUp();
|
||||
|
||||
$this->uid = $this->getUniqueID('user');
|
||||
$this->mountManager = new \OC\Files\Mount\Manager();
|
||||
$this->httpHelper = $httpHelper = $this->getMockBuilder('\OC\HTTPHelper')->disableOriginalConstructor()->getMock();
|
||||
/** @var \OC\HTTPHelper $httpHelper */
|
||||
$this->manager = new Manager(
|
||||
\OC::$server->getDatabaseConnection(),
|
||||
$this->mountManager,
|
||||
new StorageFactory(),
|
||||
$httpHelper,
|
||||
$this->uid
|
||||
);
|
||||
}
|
||||
|
||||
public function testAddShare() {
|
||||
|
||||
$shareData1 = [
|
||||
'remote' => 'http://localhost',
|
||||
'token' => 'token1',
|
||||
'password' => '',
|
||||
'name' => '/SharedFolder',
|
||||
'owner' => 'foobar',
|
||||
'accepted' => false,
|
||||
'user' => $this->uid,
|
||||
];
|
||||
$shareData2 = $shareData1;
|
||||
$shareData2['token'] = 'token2';
|
||||
$shareData3 = $shareData1;
|
||||
$shareData3['token'] = 'token3';
|
||||
|
||||
// Add a share for "user"
|
||||
$this->assertSame(null, call_user_func_array([$this->manager, 'addShare'], $shareData1));
|
||||
$openShares = $this->manager->getOpenShares();
|
||||
$this->assertCount(1, $openShares);
|
||||
$this->assertExternalShareEntry($shareData1, $openShares[0], 1, '{{TemporaryMountPointName#' . $shareData1['name'] . '}}');
|
||||
|
||||
\Test_Helper::invokePrivate($this->manager, 'setupMounts');
|
||||
$this->assertNotMount('SharedFolder');
|
||||
$this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}');
|
||||
|
||||
// Add a second share for "user" with the same name
|
||||
$this->assertSame(null, call_user_func_array([$this->manager, 'addShare'], $shareData2));
|
||||
$openShares = $this->manager->getOpenShares();
|
||||
$this->assertCount(2, $openShares);
|
||||
$this->assertExternalShareEntry($shareData1, $openShares[0], 1, '{{TemporaryMountPointName#' . $shareData1['name'] . '}}');
|
||||
// New share falls back to "-1" appendix, because the name is already taken
|
||||
$this->assertExternalShareEntry($shareData2, $openShares[1], 2, '{{TemporaryMountPointName#' . $shareData2['name'] . '}}-1');
|
||||
|
||||
\Test_Helper::invokePrivate($this->manager, 'setupMounts');
|
||||
$this->assertNotMount('SharedFolder');
|
||||
$this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}');
|
||||
$this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}-1');
|
||||
|
||||
$this->httpHelper->expects($this->at(0))
|
||||
->method('post')
|
||||
->with($this->stringStartsWith('http://localhost/ocs/v1.php/cloud/shares/' . $openShares[0]['remote_id']), $this->anything());
|
||||
|
||||
// Accept the first share
|
||||
$this->manager->acceptShare($openShares[0]['id']);
|
||||
|
||||
// Check remaining shares - Accepted
|
||||
$acceptedShares = \Test_Helper::invokePrivate($this->manager, 'getShares', [true]);
|
||||
$this->assertCount(1, $acceptedShares);
|
||||
$shareData1['accepted'] = true;
|
||||
$this->assertExternalShareEntry($shareData1, $acceptedShares[0], 1, $shareData1['name']);
|
||||
// Check remaining shares - Open
|
||||
$openShares = $this->manager->getOpenShares();
|
||||
$this->assertCount(1, $openShares);
|
||||
$this->assertExternalShareEntry($shareData2, $openShares[0], 2, '{{TemporaryMountPointName#' . $shareData2['name'] . '}}-1');
|
||||
|
||||
\Test_Helper::invokePrivate($this->manager, 'setupMounts');
|
||||
$this->assertMount($shareData1['name']);
|
||||
$this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}');
|
||||
$this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}-1');
|
||||
|
||||
// Add another share for "user" with the same name
|
||||
$this->assertSame(null, call_user_func_array([$this->manager, 'addShare'], $shareData3));
|
||||
$openShares = $this->manager->getOpenShares();
|
||||
$this->assertCount(2, $openShares);
|
||||
$this->assertExternalShareEntry($shareData2, $openShares[0], 2, '{{TemporaryMountPointName#' . $shareData2['name'] . '}}-1');
|
||||
// New share falls back to the original name (no "-\d", because the name is not taken)
|
||||
$this->assertExternalShareEntry($shareData3, $openShares[1], 3, '{{TemporaryMountPointName#' . $shareData3['name'] . '}}');
|
||||
|
||||
\Test_Helper::invokePrivate($this->manager, 'setupMounts');
|
||||
$this->assertMount($shareData1['name']);
|
||||
$this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}');
|
||||
$this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}-1');
|
||||
|
||||
$this->httpHelper->expects($this->at(0))
|
||||
->method('post')
|
||||
->with($this->stringStartsWith('http://localhost/ocs/v1.php/cloud/shares/' . $openShares[1]['remote_id'] . '/decline'), $this->anything());
|
||||
|
||||
// Decline the third share
|
||||
$this->manager->declineShare($openShares[1]['id']);
|
||||
|
||||
\Test_Helper::invokePrivate($this->manager, 'setupMounts');
|
||||
$this->assertMount($shareData1['name']);
|
||||
$this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}');
|
||||
$this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}-1');
|
||||
|
||||
// Check remaining shares - Accepted
|
||||
$acceptedShares = \Test_Helper::invokePrivate($this->manager, 'getShares', [true]);
|
||||
$this->assertCount(1, $acceptedShares);
|
||||
$shareData1['accepted'] = true;
|
||||
$this->assertExternalShareEntry($shareData1, $acceptedShares[0], 1, $shareData1['name']);
|
||||
// Check remaining shares - Open
|
||||
$openShares = $this->manager->getOpenShares();
|
||||
$this->assertCount(1, $openShares);
|
||||
$this->assertExternalShareEntry($shareData2, $openShares[0], 2, '{{TemporaryMountPointName#' . $shareData2['name'] . '}}-1');
|
||||
|
||||
\Test_Helper::invokePrivate($this->manager, 'setupMounts');
|
||||
$this->assertMount($shareData1['name']);
|
||||
$this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}');
|
||||
$this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}-1');
|
||||
|
||||
$this->httpHelper->expects($this->at(0))
|
||||
->method('post')
|
||||
->with($this->stringStartsWith('http://localhost/ocs/v1.php/cloud/shares/' . $openShares[0]['remote_id'] . '/decline'), $this->anything());
|
||||
$this->httpHelper->expects($this->at(1))
|
||||
->method('post')
|
||||
->with($this->stringStartsWith('http://localhost/ocs/v1.php/cloud/shares/' . $acceptedShares[0]['remote_id'] . '/decline'), $this->anything());
|
||||
|
||||
$this->manager->removeUserShares($this->uid);
|
||||
$this->assertEmpty(\Test_Helper::invokePrivate($this->manager, 'getShares', [null]), 'Asserting all shares for the user have been deleted');
|
||||
|
||||
$this->mountManager->clear();
|
||||
\Test_Helper::invokePrivate($this->manager, 'setupMounts');
|
||||
$this->assertNotMount($shareData1['name']);
|
||||
$this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}');
|
||||
$this->assertNotMount('{{TemporaryMountPointName#' . $shareData1['name'] . '}}-1');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $expected
|
||||
* @param array $actual
|
||||
* @param int $share
|
||||
* @param string $mountPoint
|
||||
*/
|
||||
protected function assertExternalShareEntry($expected, $actual, $share, $mountPoint) {
|
||||
$this->assertEquals($expected['remote'], $actual['remote'], 'Asserting remote of a share #' . $share);
|
||||
$this->assertEquals($expected['token'], $actual['share_token'], 'Asserting token of a share #' . $share);
|
||||
$this->assertEquals($expected['name'], $actual['name'], 'Asserting name of a share #' . $share);
|
||||
$this->assertEquals($expected['owner'], $actual['owner'], 'Asserting owner of a share #' . $share);
|
||||
$this->assertEquals($expected['accepted'], (int) $actual['accepted'], 'Asserting accept of a share #' . $share);
|
||||
$this->assertEquals($expected['user'], $actual['user'], 'Asserting user of a share #' . $share);
|
||||
$this->assertEquals($mountPoint, $actual['mountpoint'], 'Asserting mountpoint of a share #' . $share);
|
||||
}
|
||||
|
||||
private function assertMount($mountPoint) {
|
||||
$mountPoint = rtrim($mountPoint, '/');
|
||||
$mount = $this->mountManager->find($this->getFullPath($mountPoint));
|
||||
$this->assertInstanceOf('\OCA\Files_Sharing\External\Mount', $mount);
|
||||
$this->assertInstanceOf('\OCP\Files\Mount\IMountPoint', $mount);
|
||||
$this->assertEquals($this->getFullPath($mountPoint), rtrim($mount->getMountPoint(), '/'));
|
||||
$storage = $mount->getStorage();
|
||||
$this->assertInstanceOf('\OCA\Files_Sharing\External\Storage', $storage);
|
||||
}
|
||||
|
||||
private function assertNotMount($mountPoint) {
|
||||
$mountPoint = rtrim($mountPoint, '/');
|
||||
$mount = $this->mountManager->find($this->getFullPath($mountPoint));
|
||||
if ($mount) {
|
||||
$this->assertInstanceOf('\OCP\Files\Mount\IMountPoint', $mount);
|
||||
$this->assertNotEquals($this->getFullPath($mountPoint), rtrim($mount->getMountPoint(), '/'));
|
||||
} else {
|
||||
$this->assertNull($mount);
|
||||
}
|
||||
}
|
||||
|
||||
private function getFullPath($path) {
|
||||
return '/' . $this->uid . '/files' . $path;
|
||||
}
|
||||
}
|
||||
@@ -30,9 +30,11 @@ class Test_Files_Sharing_Helper extends TestCase {
|
||||
function testSetGetShareFolder() {
|
||||
$this->assertSame('/', \OCA\Files_Sharing\Helper::getShareFolder());
|
||||
|
||||
\OCA\Files_Sharing\Helper::setShareFolder('/Shared');
|
||||
\OCA\Files_Sharing\Helper::setShareFolder('/Shared/Folder');
|
||||
|
||||
$this->assertSame('/Shared', \OCA\Files_Sharing\Helper::getShareFolder());
|
||||
$sharedFolder = \OCA\Files_Sharing\Helper::getShareFolder();
|
||||
$this->assertSame('/Shared/Folder', \OCA\Files_Sharing\Helper::getShareFolder());
|
||||
$this->assertTrue(\OC\Files\Filesystem::is_dir($sharedFolder));
|
||||
|
||||
// cleanup
|
||||
\OC::$server->getConfig()->deleteSystemValue('share_folder');
|
||||
|
||||
90
apps/files_sharing/tests/propagation.php
Normal file
90
apps/files_sharing/tests/propagation.php
Normal file
@@ -0,0 +1,90 @@
|
||||
<?php
|
||||
/**
|
||||
* ownCloud
|
||||
*
|
||||
* @author Robin Appelman
|
||||
* @copyright 2015 Robin Appelman <icewind@owncloud.com>
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
||||
* License as published by the Free Software Foundation; either
|
||||
* version 3 of the License, or any later version.
|
||||
*
|
||||
* This library 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 library. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace OCA\Files_sharing\Tests;
|
||||
|
||||
use OC\Files\View;
|
||||
|
||||
class Propagation extends TestCase {
|
||||
|
||||
public function testSizePropagationWhenOwnerChangesFile() {
|
||||
$this->loginAsUser(self::TEST_FILES_SHARING_API_USER1);
|
||||
$recipientView = new View('/' . self::TEST_FILES_SHARING_API_USER1 . '/files');
|
||||
|
||||
$this->loginAsUser(self::TEST_FILES_SHARING_API_USER2);
|
||||
$ownerView = new View('/' . self::TEST_FILES_SHARING_API_USER2 . '/files');
|
||||
$ownerView->mkdir('/sharedfolder/subfolder');
|
||||
$ownerView->file_put_contents('/sharedfolder/subfolder/foo.txt', 'bar');
|
||||
|
||||
$sharedFolderInfo = $ownerView->getFileInfo('/sharedfolder', false);
|
||||
\OCP\Share::shareItem('folder', $sharedFolderInfo->getId(), \OCP\Share::SHARE_TYPE_USER, self::TEST_FILES_SHARING_API_USER1, 31);
|
||||
$ownerRootInfo = $ownerView->getFileInfo('', false);
|
||||
|
||||
$this->loginAsUser(self::TEST_FILES_SHARING_API_USER1);
|
||||
$this->assertTrue($recipientView->file_exists('/sharedfolder/subfolder/foo.txt'));
|
||||
$recipientRootInfo = $recipientView->getFileInfo('', false);
|
||||
|
||||
// when file changed as owner
|
||||
$this->loginAsUser(self::TEST_FILES_SHARING_API_USER2);
|
||||
$ownerView->file_put_contents('/sharedfolder/subfolder/foo.txt', 'foobar');
|
||||
|
||||
// size of recipient's root stays the same
|
||||
$this->loginAsUser(self::TEST_FILES_SHARING_API_USER1);
|
||||
$newRecipientRootInfo = $recipientView->getFileInfo('', false);
|
||||
$this->assertEquals($recipientRootInfo->getSize(), $newRecipientRootInfo->getSize());
|
||||
|
||||
// size of owner's root increases
|
||||
$this->loginAsUser(self::TEST_FILES_SHARING_API_USER2);
|
||||
$newOwnerRootInfo = $ownerView->getFileInfo('', false);
|
||||
$this->assertEquals($ownerRootInfo->getSize() + 3, $newOwnerRootInfo->getSize());
|
||||
}
|
||||
|
||||
public function testSizePropagationWhenRecipientChangesFile() {
|
||||
$this->loginAsUser(self::TEST_FILES_SHARING_API_USER1);
|
||||
$recipientView = new View('/' . self::TEST_FILES_SHARING_API_USER1 . '/files');
|
||||
|
||||
$this->loginAsUser(self::TEST_FILES_SHARING_API_USER2);
|
||||
$ownerView = new View('/' . self::TEST_FILES_SHARING_API_USER2 . '/files');
|
||||
$ownerView->mkdir('/sharedfolder/subfolder');
|
||||
$ownerView->file_put_contents('/sharedfolder/subfolder/foo.txt', 'bar');
|
||||
|
||||
$sharedFolderInfo = $ownerView->getFileInfo('/sharedfolder', false);
|
||||
\OCP\Share::shareItem('folder', $sharedFolderInfo->getId(), \OCP\Share::SHARE_TYPE_USER, self::TEST_FILES_SHARING_API_USER1, 31);
|
||||
$ownerRootInfo = $ownerView->getFileInfo('', false);
|
||||
|
||||
$this->loginAsUser(self::TEST_FILES_SHARING_API_USER1);
|
||||
$this->assertTrue($recipientView->file_exists('/sharedfolder/subfolder/foo.txt'));
|
||||
$recipientRootInfo = $recipientView->getFileInfo('', false);
|
||||
|
||||
// when file changed as recipient
|
||||
$recipientView->file_put_contents('/sharedfolder/subfolder/foo.txt', 'foobar');
|
||||
|
||||
// size of recipient's root stays the same
|
||||
$newRecipientRootInfo = $recipientView->getFileInfo('', false);
|
||||
$this->assertEquals($recipientRootInfo->getSize(), $newRecipientRootInfo->getSize());
|
||||
|
||||
// size of owner's root increases
|
||||
$this->loginAsUser(self::TEST_FILES_SHARING_API_USER2);
|
||||
$newOwnerRootInfo = $ownerView->getFileInfo('', false);
|
||||
$this->assertEquals($ownerRootInfo->getSize() + 3, $newOwnerRootInfo->getSize());
|
||||
}
|
||||
}
|
||||
@@ -141,14 +141,20 @@ class Test_Files_Sharing_Mount extends OCA\Files_sharing\Tests\TestCase {
|
||||
|
||||
self::loginHelper(self::TEST_FILES_SHARING_API_USER2);
|
||||
|
||||
\OC\Files\Filesystem::rename($this->filename, "newFileName");
|
||||
\OC\Files\Filesystem::rename($this->filename, $this->filename . '_renamed');
|
||||
|
||||
$this->assertTrue(\OC\Files\Filesystem::file_exists('newFileName'));
|
||||
$this->assertTrue(\OC\Files\Filesystem::file_exists($this->filename . '_renamed'));
|
||||
$this->assertFalse(\OC\Files\Filesystem::file_exists($this->filename));
|
||||
|
||||
self::loginHelper(self::TEST_FILES_SHARING_API_USER1);
|
||||
$this->assertTrue(\OC\Files\Filesystem::file_exists($this->filename));
|
||||
$this->assertFalse(\OC\Files\Filesystem::file_exists("newFileName"));
|
||||
$this->assertFalse(\OC\Files\Filesystem::file_exists($this->filename . '_renamed'));
|
||||
|
||||
// rename back to original name
|
||||
self::loginHelper(self::TEST_FILES_SHARING_API_USER2);
|
||||
\OC\Files\Filesystem::rename($this->filename . '_renamed', $this->filename);
|
||||
$this->assertFalse(\OC\Files\Filesystem::file_exists($this->filename . '_renamed'));
|
||||
$this->assertTrue(\OC\Files\Filesystem::file_exists($this->filename));
|
||||
|
||||
//cleanup
|
||||
\OCP\Share::unshare('file', $fileinfo['fileid'], \OCP\Share::SHARE_TYPE_USER, self::TEST_FILES_SHARING_API_USER2);
|
||||
|
||||
@@ -182,9 +182,8 @@ class Test_Files_Sharing_Storage extends OCA\Files_sharing\Tests\TestCase {
|
||||
// for the share root we expect:
|
||||
// the shared permissions (1)
|
||||
// the delete permission (8), to enable unshare
|
||||
// the update permission (2), to allow renaming of the mount point
|
||||
$rootInfo = \OC\Files\Filesystem::getFileInfo($this->folder);
|
||||
$this->assertSame(11, $rootInfo->getPermissions());
|
||||
$this->assertSame(9, $rootInfo->getPermissions());
|
||||
|
||||
// for the file within the shared folder we expect:
|
||||
// the shared permissions (1)
|
||||
@@ -199,6 +198,158 @@ class Test_Files_Sharing_Storage extends OCA\Files_sharing\Tests\TestCase {
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
public function testFopenWithReadOnlyPermission() {
|
||||
$this->view->file_put_contents($this->folder . '/existing.txt', 'foo');
|
||||
$fileinfoFolder = $this->view->getFileInfo($this->folder);
|
||||
$result = \OCP\Share::shareItem('folder', $fileinfoFolder['fileid'], \OCP\Share::SHARE_TYPE_USER,
|
||||
self::TEST_FILES_SHARING_API_USER2, \OCP\Constants::PERMISSION_READ);
|
||||
$this->assertTrue($result);
|
||||
|
||||
self::loginHelper(self::TEST_FILES_SHARING_API_USER2);
|
||||
$user2View = new \OC\Files\View('/' . self::TEST_FILES_SHARING_API_USER2 . '/files');
|
||||
|
||||
// part file should be forbidden
|
||||
$handle = $user2View->fopen($this->folder . '/test.txt.part', 'w');
|
||||
$this->assertFalse($handle);
|
||||
|
||||
// regular file forbidden
|
||||
$handle = $user2View->fopen($this->folder . '/test.txt', 'w');
|
||||
$this->assertFalse($handle);
|
||||
|
||||
// rename forbidden
|
||||
$this->assertFalse($user2View->rename($this->folder . '/existing.txt', $this->folder . '/existing2.txt'));
|
||||
|
||||
// delete forbidden
|
||||
$this->assertFalse($user2View->unlink($this->folder . '/existing.txt'));
|
||||
|
||||
//cleanup
|
||||
self::loginHelper(self::TEST_FILES_SHARING_API_USER1);
|
||||
$result = \OCP\Share::unshare('folder', $fileinfoFolder['fileid'], \OCP\Share::SHARE_TYPE_USER,
|
||||
self::TEST_FILES_SHARING_API_USER2);
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
public function testFopenWithCreateOnlyPermission() {
|
||||
$this->view->file_put_contents($this->folder . '/existing.txt', 'foo');
|
||||
$fileinfoFolder = $this->view->getFileInfo($this->folder);
|
||||
$result = \OCP\Share::shareItem('folder', $fileinfoFolder['fileid'], \OCP\Share::SHARE_TYPE_USER,
|
||||
self::TEST_FILES_SHARING_API_USER2, \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_CREATE);
|
||||
$this->assertTrue($result);
|
||||
|
||||
self::loginHelper(self::TEST_FILES_SHARING_API_USER2);
|
||||
$user2View = new \OC\Files\View('/' . self::TEST_FILES_SHARING_API_USER2 . '/files');
|
||||
|
||||
// create part file allowed
|
||||
$handle = $user2View->fopen($this->folder . '/test.txt.part', 'w');
|
||||
$this->assertNotFalse($handle);
|
||||
fclose($handle);
|
||||
|
||||
// create regular file allowed
|
||||
$handle = $user2View->fopen($this->folder . '/test-create.txt', 'w');
|
||||
$this->assertNotFalse($handle);
|
||||
fclose($handle);
|
||||
|
||||
// rename file never allowed
|
||||
$this->assertFalse($user2View->rename($this->folder . '/test-create.txt', $this->folder . '/newtarget.txt'));
|
||||
$this->assertFalse($user2View->file_exists($this->folder . '/newtarget.txt'));
|
||||
|
||||
// rename file not allowed if target exists
|
||||
$this->assertFalse($user2View->rename($this->folder . '/newtarget.txt', $this->folder . '/existing.txt'));
|
||||
|
||||
// overwriting file not allowed
|
||||
$handle = $user2View->fopen($this->folder . '/existing.txt', 'w');
|
||||
$this->assertFalse($handle);
|
||||
|
||||
// overwrite forbidden (no update permission)
|
||||
$this->assertFalse($user2View->rename($this->folder . '/test.txt.part', $this->folder . '/existing.txt'));
|
||||
|
||||
// delete forbidden
|
||||
$this->assertFalse($user2View->unlink($this->folder . '/existing.txt'));
|
||||
|
||||
//cleanup
|
||||
self::loginHelper(self::TEST_FILES_SHARING_API_USER1);
|
||||
$result = \OCP\Share::unshare('folder', $fileinfoFolder['fileid'], \OCP\Share::SHARE_TYPE_USER,
|
||||
self::TEST_FILES_SHARING_API_USER2);
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
public function testFopenWithUpdateOnlyPermission() {
|
||||
$this->view->file_put_contents($this->folder . '/existing.txt', 'foo');
|
||||
$fileinfoFolder = $this->view->getFileInfo($this->folder);
|
||||
|
||||
$result = \OCP\Share::shareItem('folder', $fileinfoFolder['fileid'], \OCP\Share::SHARE_TYPE_USER,
|
||||
self::TEST_FILES_SHARING_API_USER2, \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_UPDATE);
|
||||
$this->assertTrue($result);
|
||||
|
||||
self::loginHelper(self::TEST_FILES_SHARING_API_USER2);
|
||||
$user2View = new \OC\Files\View('/' . self::TEST_FILES_SHARING_API_USER2 . '/files');
|
||||
|
||||
// create part file allowed
|
||||
$handle = $user2View->fopen($this->folder . '/test.txt.part', 'w');
|
||||
$this->assertNotFalse($handle);
|
||||
fclose($handle);
|
||||
|
||||
// create regular file not allowed
|
||||
$handle = $user2View->fopen($this->folder . '/test-create.txt', 'w');
|
||||
$this->assertFalse($handle);
|
||||
|
||||
// rename part file not allowed to non-existing file
|
||||
$this->assertFalse($user2View->rename($this->folder . '/test.txt.part', $this->folder . '/nonexist.txt'));
|
||||
|
||||
// rename part file allowed to target existing file
|
||||
$this->assertTrue($user2View->rename($this->folder . '/test.txt.part', $this->folder . '/existing.txt'));
|
||||
$this->assertTrue($user2View->file_exists($this->folder . '/existing.txt'));
|
||||
|
||||
// rename regular file allowed
|
||||
$this->assertTrue($user2View->rename($this->folder . '/existing.txt', $this->folder . '/existing-renamed.txt'));
|
||||
$this->assertTrue($user2View->file_exists($this->folder . '/existing-renamed.txt'));
|
||||
|
||||
// overwriting file directly is allowed
|
||||
$handle = $user2View->fopen($this->folder . '/existing-renamed.txt', 'w');
|
||||
$this->assertNotFalse($handle);
|
||||
fclose($handle);
|
||||
|
||||
// delete forbidden
|
||||
$this->assertFalse($user2View->unlink($this->folder . '/existing-renamed.txt'));
|
||||
|
||||
//cleanup
|
||||
self::loginHelper(self::TEST_FILES_SHARING_API_USER1);
|
||||
$result = \OCP\Share::unshare('folder', $fileinfoFolder['fileid'], \OCP\Share::SHARE_TYPE_USER,
|
||||
self::TEST_FILES_SHARING_API_USER2);
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
public function testFopenWithDeleteOnlyPermission() {
|
||||
$this->view->file_put_contents($this->folder . '/existing.txt', 'foo');
|
||||
$fileinfoFolder = $this->view->getFileInfo($this->folder);
|
||||
$result = \OCP\Share::shareItem('folder', $fileinfoFolder['fileid'], \OCP\Share::SHARE_TYPE_USER,
|
||||
self::TEST_FILES_SHARING_API_USER2, \OCP\Constants::PERMISSION_READ | \OCP\Constants::PERMISSION_DELETE);
|
||||
$this->assertTrue($result);
|
||||
|
||||
self::loginHelper(self::TEST_FILES_SHARING_API_USER2);
|
||||
$user2View = new \OC\Files\View('/' . self::TEST_FILES_SHARING_API_USER2 . '/files');
|
||||
|
||||
// part file should be forbidden
|
||||
$handle = $user2View->fopen($this->folder . '/test.txt.part', 'w');
|
||||
$this->assertFalse($handle);
|
||||
|
||||
// regular file forbidden
|
||||
$handle = $user2View->fopen($this->folder . '/test.txt', 'w');
|
||||
$this->assertFalse($handle);
|
||||
|
||||
// rename forbidden
|
||||
$this->assertFalse($user2View->rename($this->folder . '/existing.txt', $this->folder . '/existing2.txt'));
|
||||
|
||||
// delete allowed
|
||||
$this->assertTrue($user2View->unlink($this->folder . '/existing.txt'));
|
||||
|
||||
//cleanup
|
||||
self::loginHelper(self::TEST_FILES_SHARING_API_USER1);
|
||||
$result = \OCP\Share::unshare('folder', $fileinfoFolder['fileid'], \OCP\Share::SHARE_TYPE_USER,
|
||||
self::TEST_FILES_SHARING_API_USER2);
|
||||
$this->assertTrue($result);
|
||||
}
|
||||
|
||||
function testMountSharesOtherUser() {
|
||||
$folderInfo = $this->view->getFileInfo($this->folder);
|
||||
$fileInfo = $this->view->getFileInfo($this->filename);
|
||||
|
||||
@@ -115,14 +115,34 @@ class Test_Files_Sharing_Updater extends OCA\Files_sharing\Tests\TestCase {
|
||||
\OC\Files\Filesystem::getLoader()->removeStorageWrapper('oc_trashbin');
|
||||
}
|
||||
|
||||
public function shareFolderProvider() {
|
||||
return [
|
||||
['/'],
|
||||
['/my_shares'],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* if a file gets shared the etag for the recipients root should change
|
||||
*
|
||||
* @dataProvider shareFolderProvider
|
||||
*
|
||||
* @param string $shareFolder share folder to use
|
||||
*/
|
||||
function testShareFile() {
|
||||
public function testShareFile($shareFolder) {
|
||||
$config = \OC::$server->getConfig();
|
||||
$oldShareFolder = $config->getSystemValue('share_folder');
|
||||
$config->setSystemValue('share_folder', $shareFolder);
|
||||
|
||||
$this->loginHelper(self::TEST_FILES_SHARING_API_USER2);
|
||||
|
||||
$beforeShare = \OC\Files\Filesystem::getFileInfo('');
|
||||
$etagBeforeShare = $beforeShare->getEtag();
|
||||
$beforeShareRoot = \OC\Files\Filesystem::getFileInfo('');
|
||||
$etagBeforeShareRoot = $beforeShareRoot->getEtag();
|
||||
|
||||
\OC\Files\Filesystem::mkdir($shareFolder);
|
||||
|
||||
$beforeShareDir = \OC\Files\Filesystem::getFileInfo($shareFolder);
|
||||
$etagBeforeShareDir = $beforeShareDir->getEtag();
|
||||
|
||||
$this->loginHelper(self::TEST_FILES_SHARING_API_USER1);
|
||||
$fileinfo = \OC\Files\Filesystem::getFileInfo($this->folder);
|
||||
@@ -131,17 +151,25 @@ class Test_Files_Sharing_Updater extends OCA\Files_sharing\Tests\TestCase {
|
||||
|
||||
$this->loginHelper(self::TEST_FILES_SHARING_API_USER2);
|
||||
|
||||
$afterShare = \OC\Files\Filesystem::getFileInfo('');
|
||||
$etagAfterShare = $afterShare->getEtag();
|
||||
$afterShareRoot = \OC\Files\Filesystem::getFileInfo('');
|
||||
$etagAfterShareRoot = $afterShareRoot->getEtag();
|
||||
|
||||
$this->assertTrue(is_string($etagBeforeShare));
|
||||
$this->assertTrue(is_string($etagAfterShare));
|
||||
$this->assertTrue($etagBeforeShare !== $etagAfterShare);
|
||||
$afterShareDir = \OC\Files\Filesystem::getFileInfo($shareFolder);
|
||||
$etagAfterShareDir = $afterShareDir->getEtag();
|
||||
|
||||
$this->assertTrue(is_string($etagBeforeShareRoot));
|
||||
$this->assertTrue(is_string($etagBeforeShareDir));
|
||||
$this->assertTrue(is_string($etagAfterShareRoot));
|
||||
$this->assertTrue(is_string($etagAfterShareDir));
|
||||
$this->assertTrue($etagBeforeShareRoot !== $etagAfterShareRoot);
|
||||
$this->assertTrue($etagBeforeShareDir !== $etagAfterShareDir);
|
||||
|
||||
// cleanup
|
||||
$this->loginHelper(self::TEST_FILES_SHARING_API_USER1);
|
||||
$result = \OCP\Share::unshare('folder', $fileinfo->getId(), \OCP\Share::SHARE_TYPE_USER, self::TEST_FILES_SHARING_API_USER2);
|
||||
$this->assertTrue($result);
|
||||
|
||||
$config->setSystemValue('share_folder', $oldShareFolder);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,5 +18,4 @@ To prevent a user from running out of disk space, the ownCloud Deleted files app
|
||||
<documentation>
|
||||
<user>user-trashbin</user>
|
||||
</documentation>
|
||||
<ocsid>166052</ocsid>
|
||||
</info>
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.6.2
|
||||
0.6.3
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
<?php
|
||||
$TRANSLATIONS = array(
|
||||
"_%n folder_::_%n folders_" => array("",""),
|
||||
"_%n file_::_%n files_" => array("","")
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n > 1);";
|
||||
@@ -1,6 +0,0 @@
|
||||
<?php
|
||||
$TRANSLATIONS = array(
|
||||
"_%n folder_::_%n folders_" => array("",""),
|
||||
"_%n file_::_%n files_" => array("","")
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
|
||||
@@ -1,6 +0,0 @@
|
||||
<?php
|
||||
$TRANSLATIONS = array(
|
||||
"_%n folder_::_%n folders_" => array("",""),
|
||||
"_%n file_::_%n files_" => array("","")
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
|
||||
@@ -1,6 +0,0 @@
|
||||
<?php
|
||||
$TRANSLATIONS = array(
|
||||
"_%n folder_::_%n folders_" => array("",""),
|
||||
"_%n file_::_%n files_" => array("","")
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
|
||||
@@ -1,6 +0,0 @@
|
||||
<?php
|
||||
$TRANSLATIONS = array(
|
||||
"_%n folder_::_%n folders_" => array(""),
|
||||
"_%n file_::_%n files_" => array("")
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=1; plural=0;";
|
||||
@@ -1,6 +0,0 @@
|
||||
<?php
|
||||
$TRANSLATIONS = array(
|
||||
"_%n folder_::_%n folders_" => array("",""),
|
||||
"_%n file_::_%n files_" => array("","")
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
|
||||
@@ -1,6 +0,0 @@
|
||||
<?php
|
||||
$TRANSLATIONS = array(
|
||||
"_%n folder_::_%n folders_" => array(""),
|
||||
"_%n file_::_%n files_" => array("")
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=1; plural=0;";
|
||||
@@ -1,6 +0,0 @@
|
||||
<?php
|
||||
$TRANSLATIONS = array(
|
||||
"_%n folder_::_%n folders_" => array("",""),
|
||||
"_%n file_::_%n files_" => array("","")
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
|
||||
@@ -1,6 +0,0 @@
|
||||
<?php
|
||||
$TRANSLATIONS = array(
|
||||
"_%n folder_::_%n folders_" => array(""),
|
||||
"_%n file_::_%n files_" => array("")
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=1; plural=0;";
|
||||
@@ -1,6 +0,0 @@
|
||||
<?php
|
||||
$TRANSLATIONS = array(
|
||||
"_%n folder_::_%n folders_" => array("",""),
|
||||
"_%n file_::_%n files_" => array("","")
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
|
||||
@@ -61,14 +61,55 @@ class Storage extends Wrapper {
|
||||
self::$disableTrash = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename path1 to path2 by calling the wrapped storage.
|
||||
*
|
||||
* @param string $path1 first path
|
||||
* @param string $path2 second path
|
||||
*/
|
||||
public function rename($path1, $path2) {
|
||||
$result = $this->storage->rename($path1, $path2);
|
||||
if ($result === false) {
|
||||
// when rename failed, the post_rename hook isn't triggered,
|
||||
// but we still want to reenable the trash logic
|
||||
self::$disableTrash = false;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given file by moving it into the trashbin.
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $path path of file or folder to delete
|
||||
*
|
||||
* @return bool true if the operation succeeded, false otherwise
|
||||
*/
|
||||
public function unlink($path) {
|
||||
return $this->doDelete($path, 'unlink');
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes the given folder by moving it into the trashbin.
|
||||
*
|
||||
* @param string $path path of folder to delete
|
||||
*
|
||||
* @return bool true if the operation succeeded, false otherwise
|
||||
*/
|
||||
public function rmdir($path) {
|
||||
return $this->doDelete($path, 'rmdir');
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the delete operation with the given method
|
||||
*
|
||||
* @param string $path path of file or folder to delete
|
||||
* @param string $method either "unlink" or "rmdir"
|
||||
*
|
||||
* @return bool true if the operation succeeded, false otherwise
|
||||
*/
|
||||
private function doDelete($path, $method) {
|
||||
if (self::$disableTrash) {
|
||||
return $this->storage->unlink($path);
|
||||
return call_user_func_array([$this->storage, $method], [$path]);
|
||||
}
|
||||
$normalized = Filesystem::normalizePath($this->mountPoint . '/' . $path);
|
||||
$result = true;
|
||||
@@ -81,14 +122,14 @@ class Storage extends Wrapper {
|
||||
// in cross-storage cases the file will be copied
|
||||
// but not deleted, so we delete it here
|
||||
if ($result) {
|
||||
$this->storage->unlink($path);
|
||||
call_user_func_array([$this->storage, $method], [$path]);
|
||||
}
|
||||
} else {
|
||||
$result = $this->storage->unlink($path);
|
||||
$result = call_user_func_array([$this->storage, $method], [$path]);
|
||||
}
|
||||
unset($this->deletedFiles[$normalized]);
|
||||
} else if ($this->storage->file_exists($path)) {
|
||||
$result = $this->storage->unlink($path);
|
||||
$result = call_user_func_array([$this->storage, $method], [$path]);
|
||||
}
|
||||
|
||||
return $result;
|
||||
|
||||
@@ -32,6 +32,13 @@ class Trashbin {
|
||||
// unit: percentage; 50% of available disk space/quota
|
||||
const DEFAULTMAXSIZE = 50;
|
||||
|
||||
/**
|
||||
* Whether versions have already be rescanned during this PHP request
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private static $scannedVersions = false;
|
||||
|
||||
public static function getUidAndFilename($filename) {
|
||||
$uid = \OC\Files\Filesystem::getOwner($filename);
|
||||
\OC\Files\Filesystem::initMountPoints($uid);
|
||||
@@ -151,6 +158,10 @@ class Trashbin {
|
||||
}
|
||||
|
||||
self::setUpTrash($user);
|
||||
if ($owner !== $user) {
|
||||
// also setup for owner
|
||||
self::setUpTrash($owner);
|
||||
}
|
||||
|
||||
$path_parts = pathinfo($file_path);
|
||||
|
||||
@@ -194,7 +205,7 @@ class Trashbin {
|
||||
\OCP\Util::emitHook('\OCA\Files_Trashbin\Trashbin', 'post_moveToTrash', array('filePath' => \OC\Files\Filesystem::normalizePath($file_path),
|
||||
'trashPath' => \OC\Files\Filesystem::normalizePath($filename . '.d' . $timestamp)));
|
||||
|
||||
$size += self::retainVersions($file_path, $filename, $timestamp);
|
||||
$size += self::retainVersions($file_path, $filename, $owner, $ownerPath, $timestamp);
|
||||
$size += self::retainEncryptionKeys($file_path, $filename, $timestamp);
|
||||
|
||||
// if owner !== user we need to also add a copy to the owners trash
|
||||
@@ -221,13 +232,15 @@ class Trashbin {
|
||||
*
|
||||
* @param string $file_path path to original file
|
||||
* @param string $filename of deleted file
|
||||
* @param string $owner owner user id
|
||||
* @param string $ownerPath path relative to the owner's home storage
|
||||
* @param integer $timestamp when the file was deleted
|
||||
*
|
||||
* @return int size of stored versions
|
||||
*/
|
||||
private static function retainVersions($file_path, $filename, $timestamp) {
|
||||
private static function retainVersions($file_path, $filename, $owner, $ownerPath, $timestamp) {
|
||||
$size = 0;
|
||||
if (\OCP\App::isEnabled('files_versions')) {
|
||||
if (\OCP\App::isEnabled('files_versions') && !empty($ownerPath)) {
|
||||
|
||||
// disable proxy to prevent recursive calls
|
||||
$proxyStatus = \OC_FileProxy::$enabled;
|
||||
@@ -236,12 +249,6 @@ class Trashbin {
|
||||
$user = \OCP\User::getUser();
|
||||
$rootView = new \OC\Files\View('/');
|
||||
|
||||
list($owner, $ownerPath) = self::getUidAndFilename($file_path);
|
||||
// file has been deleted in between
|
||||
if (empty($ownerPath)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ($rootView->is_dir($owner . '/files_versions/' . $ownerPath)) {
|
||||
$size += self::calculateSize(new \OC\Files\View('/' . $owner . '/files_versions/' . $ownerPath));
|
||||
if ($owner !== $user) {
|
||||
@@ -292,7 +299,7 @@ class Trashbin {
|
||||
$util = new \OCA\Files_Encryption\Util($rootView, $user);
|
||||
|
||||
$baseDir = '/files_encryption/';
|
||||
if (!$util->isSystemWideMountPoint($ownerPath)) {
|
||||
if (!$util->isSystemWideMountPoint($ownerPath, $owner)) {
|
||||
$baseDir = $owner . $baseDir;
|
||||
}
|
||||
|
||||
@@ -466,7 +473,7 @@ class Trashbin {
|
||||
$util = new \OCA\Files_Encryption\Util($rootView, $user);
|
||||
|
||||
$baseDir = '/files_encryption/';
|
||||
if (!$util->isSystemWideMountPoint($ownerPath)) {
|
||||
if (!$util->isSystemWideMountPoint($ownerPath, $owner)) {
|
||||
$baseDir = $owner . $baseDir;
|
||||
}
|
||||
|
||||
@@ -825,9 +832,12 @@ class Trashbin {
|
||||
$versions = array();
|
||||
|
||||
//force rescan of versions, local storage may not have updated the cache
|
||||
/** @var \OC\Files\Storage\Storage $storage */
|
||||
list($storage, ) = $view->resolvePath('/');
|
||||
$storage->getScanner()->scan('files_trashbin');
|
||||
if (!self::$scannedVersions) {
|
||||
/** @var \OC\Files\Storage\Storage $storage */
|
||||
list($storage, ) = $view->resolvePath('/');
|
||||
$storage->getScanner()->scan('files_trashbin/versions');
|
||||
self::$scannedVersions = true;
|
||||
}
|
||||
|
||||
if ($timestamp) {
|
||||
// fetch for old versions
|
||||
|
||||
@@ -54,6 +54,8 @@ class Storage extends \Test\TestCase {
|
||||
$this->userView = new \OC\Files\View('/' . $this->user . '/files/');
|
||||
$this->userView->file_put_contents('test.txt', 'foo');
|
||||
|
||||
$this->userView->mkdir('folder');
|
||||
$this->userView->file_put_contents('folder/inside.txt', 'bar');
|
||||
}
|
||||
|
||||
protected function tearDown() {
|
||||
@@ -68,7 +70,7 @@ class Storage extends \Test\TestCase {
|
||||
/**
|
||||
* Test that deleting a file puts it into the trashbin.
|
||||
*/
|
||||
public function testSingleStorageDelete() {
|
||||
public function testSingleStorageDeleteFile() {
|
||||
$this->assertTrue($this->userView->file_exists('test.txt'));
|
||||
$this->userView->unlink('test.txt');
|
||||
list($storage,) = $this->userView->resolvePath('test.txt');
|
||||
@@ -82,13 +84,35 @@ class Storage extends \Test\TestCase {
|
||||
$this->assertEquals('test.txt', substr($name, 0, strrpos($name, '.')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that deleting a folder puts it into the trashbin.
|
||||
*/
|
||||
public function testSingleStorageDeleteFolder() {
|
||||
$this->assertTrue($this->userView->file_exists('folder/inside.txt'));
|
||||
$this->userView->rmdir('folder');
|
||||
list($storage,) = $this->userView->resolvePath('folder/inside.txt');
|
||||
$storage->getScanner()->scan(''); // make sure we check the storage
|
||||
$this->assertFalse($this->userView->getFileInfo('folder'));
|
||||
|
||||
// check if folder is in trashbin
|
||||
$results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/');
|
||||
$this->assertEquals(1, count($results));
|
||||
$name = $results[0]->getName();
|
||||
$this->assertEquals('folder', substr($name, 0, strrpos($name, '.')));
|
||||
|
||||
$results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/' . $name . '/');
|
||||
$this->assertEquals(1, count($results));
|
||||
$name = $results[0]->getName();
|
||||
$this->assertEquals('inside.txt', $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that deleting a file from another mounted storage properly
|
||||
* lands in the trashbin. This is a cross-storage situation because
|
||||
* the trashbin folder is in the root storage while the mounted one
|
||||
* isn't.
|
||||
*/
|
||||
public function testCrossStorageDelete() {
|
||||
public function testCrossStorageDeleteFile() {
|
||||
$storage2 = new Temporary(array());
|
||||
\OC\Files\Filesystem::mount($storage2, array(), $this->user . '/files/substorage');
|
||||
|
||||
@@ -108,10 +132,42 @@ class Storage extends \Test\TestCase {
|
||||
$this->assertEquals('subfile.txt', substr($name, 0, strrpos($name, '.')));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that deleting a folder from another mounted storage properly
|
||||
* lands in the trashbin. This is a cross-storage situation because
|
||||
* the trashbin folder is in the root storage while the mounted one
|
||||
* isn't.
|
||||
*/
|
||||
public function testCrossStorageDeleteFolder() {
|
||||
$storage2 = new Temporary(array());
|
||||
\OC\Files\Filesystem::mount($storage2, array(), $this->user . '/files/substorage');
|
||||
|
||||
$this->userView->mkdir('substorage/folder');
|
||||
$this->userView->file_put_contents('substorage/folder/subfile.txt', 'bar');
|
||||
$storage2->getScanner()->scan('');
|
||||
$this->assertTrue($storage2->file_exists('folder/subfile.txt'));
|
||||
$this->userView->rmdir('substorage/folder');
|
||||
|
||||
$storage2->getScanner()->scan('');
|
||||
$this->assertFalse($this->userView->getFileInfo('substorage/folder'));
|
||||
$this->assertFalse($storage2->file_exists('folder/subfile.txt'));
|
||||
|
||||
// check if folder is in trashbin
|
||||
$results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files');
|
||||
$this->assertEquals(1, count($results));
|
||||
$name = $results[0]->getName();
|
||||
$this->assertEquals('folder', substr($name, 0, strrpos($name, '.')));
|
||||
|
||||
$results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/' . $name . '/');
|
||||
$this->assertEquals(1, count($results));
|
||||
$name = $results[0]->getName();
|
||||
$this->assertEquals('subfile.txt', $name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that deleted versions properly land in the trashbin.
|
||||
*/
|
||||
public function testDeleteVersions() {
|
||||
public function testDeleteVersionsOfFile() {
|
||||
\OCA\Files_Versions\Hooks::connectHooks();
|
||||
|
||||
// trigger a version (multiple would not work because of the expire logic)
|
||||
@@ -130,7 +186,156 @@ class Storage extends \Test\TestCase {
|
||||
$results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/versions');
|
||||
$this->assertEquals(1, count($results));
|
||||
$name = $results[0]->getName();
|
||||
$this->assertEquals('test.txt', substr($name, 0, strlen('test.txt')));
|
||||
$this->assertEquals('test.txt.v', substr($name, 0, strlen('test.txt.v')));
|
||||
|
||||
// versions deleted
|
||||
$results = $this->rootView->getDirectoryContent($this->user . '/files_versions/');
|
||||
$this->assertEquals(0, count($results));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that deleted versions properly land in the trashbin.
|
||||
*/
|
||||
public function testDeleteVersionsOfFolder() {
|
||||
\OCA\Files_Versions\Hooks::connectHooks();
|
||||
|
||||
// trigger a version (multiple would not work because of the expire logic)
|
||||
$this->userView->file_put_contents('folder/inside.txt', 'v1');
|
||||
|
||||
$results = $this->rootView->getDirectoryContent($this->user . '/files_versions/folder/');
|
||||
$this->assertEquals(1, count($results));
|
||||
|
||||
$this->userView->rmdir('folder');
|
||||
|
||||
// rescan trash storage
|
||||
list($rootStorage,) = $this->rootView->resolvePath($this->user . '/files_trashbin');
|
||||
$rootStorage->getScanner()->scan('');
|
||||
|
||||
// check if versions are in trashbin
|
||||
$results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/versions');
|
||||
$this->assertEquals(1, count($results));
|
||||
$name = $results[0]->getName();
|
||||
$this->assertEquals('folder.d', substr($name, 0, strlen('folder.d')));
|
||||
|
||||
// check if versions are in trashbin
|
||||
$results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/versions/' . $name . '/');
|
||||
$this->assertEquals(1, count($results));
|
||||
$name = $results[0]->getName();
|
||||
$this->assertEquals('inside.txt.v', substr($name, 0, strlen('inside.txt.v')));
|
||||
|
||||
// versions deleted
|
||||
$results = $this->rootView->getDirectoryContent($this->user . '/files_versions/folder/');
|
||||
$this->assertEquals(0, count($results));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that deleted versions properly land in the trashbin when deleting as share recipient.
|
||||
*/
|
||||
public function testDeleteVersionsOfFileAsRecipient() {
|
||||
\OCA\Files_Versions\Hooks::connectHooks();
|
||||
\OCA\Files_Sharing\Helper::registerHooks();
|
||||
|
||||
$this->userView->mkdir('share');
|
||||
// trigger a version (multiple would not work because of the expire logic)
|
||||
$this->userView->file_put_contents('share/test.txt', 'v1');
|
||||
$this->userView->file_put_contents('share/test.txt', 'v2');
|
||||
|
||||
$results = $this->rootView->getDirectoryContent($this->user . '/files_versions/share/');
|
||||
$this->assertEquals(1, count($results));
|
||||
|
||||
$recipientUser = $this->getUniqueId('recipient_');
|
||||
\OC::$server->getUserManager()->createUser($recipientUser, $recipientUser);
|
||||
|
||||
$fileinfo = $this->userView->getFileInfo('share');
|
||||
$this->assertTrue(\OCP\Share::shareItem('folder', $fileinfo['fileid'], \OCP\Share::SHARE_TYPE_USER,
|
||||
$recipientUser, 31));
|
||||
|
||||
$this->loginAsUser($recipientUser);
|
||||
|
||||
// delete as recipient
|
||||
$recipientView = new \OC\Files\View('/' . $recipientUser . '/files');
|
||||
$recipientView->unlink('share/test.txt');
|
||||
|
||||
// rescan trash storage for both users
|
||||
list($rootStorage,) = $this->rootView->resolvePath($this->user . '/files_trashbin');
|
||||
$rootStorage->getScanner()->scan('');
|
||||
|
||||
// check if versions are in trashbin for both users
|
||||
$results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/versions');
|
||||
$this->assertEquals(1, count($results), 'Versions in owner\'s trashbin');
|
||||
$name = $results[0]->getName();
|
||||
$this->assertEquals('test.txt.v', substr($name, 0, strlen('test.txt.v')));
|
||||
|
||||
$results = $this->rootView->getDirectoryContent($recipientUser . '/files_trashbin/versions');
|
||||
$this->assertEquals(1, count($results), 'Versions in recipient\'s trashbin');
|
||||
$name = $results[0]->getName();
|
||||
$this->assertEquals('test.txt.v', substr($name, 0, strlen('test.txt.v')));
|
||||
|
||||
// versions deleted
|
||||
$results = $this->rootView->getDirectoryContent($this->user . '/files_versions/share/');
|
||||
$this->assertEquals(0, count($results));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that deleted versions properly land in the trashbin when deleting as share recipient.
|
||||
*/
|
||||
public function testDeleteVersionsOfFolderAsRecipient() {
|
||||
\OCA\Files_Versions\Hooks::connectHooks();
|
||||
\OCA\Files_Sharing\Helper::registerHooks();
|
||||
|
||||
$this->userView->mkdir('share');
|
||||
$this->userView->mkdir('share/folder');
|
||||
// trigger a version (multiple would not work because of the expire logic)
|
||||
$this->userView->file_put_contents('share/folder/test.txt', 'v1');
|
||||
$this->userView->file_put_contents('share/folder/test.txt', 'v2');
|
||||
|
||||
$results = $this->rootView->getDirectoryContent($this->user . '/files_versions/share/folder/');
|
||||
$this->assertEquals(1, count($results));
|
||||
|
||||
$recipientUser = $this->getUniqueId('recipient_');
|
||||
\OC::$server->getUserManager()->createUser($recipientUser, $recipientUser);
|
||||
|
||||
$fileinfo = $this->userView->getFileInfo('share');
|
||||
$this->assertTrue(\OCP\Share::shareItem('folder', $fileinfo['fileid'], \OCP\Share::SHARE_TYPE_USER,
|
||||
$recipientUser, 31));
|
||||
|
||||
$this->loginAsUser($recipientUser);
|
||||
|
||||
// delete as recipient
|
||||
$recipientView = new \OC\Files\View('/' . $recipientUser . '/files');
|
||||
$recipientView->rmdir('share/folder');
|
||||
|
||||
// rescan trash storage
|
||||
list($rootStorage,) = $this->rootView->resolvePath($this->user . '/files_trashbin');
|
||||
$rootStorage->getScanner()->scan('');
|
||||
|
||||
// check if versions are in trashbin for owner
|
||||
$results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/versions');
|
||||
$this->assertEquals(1, count($results));
|
||||
$name = $results[0]->getName();
|
||||
$this->assertEquals('folder.d', substr($name, 0, strlen('folder.d')));
|
||||
|
||||
// check if file versions are in trashbin for owner
|
||||
$results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/versions/' . $name . '/');
|
||||
$this->assertEquals(1, count($results));
|
||||
$name = $results[0]->getName();
|
||||
$this->assertEquals('test.txt.v', substr($name, 0, strlen('test.txt.v')));
|
||||
|
||||
// check if versions are in trashbin for recipient
|
||||
$results = $this->rootView->getDirectoryContent($recipientUser . '/files_trashbin/versions');
|
||||
$this->assertEquals(1, count($results));
|
||||
$name = $results[0]->getName();
|
||||
$this->assertEquals('folder.d', substr($name, 0, strlen('folder.d')));
|
||||
|
||||
// check if file versions are in trashbin for recipient
|
||||
$results = $this->rootView->getDirectoryContent($recipientUser . '/files_trashbin/versions/' . $name . '/');
|
||||
$this->assertEquals(1, count($results));
|
||||
$name = $results[0]->getName();
|
||||
$this->assertEquals('test.txt.v', substr($name, 0, strlen('test.txt.v')));
|
||||
|
||||
// versions deleted
|
||||
$results = $this->rootView->getDirectoryContent($recipientUser . '/files_versions/share/folder/');
|
||||
$this->assertEquals(0, count($results));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,7 +343,7 @@ class Storage extends \Test\TestCase {
|
||||
* storages. This is because rename() between storages would call
|
||||
* unlink() which should NOT trigger the version deletion logic.
|
||||
*/
|
||||
public function testKeepFileAndVersionsWhenMovingBetweenStorages() {
|
||||
public function testKeepFileAndVersionsWhenMovingFileBetweenStorages() {
|
||||
\OCA\Files_Versions\Hooks::connectHooks();
|
||||
|
||||
$storage2 = new Temporary(array());
|
||||
@@ -155,7 +360,7 @@ class Storage extends \Test\TestCase {
|
||||
|
||||
// move to another storage
|
||||
$this->userView->rename('test.txt', 'substorage/test.txt');
|
||||
$this->userView->file_exists('substorage/test.txt');
|
||||
$this->assertTrue($this->userView->file_exists('substorage/test.txt'));
|
||||
|
||||
// rescan trash storage
|
||||
list($rootStorage,) = $this->rootView->resolvePath($this->user . '/files_trashbin');
|
||||
@@ -174,10 +379,51 @@ class Storage extends \Test\TestCase {
|
||||
$this->assertEquals(0, count($results));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that versions are not auto-trashed when moving a file between
|
||||
* storages. This is because rename() between storages would call
|
||||
* unlink() which should NOT trigger the version deletion logic.
|
||||
*/
|
||||
public function testKeepFileAndVersionsWhenMovingFolderBetweenStorages() {
|
||||
\OCA\Files_Versions\Hooks::connectHooks();
|
||||
|
||||
$storage2 = new Temporary(array());
|
||||
\OC\Files\Filesystem::mount($storage2, array(), $this->user . '/files/substorage');
|
||||
|
||||
// trigger a version (multiple would not work because of the expire logic)
|
||||
$this->userView->file_put_contents('folder/inside.txt', 'v1');
|
||||
|
||||
$results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files');
|
||||
$this->assertEquals(0, count($results));
|
||||
|
||||
$results = $this->rootView->getDirectoryContent($this->user . '/files_versions/folder/');
|
||||
$this->assertEquals(1, count($results));
|
||||
|
||||
// move to another storage
|
||||
$this->userView->rename('folder', 'substorage/folder');
|
||||
$this->assertTrue($this->userView->file_exists('substorage/folder/inside.txt'));
|
||||
|
||||
// rescan trash storage
|
||||
list($rootStorage,) = $this->rootView->resolvePath($this->user . '/files_trashbin');
|
||||
$rootStorage->getScanner()->scan('');
|
||||
|
||||
// versions were moved too
|
||||
$results = $this->rootView->getDirectoryContent($this->user . '/files_versions/substorage/folder/');
|
||||
$this->assertEquals(1, count($results));
|
||||
|
||||
// check that nothing got trashed by the rename's unlink() call
|
||||
$results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files');
|
||||
$this->assertEquals(0, count($results));
|
||||
|
||||
// check that versions were moved and not trashed
|
||||
$results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/versions/');
|
||||
$this->assertEquals(0, count($results));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete should fail is the source file cant be deleted
|
||||
*/
|
||||
public function testSingleStorageDeleteFail() {
|
||||
public function testSingleStorageDeleteFileFail() {
|
||||
/**
|
||||
* @var \OC\Files\Storage\Temporary | \PHPUnit_Framework_MockObject_MockObject $storage
|
||||
*/
|
||||
@@ -186,9 +432,6 @@ class Storage extends \Test\TestCase {
|
||||
->setMethods(['rename', 'unlink'])
|
||||
->getMock();
|
||||
|
||||
$storage->expects($this->any())
|
||||
->method('rename')
|
||||
->will($this->returnValue(false));
|
||||
$storage->expects($this->any())
|
||||
->method('unlink')
|
||||
->will($this->returnValue(false));
|
||||
@@ -206,4 +449,37 @@ class Storage extends \Test\TestCase {
|
||||
$results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/');
|
||||
$this->assertEquals(0, count($results));
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete should fail is the source folder cant be deleted
|
||||
*/
|
||||
public function testSingleStorageDeleteFolderFail() {
|
||||
/**
|
||||
* @var \OC\Files\Storage\Temporary | \PHPUnit_Framework_MockObject_MockObject $storage
|
||||
*/
|
||||
$storage = $this->getMockBuilder('\OC\Files\Storage\Temporary')
|
||||
->setConstructorArgs([[]])
|
||||
->setMethods(['rename', 'unlink', 'rmdir'])
|
||||
->getMock();
|
||||
|
||||
$storage->expects($this->any())
|
||||
->method('rmdir')
|
||||
->will($this->returnValue(false));
|
||||
|
||||
$cache = $storage->getCache();
|
||||
|
||||
Filesystem::mount($storage, [], '/' . $this->user . '/files');
|
||||
$this->userView->mkdir('folder');
|
||||
$this->userView->file_put_contents('folder/test.txt', 'foo');
|
||||
$this->assertTrue($storage->file_exists('folder/test.txt'));
|
||||
$this->assertFalse($this->userView->rmdir('folder'));
|
||||
$this->assertTrue($storage->file_exists('folder'));
|
||||
$this->assertTrue($storage->file_exists('folder/test.txt'));
|
||||
$this->assertTrue($cache->inCache('folder'));
|
||||
$this->assertTrue($cache->inCache('folder/test.txt'));
|
||||
|
||||
// file should not be in the trashbin
|
||||
$results = $this->rootView->getDirectoryContent($this->user . '/files_trashbin/files/');
|
||||
$this->assertEquals(0, count($results));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,5 +18,4 @@ In addition to the expiry of versions, ownCloud’s versions app makes certain n
|
||||
<user>user-versions</user>
|
||||
</documentation>
|
||||
<default_enable/>
|
||||
<ocsid>166053</ocsid>
|
||||
</info>
|
||||
|
||||
@@ -1 +1 @@
|
||||
1.0.5
|
||||
1.0.6
|
||||
|
||||
@@ -38,7 +38,7 @@ $ftype = $view->getMimeType('/'.$uid.'/files/'.$filename);
|
||||
header('Content-Type:'.$ftype);
|
||||
OCP\Response::setContentDispositionHeader(basename($filename), 'attachment');
|
||||
OCP\Response::disableCaching();
|
||||
header('Content-Length: '.$view->filesize($versionName));
|
||||
OCP\Response::setContentLengthHeader($view->filesize($versionName));
|
||||
|
||||
OC_Util::obEnd();
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
<?php $TRANSLATIONS = array(
|
||||
"History" => "Historique",
|
||||
"Files Versioning" => "Fichier's Versionéierung ",
|
||||
"Enable" => "Aschalten"
|
||||
);
|
||||
@@ -204,50 +204,75 @@ class Storage {
|
||||
}
|
||||
|
||||
/**
|
||||
* rename or copy versions of a file
|
||||
* @param string $old_path
|
||||
* @param string $new_path
|
||||
* Rename or copy versions of a file of the given paths
|
||||
*
|
||||
* @param string $sourcePath source path of the file to move, relative to
|
||||
* the currently logged in user's "files" folder
|
||||
* @param string $targetPath target path of the file to move, relative to
|
||||
* the currently logged in user's "files" folder
|
||||
* @param string $operation can be 'copy' or 'rename'
|
||||
*/
|
||||
public static function renameOrCopy($old_path, $new_path, $operation) {
|
||||
list($uid, $oldpath) = self::getSourcePathAndUser($old_path);
|
||||
public static function renameOrCopy($sourcePath, $targetPath, $operation) {
|
||||
list($sourceOwner, $sourcePath) = self::getSourcePathAndUser($sourcePath);
|
||||
|
||||
// it was a upload of a existing file if no old path exists
|
||||
// in this case the pre-hook already called the store method and we can
|
||||
// stop here
|
||||
if ($oldpath === false) {
|
||||
if ($sourcePath === false) {
|
||||
return true;
|
||||
}
|
||||
|
||||
list($uidn, $newpath) = self::getUidAndFilename($new_path);
|
||||
$versions_view = new \OC\Files\View('/'.$uid .'/files_versions');
|
||||
$files_view = new \OC\Files\View('/'.$uid .'/files');
|
||||
list($targetOwner, $targetPath) = self::getUidAndFilename($targetPath);
|
||||
|
||||
$sourcePath = ltrim($sourcePath, '/');
|
||||
$targetPath = ltrim($targetPath, '/');
|
||||
|
||||
$rootView = new \OC\Files\View('');
|
||||
|
||||
if ( $files_view->is_dir($oldpath) && $versions_view->is_dir($oldpath) ) {
|
||||
$versions_view->$operation($oldpath, $newpath);
|
||||
} else if ( ($versions = Storage::getVersions($uid, $oldpath)) ) {
|
||||
// did we move a directory ?
|
||||
if ($rootView->is_dir('/' . $targetOwner . '/files/' . $targetPath)) {
|
||||
// does the directory exists for versions too ?
|
||||
if ($rootView->is_dir('/' . $sourceOwner . '/files_versions/' . $sourcePath)) {
|
||||
// create missing dirs if necessary
|
||||
self::createMissingDirectories($targetPath, new \OC\Files\View('/'. $targetOwner));
|
||||
|
||||
// move the directory containing the versions
|
||||
$rootView->$operation(
|
||||
'/' . $sourceOwner . '/files_versions/' . $sourcePath,
|
||||
'/' . $targetOwner . '/files_versions/' . $targetPath
|
||||
);
|
||||
}
|
||||
} else if ($versions = Storage::getVersions($sourceOwner, '/' . $sourcePath)) {
|
||||
// create missing dirs if necessary
|
||||
self::createMissingDirectories($newpath, new \OC\Files\View('/'. $uidn));
|
||||
self::createMissingDirectories($targetPath, new \OC\Files\View('/'. $targetOwner));
|
||||
|
||||
foreach ($versions as $v) {
|
||||
$versions_view->$operation($oldpath.'.v'.$v['version'], $newpath.'.v'.$v['version']);
|
||||
// move each version one by one to the target directory
|
||||
$rootView->$operation(
|
||||
'/' . $sourceOwner . '/files_versions/' . $sourcePath.'.v' . $v['version'],
|
||||
'/' . $targetOwner . '/files_versions/' . $targetPath.'.v'.$v['version']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (!$files_view->is_dir($newpath)) {
|
||||
self::expire($newpath);
|
||||
// if we moved versions directly for a file, schedule expiration check for that file
|
||||
if (!$rootView->is_dir('/' . $targetOwner . '/files/' . $targetPath)) {
|
||||
self::expire($targetPath);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* rollback to an old version of a file.
|
||||
* Rollback to an old version of a file.
|
||||
*
|
||||
* @param string $file file name
|
||||
* @param int $revision revision timestamp
|
||||
*/
|
||||
public static function rollback($file, $revision) {
|
||||
|
||||
if(\OCP\Config::getSystemValue('files_versions', Storage::DEFAULTENABLED)=='true') {
|
||||
// add expected leading slash
|
||||
$file = '/' . ltrim($file, '/');
|
||||
list($uid, $filename) = self::getUidAndFilename($file);
|
||||
$users_view = new \OC\Files\View('/'.$uid);
|
||||
$files_view = new \OC\Files\View('/'.\OCP\User::getUser().'/files');
|
||||
@@ -270,12 +295,11 @@ class Storage {
|
||||
}
|
||||
|
||||
// rollback
|
||||
if( @$users_view->rename('files_versions'.$filename.'.v'.$revision, 'files'.$filename) ) {
|
||||
if (self::copyFileContents($users_view, 'files_versions' . $filename . '.v' . $revision, 'files' . $filename)) {
|
||||
$files_view->touch($file, $revision);
|
||||
Storage::expire($file);
|
||||
return true;
|
||||
|
||||
}else if ( $versionCreated ) {
|
||||
} else if ($versionCreated) {
|
||||
self::deleteVersion($users_view, $version);
|
||||
}
|
||||
}
|
||||
@@ -283,6 +307,36 @@ class Storage {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream copy file contents from $path1 to $path2
|
||||
*
|
||||
* @param \OC\Files\View $view view to use for copying
|
||||
* @param string $path1 source file to copy
|
||||
* @param string $path2 target file
|
||||
*
|
||||
* @return bool true for success, false otherwise
|
||||
*/
|
||||
private static function copyFileContents($view, $path1, $path2) {
|
||||
list($storage1, $internalPath1) = $view->resolvePath($path1);
|
||||
list($storage2, $internalPath2) = $view->resolvePath($path2);
|
||||
|
||||
if ($storage1 === $storage2) {
|
||||
return $storage1->rename($internalPath1, $internalPath2);
|
||||
}
|
||||
$source = $storage1->fopen($internalPath1, 'r');
|
||||
$target = $storage2->fopen($internalPath2, 'w');
|
||||
// FIXME: might need to use part file to avoid concurrent writes
|
||||
// (this would be an issue anyway when renaming/restoring cross-storage)
|
||||
list(, $result) = \OC_Helper::streamCopy($source, $target);
|
||||
fclose($source);
|
||||
fclose($target);
|
||||
|
||||
if ($result !== false) {
|
||||
$storage1->unlink($internalPath1);
|
||||
}
|
||||
|
||||
return ($result !== false);
|
||||
}
|
||||
|
||||
/**
|
||||
* get a list of all available versions of a file in descending chronological order
|
||||
@@ -574,8 +628,11 @@ class Storage {
|
||||
}
|
||||
|
||||
/**
|
||||
* create recursively missing directories
|
||||
* @param string $filename $path to a file
|
||||
* Create recursively missing directories inside of files_versions
|
||||
* that match the given path to a file.
|
||||
*
|
||||
* @param string $filename $path to a file, relative to the user's
|
||||
* "files" folder
|
||||
* @param \OC\Files\View $view view on data/user/
|
||||
*/
|
||||
private static function createMissingDirectories($filename, $view) {
|
||||
|
||||
@@ -22,6 +22,8 @@
|
||||
|
||||
require_once __DIR__ . '/../appinfo/app.php';
|
||||
|
||||
use OC\Files\Storage\Temporary;
|
||||
|
||||
/**
|
||||
* Class Test_Files_versions
|
||||
* this class provide basic files versions test
|
||||
@@ -70,7 +72,10 @@ class Test_Files_Versioning extends \Test\TestCase {
|
||||
}
|
||||
|
||||
protected function tearDown() {
|
||||
$this->rootView->deleteAll(self::USERS_VERSIONS_ROOT);
|
||||
$this->rootView->deleteAll(self::TEST_VERSIONS_USER . '/files/');
|
||||
$this->rootView->deleteAll(self::TEST_VERSIONS_USER2 . '/files/');
|
||||
$this->rootView->deleteAll(self::TEST_VERSIONS_USER . '/files_versions/');
|
||||
$this->rootView->deleteAll(self::TEST_VERSIONS_USER2 . '/files_versions/');
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
@@ -248,9 +253,6 @@ class Test_Files_Versioning extends \Test\TestCase {
|
||||
|
||||
$this->assertTrue($this->rootView->file_exists($v1Renamed));
|
||||
$this->assertTrue($this->rootView->file_exists($v2Renamed));
|
||||
|
||||
//cleanup
|
||||
\OC\Files\Filesystem::unlink('test2.txt');
|
||||
}
|
||||
|
||||
public function testRenameInSharedFolder() {
|
||||
@@ -292,9 +294,131 @@ class Test_Files_Versioning extends \Test\TestCase {
|
||||
|
||||
$this->assertTrue($this->rootView->file_exists($v1Renamed));
|
||||
$this->assertTrue($this->rootView->file_exists($v2Renamed));
|
||||
}
|
||||
|
||||
//cleanup
|
||||
\OC\Files\Filesystem::unlink('/folder1/folder2/test.txt');
|
||||
public function testMoveFolder() {
|
||||
|
||||
\OC\Files\Filesystem::mkdir('folder1');
|
||||
\OC\Files\Filesystem::mkdir('folder2');
|
||||
\OC\Files\Filesystem::file_put_contents('folder1/test.txt', 'test file');
|
||||
|
||||
$t1 = time();
|
||||
// second version is two weeks older, this way we make sure that no
|
||||
// version will be expired
|
||||
$t2 = $t1 - 60 * 60 * 24 * 14;
|
||||
|
||||
// create some versions
|
||||
$this->rootView->mkdir(self::USERS_VERSIONS_ROOT . '/folder1');
|
||||
$v1 = self::USERS_VERSIONS_ROOT . '/folder1/test.txt.v' . $t1;
|
||||
$v2 = self::USERS_VERSIONS_ROOT . '/folder1/test.txt.v' . $t2;
|
||||
$v1Renamed = self::USERS_VERSIONS_ROOT . '/folder2/folder1/test.txt.v' . $t1;
|
||||
$v2Renamed = self::USERS_VERSIONS_ROOT . '/folder2/folder1/test.txt.v' . $t2;
|
||||
|
||||
$this->rootView->file_put_contents($v1, 'version1');
|
||||
$this->rootView->file_put_contents($v2, 'version2');
|
||||
|
||||
// execute rename hook of versions app
|
||||
\OC\Files\Filesystem::rename('folder1', 'folder2/folder1');
|
||||
|
||||
$this->assertFalse($this->rootView->file_exists($v1));
|
||||
$this->assertFalse($this->rootView->file_exists($v2));
|
||||
|
||||
$this->assertTrue($this->rootView->file_exists($v1Renamed));
|
||||
$this->assertTrue($this->rootView->file_exists($v2Renamed));
|
||||
}
|
||||
|
||||
|
||||
public function testMoveFileIntoSharedFolderAsRecipient() {
|
||||
|
||||
\OC\Files\Filesystem::mkdir('folder1');
|
||||
$fileInfo = \OC\Files\Filesystem::getFileInfo('folder1');
|
||||
|
||||
\OCP\Share::shareItem(
|
||||
'folder',
|
||||
$fileInfo['fileid'],
|
||||
\OCP\Share::SHARE_TYPE_USER,
|
||||
self::TEST_VERSIONS_USER2,
|
||||
\OCP\Constants::PERMISSION_ALL
|
||||
);
|
||||
|
||||
self::loginHelper(self::TEST_VERSIONS_USER2);
|
||||
$versionsFolder2 = '/' . self::TEST_VERSIONS_USER2 . '/files_versions';
|
||||
\OC\Files\Filesystem::file_put_contents('test.txt', 'test file');
|
||||
|
||||
$t1 = time();
|
||||
// second version is two weeks older, this way we make sure that no
|
||||
// version will be expired
|
||||
$t2 = $t1 - 60 * 60 * 24 * 14;
|
||||
|
||||
$this->rootView->mkdir($versionsFolder2);
|
||||
// create some versions
|
||||
$v1 = $versionsFolder2 . '/test.txt.v' . $t1;
|
||||
$v2 = $versionsFolder2 . '/test.txt.v' . $t2;
|
||||
|
||||
$this->rootView->file_put_contents($v1, 'version1');
|
||||
$this->rootView->file_put_contents($v2, 'version2');
|
||||
|
||||
// move file into the shared folder as recipient
|
||||
\OC\Files\Filesystem::rename('/test.txt', '/folder1/test.txt');
|
||||
|
||||
$this->assertFalse($this->rootView->file_exists($v1));
|
||||
$this->assertFalse($this->rootView->file_exists($v2));
|
||||
|
||||
self::loginHelper(self::TEST_VERSIONS_USER);
|
||||
|
||||
$versionsFolder1 = '/' . self::TEST_VERSIONS_USER . '/files_versions';
|
||||
|
||||
$v1Renamed = $versionsFolder1 . '/folder1/test.txt.v' . $t1;
|
||||
$v2Renamed = $versionsFolder1 . '/folder1/test.txt.v' . $t2;
|
||||
|
||||
$this->assertTrue($this->rootView->file_exists($v1Renamed));
|
||||
$this->assertTrue($this->rootView->file_exists($v2Renamed));
|
||||
}
|
||||
|
||||
public function testMoveFolderIntoSharedFolderAsRecipient() {
|
||||
|
||||
\OC\Files\Filesystem::mkdir('folder1');
|
||||
$fileInfo = \OC\Files\Filesystem::getFileInfo('folder1');
|
||||
|
||||
\OCP\Share::shareItem(
|
||||
'folder',
|
||||
$fileInfo['fileid'],
|
||||
\OCP\Share::SHARE_TYPE_USER,
|
||||
self::TEST_VERSIONS_USER2,
|
||||
\OCP\Constants::PERMISSION_ALL
|
||||
);
|
||||
|
||||
self::loginHelper(self::TEST_VERSIONS_USER2);
|
||||
$versionsFolder2 = '/' . self::TEST_VERSIONS_USER2 . '/files_versions';
|
||||
\OC\Files\Filesystem::mkdir('folder2');
|
||||
\OC\Files\Filesystem::file_put_contents('folder2/test.txt', 'test file');
|
||||
|
||||
$t1 = time();
|
||||
// second version is two weeks older, this way we make sure that no
|
||||
// version will be expired
|
||||
$t2 = $t1 - 60 * 60 * 24 * 14;
|
||||
|
||||
$this->rootView->mkdir($versionsFolder2);
|
||||
$this->rootView->mkdir($versionsFolder2 . '/folder2');
|
||||
// create some versions
|
||||
$v1 = $versionsFolder2 . '/folder2/test.txt.v' . $t1;
|
||||
$v2 = $versionsFolder2 . '/folder2/test.txt.v' . $t2;
|
||||
|
||||
$this->rootView->file_put_contents($v1, 'version1');
|
||||
$this->rootView->file_put_contents($v2, 'version2');
|
||||
|
||||
// move file into the shared folder as recipient
|
||||
\OC\Files\Filesystem::rename('/folder2', '/folder1/folder2');
|
||||
|
||||
self::loginHelper(self::TEST_VERSIONS_USER);
|
||||
|
||||
$versionsFolder1 = '/' . self::TEST_VERSIONS_USER . '/files_versions';
|
||||
|
||||
$v1Renamed = $versionsFolder1 . '/folder1/folder2/test.txt.v' . $t1;
|
||||
$v2Renamed = $versionsFolder1 . '/folder1/folder2/test.txt.v' . $t2;
|
||||
|
||||
$this->assertTrue($this->rootView->file_exists($v1Renamed));
|
||||
$this->assertTrue($this->rootView->file_exists($v2Renamed));
|
||||
}
|
||||
|
||||
public function testRenameSharedFile() {
|
||||
@@ -335,9 +459,6 @@ class Test_Files_Versioning extends \Test\TestCase {
|
||||
|
||||
$this->assertFalse($this->rootView->file_exists($v1Renamed));
|
||||
$this->assertFalse($this->rootView->file_exists($v2Renamed));
|
||||
|
||||
//cleanup
|
||||
\OC\Files\Filesystem::unlink('/test.txt');
|
||||
}
|
||||
|
||||
public function testCopy() {
|
||||
@@ -366,10 +487,6 @@ class Test_Files_Versioning extends \Test\TestCase {
|
||||
|
||||
$this->assertTrue($this->rootView->file_exists($v1Copied));
|
||||
$this->assertTrue($this->rootView->file_exists($v2Copied));
|
||||
|
||||
//cleanup
|
||||
\OC\Files\Filesystem::unlink('test.txt');
|
||||
\OC\Files\Filesystem::unlink('test2.txt');
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -406,6 +523,100 @@ class Test_Files_Versioning extends \Test\TestCase {
|
||||
$this->rootView->deleteAll(self::USERS_VERSIONS_ROOT . '/subfolder');
|
||||
}
|
||||
|
||||
public function testRestoreSameStorage() {
|
||||
\OC\Files\Filesystem::mkdir('sub');
|
||||
$this->doTestRestore();
|
||||
}
|
||||
|
||||
public function testRestoreCrossStorage() {
|
||||
$storage2 = new Temporary(array());
|
||||
\OC\Files\Filesystem::mount($storage2, array(), self::TEST_VERSIONS_USER . '/files/sub');
|
||||
|
||||
$this->doTestRestore();
|
||||
}
|
||||
|
||||
private function doTestRestore() {
|
||||
$filePath = self::TEST_VERSIONS_USER . '/files/sub/test.txt';
|
||||
$this->rootView->file_put_contents($filePath, 'test file');
|
||||
|
||||
$t0 = $this->rootView->filemtime($filePath);
|
||||
|
||||
// not exactly the same timestamp as the file
|
||||
$t1 = time() - 60;
|
||||
// second version is two weeks older
|
||||
$t2 = $t1 - 60 * 60 * 24 * 14;
|
||||
|
||||
// create some versions
|
||||
$v1 = self::USERS_VERSIONS_ROOT . '/sub/test.txt.v' . $t1;
|
||||
$v2 = self::USERS_VERSIONS_ROOT . '/sub/test.txt.v' . $t2;
|
||||
|
||||
$this->rootView->mkdir(self::USERS_VERSIONS_ROOT . '/sub');
|
||||
$this->rootView->file_put_contents($v1, 'version1');
|
||||
$this->rootView->file_put_contents($v2, 'version2');
|
||||
|
||||
$oldVersions = \OCA\Files_Versions\Storage::getVersions(
|
||||
self::TEST_VERSIONS_USER, '/sub/test.txt'
|
||||
);
|
||||
|
||||
$this->assertCount(2, $oldVersions);
|
||||
|
||||
$this->assertEquals('test file', $this->rootView->file_get_contents($filePath));
|
||||
$info1 = $this->rootView->getFileInfo($filePath);
|
||||
|
||||
\OCA\Files_Versions\Storage::rollback('sub/test.txt', $t2);
|
||||
|
||||
$this->assertEquals('version2', $this->rootView->file_get_contents($filePath));
|
||||
$info2 = $this->rootView->getFileInfo($filePath);
|
||||
|
||||
$this->assertNotEquals(
|
||||
$info2['etag'],
|
||||
$info1['etag'],
|
||||
'Etag must change after rolling back version'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$info2['fileid'],
|
||||
$info1['fileid'],
|
||||
'File id must not change after rolling back version'
|
||||
);
|
||||
$this->assertEquals(
|
||||
$info2['mtime'],
|
||||
$t2,
|
||||
'Restored file has mtime from version'
|
||||
);
|
||||
|
||||
$newVersions = \OCA\Files_Versions\Storage::getVersions(
|
||||
self::TEST_VERSIONS_USER, '/sub/test.txt'
|
||||
);
|
||||
|
||||
$this->assertTrue(
|
||||
$this->rootView->file_exists(self::USERS_VERSIONS_ROOT . '/sub/test.txt.v' . $t0),
|
||||
'A version file was created for the file before restoration'
|
||||
);
|
||||
$this->assertTrue(
|
||||
$this->rootView->file_exists($v1),
|
||||
'Untouched version file is still there'
|
||||
);
|
||||
$this->assertFalse(
|
||||
$this->rootView->file_exists($v2),
|
||||
'Restored version file gone from files_version folder'
|
||||
);
|
||||
|
||||
$this->assertCount(2, $newVersions, 'Additional version created');
|
||||
|
||||
$this->assertTrue(
|
||||
isset($newVersions[$t0 . '#' . 'test.txt']),
|
||||
'A version was created for the file before restoration'
|
||||
);
|
||||
$this->assertTrue(
|
||||
isset($newVersions[$t1 . '#' . 'test.txt']),
|
||||
'Untouched version is still there'
|
||||
);
|
||||
$this->assertFalse(
|
||||
isset($newVersions[$t2 . '#' . 'test.txt']),
|
||||
'Restored version is not in the list any more'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $user
|
||||
* @param bool $create
|
||||
|
||||
@@ -296,6 +296,10 @@ class Users {
|
||||
if(strtolower($group) == 'admin') {
|
||||
return new OC_OCS_Result(null, 103, 'Cannot create subadmins for admin group');
|
||||
}
|
||||
// We cannot be subadmin twice
|
||||
if (OC_Subadmin::isSubAdminOfGroup($user, $group)) {
|
||||
return new OC_OCS_Result(null, 100);
|
||||
}
|
||||
// Go
|
||||
if(OC_Subadmin::createSubAdmin($user, $group)) {
|
||||
return new OC_OCS_Result(null, 100);
|
||||
|
||||
@@ -767,4 +767,29 @@ class UsersTest extends TestCase {
|
||||
$this->assertFalse($result->succeeded());
|
||||
$this->assertEquals(101, $result->getStatusCode());
|
||||
}
|
||||
|
||||
public function testSubAdminOfGroupAlreadySubAdmin() {
|
||||
$user1 = $this->generateUsers();
|
||||
$user2 = $this->generateUsers();
|
||||
\OC_User::setUserId($user1);
|
||||
\OC_Group::addToGroup($user1, 'admin');
|
||||
$group1 = $this->getUniqueID();
|
||||
\OC_Group::createGroup($group1);
|
||||
|
||||
//Make user2 subadmin of group1
|
||||
$_POST['groupid'] = $group1;
|
||||
$result = \OCA\provisioning_api\Users::addSubAdmin([
|
||||
'userid' => $user2,
|
||||
]);
|
||||
$this->assertInstanceOf('OC_OCS_Result', $result);
|
||||
$this->assertTrue($result->succeeded());
|
||||
|
||||
//Make user2 subadmin of group1 again
|
||||
$_POST['groupid'] = $group1;
|
||||
$result = \OCA\provisioning_api\Users::addSubAdmin([
|
||||
'userid' => $user2,
|
||||
]);
|
||||
$this->assertInstanceOf('OC_OCS_Result', $result);
|
||||
$this->assertTrue($result->succeeded());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,7 +85,7 @@ switch($action) {
|
||||
exit;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
\OCP\JSON::error(array('message' => $e->getMessage()));
|
||||
\OCP\JSON::error(array('message' => $e->getMessage(), 'code' => $e->getCode()));
|
||||
exit;
|
||||
}
|
||||
\OCP\JSON::error();
|
||||
|
||||
@@ -17,7 +17,6 @@ A user logs into ownCloud with their LDAP or AD credentials, and is granted acce
|
||||
<documentation>
|
||||
<admin>admin-ldap</admin>
|
||||
</documentation>
|
||||
<ocsid>166061</ocsid>
|
||||
<dependencies>
|
||||
<lib>ldap</lib>
|
||||
</dependencies>
|
||||
|
||||
@@ -1 +1 @@
|
||||
0.5.0
|
||||
0.5.1
|
||||
|
||||
@@ -49,13 +49,15 @@
|
||||
}
|
||||
|
||||
#ldapWizard1 .hostPortCombinator div span {
|
||||
width: 7%;
|
||||
display: table-cell;
|
||||
width: 14.5%;
|
||||
display: inline-block;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
#ldapWizard1 .host {
|
||||
width: 96.5% !important;
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
margin-right: 0;
|
||||
}
|
||||
|
||||
.tableCellInput {
|
||||
|
||||
@@ -630,7 +630,7 @@ class GROUP_LDAP extends BackendUtility implements \OCP\GroupInterface {
|
||||
}
|
||||
$maxGroups = 100000; // limit max results (just for safety reasons)
|
||||
if ($limit > -1) {
|
||||
$overallLimit = min($limit, $maxGroups);
|
||||
$overallLimit = min($limit + $offset, $maxGroups);
|
||||
} else {
|
||||
$overallLimit = $maxGroups;
|
||||
}
|
||||
|
||||
@@ -351,7 +351,7 @@ var LdapWizard = {
|
||||
encodeURIComponent($('#ldap_serverconfig_chooser').val());
|
||||
|
||||
LdapWizard.showSpinner(spinnerID);
|
||||
var request = LdapWizard.ajax(param,
|
||||
LdapWizard.ajax(param,
|
||||
function(result) {
|
||||
LdapWizard.applyChanges(result);
|
||||
LdapWizard.hideSpinner(spinnerID);
|
||||
@@ -360,7 +360,7 @@ var LdapWizard = {
|
||||
}
|
||||
},
|
||||
function (result) {
|
||||
OC.Notification.show('Counting the entries failed with, ' + result.message);
|
||||
OC.Notification.showTemporary('Counting the entries failed with: ' + result.message);
|
||||
LdapWizard.hideSpinner(spinnerID);
|
||||
if(!_.isUndefined(doneCallback)) {
|
||||
doneCallback(method);
|
||||
@@ -371,11 +371,17 @@ var LdapWizard = {
|
||||
},
|
||||
|
||||
countGroups: function(doneCallback) {
|
||||
LdapWizard._countThings('countGroups', '#ldap_group_count', doneCallback);
|
||||
var groupFilter = $('#ldap_group_filter').val();
|
||||
if(!_.isEmpty(groupFilter)) {
|
||||
LdapWizard._countThings('countGroups', '#ldap_group_count', doneCallback);
|
||||
}
|
||||
},
|
||||
|
||||
countUsers: function(doneCallback) {
|
||||
LdapWizard._countThings('countUsers', '#ldap_user_count', doneCallback);
|
||||
var userFilter = $('#ldap_userlist_filter').val();
|
||||
if(!_.isEmpty(userFilter)) {
|
||||
LdapWizard._countThings('countUsers', '#ldap_user_count', doneCallback);
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -649,7 +655,7 @@ var LdapWizard = {
|
||||
header: false,
|
||||
selectedList: 9,
|
||||
noneSelectedText: caption,
|
||||
click: function(event, ui) {
|
||||
close: function(event, ui) {
|
||||
LdapWizard.saveMultiSelect(id,
|
||||
$('#'+id).multiselect("getChecked"));
|
||||
}
|
||||
@@ -836,7 +842,12 @@ var LdapWizard = {
|
||||
for(var i = 0; i < resultObj.length; i++) {
|
||||
values = values + "\n" + resultObj[i].value;
|
||||
}
|
||||
LdapWizard._save($('#'+originalObj)[0], $.trim(values));
|
||||
LdapWizard._save($('#'+originalObj)[0], $.trim(values)).then(
|
||||
_.bind(this._updateAfterSavingMultiSelect, this, originalObj, resultObj)
|
||||
);
|
||||
},
|
||||
|
||||
_updateAfterSavingMultiSelect: function(originalObj, resultObj) {
|
||||
var $multiSelectObj = $('#'+originalObj);
|
||||
var updateCount = !$multiSelectObj.multiselect("isOpen");
|
||||
var applyUpdateOnCloseToFilter;
|
||||
@@ -873,6 +884,15 @@ var LdapWizard = {
|
||||
},
|
||||
|
||||
saveProcesses: 0,
|
||||
|
||||
/**
|
||||
* Saves the config value of a given input/select field
|
||||
*
|
||||
* @param {Object} object DOM object for the field
|
||||
* @param {String} value value to save
|
||||
*
|
||||
* @return {Promise} promise from the save ajax call
|
||||
*/
|
||||
_save: function(object, value) {
|
||||
$('#ldap .ldap_saving').removeClass('hidden');
|
||||
LdapWizard.saveProcesses += 1;
|
||||
@@ -882,7 +902,8 @@ var LdapWizard = {
|
||||
'&action=save'+
|
||||
'&ldap_serverconfig_chooser='+$('#ldap_serverconfig_chooser').val();
|
||||
|
||||
$.post(
|
||||
|
||||
return $.post(
|
||||
OC.filePath('user_ldap','ajax','wizard.php'),
|
||||
param,
|
||||
function(result) {
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
<?php
|
||||
$TRANSLATIONS = array(
|
||||
"_%s group found_::_%s groups found_" => array("",""),
|
||||
"_%s user found_::_%s users found_" => array("","")
|
||||
);
|
||||
$PLURAL_FORMS = "nplurals=2; plural=(n != 1);";
|
||||
@@ -1372,7 +1372,8 @@ class Access extends LDAPUtility implements user\IUserTools {
|
||||
* @return void
|
||||
*/
|
||||
private function setPagedResultCookie($base, $filter, $limit, $offset, $cookie) {
|
||||
if(!empty($cookie)) {
|
||||
// allow '0' for 389ds
|
||||
if(!empty($cookie) || $cookie === '0') {
|
||||
$cacheKey = 'lc' . crc32($base) . '-' . crc32($filter) . '-' .intval($limit) . '-' . intval($offset);
|
||||
$this->cookies[$cacheKey] = $cookie;
|
||||
$this->lastCookie = $cookie;
|
||||
@@ -1410,11 +1411,12 @@ class Access extends LDAPUtility implements user\IUserTools {
|
||||
foreach($bases as $base) {
|
||||
|
||||
$cookie = $this->getPagedResultCookie($base, $filter, $limit, $offset);
|
||||
if(empty($cookie) && ($offset > 0)) {
|
||||
if(empty($cookie) && $cookie !== "0" && ($offset > 0)) {
|
||||
// no cookie known, although the offset is not 0. Maybe cache run out. We need
|
||||
// to start all over *sigh* (btw, Dear Reader, did you know LDAP paged
|
||||
// searching was designed by MSFT?)
|
||||
// Lukas: No, but thanks to reading that source I finally know!
|
||||
// '0' is valid, because 389ds
|
||||
$reOffset = ($offset - $limit) < 0 ? 0 : $offset - $limit;
|
||||
//a bit recursive, $offset of 0 is the exit
|
||||
\OCP\Util::writeLog('user_ldap', 'Looking for cookie L/O '.$limit.'/'.$reOffset, \OCP\Util::INFO);
|
||||
@@ -1422,7 +1424,8 @@ class Access extends LDAPUtility implements user\IUserTools {
|
||||
$cookie = $this->getPagedResultCookie($base, $filter, $limit, $offset);
|
||||
//still no cookie? obviously, the server does not like us. Let's skip paging efforts.
|
||||
//TODO: remember this, probably does not change in the next request...
|
||||
if(empty($cookie)) {
|
||||
if(empty($cookie) && $cookie !== '0') {
|
||||
// '0' is valid, because 389ds
|
||||
$cookie = null;
|
||||
}
|
||||
}
|
||||
@@ -1443,6 +1446,17 @@ class Access extends LDAPUtility implements user\IUserTools {
|
||||
}
|
||||
|
||||
}
|
||||
} else if($this->connection->hasPagedResultSupport && $limit === 0) {
|
||||
// a search without limit was requested. However, if we do use
|
||||
// Paged Search once, we always must do it. This requires us to
|
||||
// initialize it with the configured page size.
|
||||
$this->abandonPagedSearch();
|
||||
// in case someone set it to 0 … use 500, otherwise no results will
|
||||
// be returned.
|
||||
$pageSize = intval($this->connection->ldapPagingSize) > 0 ? intval($this->connection->ldapPagingSize) : 500;
|
||||
$pagedSearchOK = $this->ldap->controlPagedResult(
|
||||
$this->connection->getConnectionResource(), $pageSize, false, ''
|
||||
);
|
||||
}
|
||||
|
||||
return $pagedSearchOK;
|
||||
|
||||
@@ -23,8 +23,10 @@
|
||||
|
||||
namespace OCA\user_ldap\lib;
|
||||
|
||||
//magic properties (incomplete)
|
||||
use OC\ServerNotAvailableException;
|
||||
|
||||
/**
|
||||
* magic properties (incomplete)
|
||||
* responsible for LDAP connections in context with the provided configuration
|
||||
*
|
||||
* @property string ldapUserFilter
|
||||
@@ -46,7 +48,7 @@ class Connection extends LDAPUtility {
|
||||
//cache handler
|
||||
protected $cache;
|
||||
|
||||
//settings handler
|
||||
/** @var Configuration settings handler **/
|
||||
protected $configuration;
|
||||
|
||||
protected $doNotValidate = false;
|
||||
@@ -159,7 +161,8 @@ class Connection extends LDAPUtility {
|
||||
$this->establishConnection();
|
||||
}
|
||||
if(is_null($this->ldapConnectionRes)) {
|
||||
\OCP\Util::writeLog('user_ldap', 'Connection could not be established', \OCP\Util::ERROR);
|
||||
\OCP\Util::writeLog('user_ldap', 'No LDAP Connection to server ' . $this->configuration->ldapHost, \OCP\Util::ERROR);
|
||||
throw new ServerNotAvailableException('Connection to LDAP server could not be established');
|
||||
}
|
||||
return $this->ldapConnectionRes;
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
|
||||
namespace OCA\user_ldap\lib;
|
||||
|
||||
use OC\ServerNotAvailableException;
|
||||
|
||||
class LDAP implements ILDAPWrapper {
|
||||
protected $curFunc = '';
|
||||
protected $curArgs = array();
|
||||
@@ -280,6 +282,8 @@ class LDAP implements ILDAPWrapper {
|
||||
//for now
|
||||
} else if ($errorCode === 10) {
|
||||
//referrals, we switch them off, but then there is AD :)
|
||||
} else if ($errorCode === -1) {
|
||||
throw new ServerNotAvailableException('Lost connection to LDAP server.');
|
||||
} else {
|
||||
\OCP\Util::writeLog('user_ldap',
|
||||
'LDAP error '.$errorMsg.' (' .
|
||||
|
||||
@@ -110,8 +110,8 @@ class Manager {
|
||||
$user = new User($uid, $dn, $this->access, $this->ocConfig,
|
||||
$this->ocFilesystem, clone $this->image, $this->ocLog,
|
||||
$this->avatarManager);
|
||||
$users['byDN'][$dn] = $user;
|
||||
$users['byUid'][$uid] = $user;
|
||||
$this->users['byDN'][$dn] = $user;
|
||||
$this->users['byUid'][$uid] = $user;
|
||||
return $user;
|
||||
}
|
||||
|
||||
@@ -150,6 +150,11 @@ class Manager {
|
||||
$this->access->getUserMapper());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief returns a User object by it's ownCloud username
|
||||
* @param string the DN or username of the user
|
||||
* @return \OCA\user_ldap\lib\user\User|\OCA\user_ldap\lib\user\OfflineUser|null
|
||||
*/
|
||||
protected function createInstancyByUserName($id) {
|
||||
//most likely a uid. Check whether it is a deleted user
|
||||
if($this->isDeletedUser($id)) {
|
||||
@@ -159,13 +164,14 @@ class Manager {
|
||||
if($dn !== false) {
|
||||
return $this->createAndCache($dn, $id);
|
||||
}
|
||||
throw new \Exception('Could not create User instance');
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief returns a User object by it's DN or ownCloud username
|
||||
* @param string the DN or username of the user
|
||||
* @return \OCA\user_ldap\lib\user\User|\OCA\user_ldap\lib\user\OfflineUser|null
|
||||
* @throws \Exception when connection could not be established
|
||||
*/
|
||||
public function get($id) {
|
||||
$this->checkAccess();
|
||||
@@ -182,12 +188,7 @@ class Manager {
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
$user = $this->createInstancyByUserName($id);
|
||||
return $user;
|
||||
} catch (\Exception $e) {
|
||||
return null;
|
||||
}
|
||||
return $this->createInstancyByUserName($id);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@
|
||||
|
||||
namespace OCA\user_ldap\lib;
|
||||
|
||||
use OC\ServerNotAvailableException;
|
||||
|
||||
class Wizard extends LDAPUtility {
|
||||
static protected $l;
|
||||
protected $access;
|
||||
@@ -1001,18 +1003,27 @@ class Wizard extends LDAPUtility {
|
||||
$this->ldap->setOption($cr, LDAP_OPT_PROTOCOL_VERSION, 3);
|
||||
$this->ldap->setOption($cr, LDAP_OPT_REFERRALS, 0);
|
||||
$this->ldap->setOption($cr, LDAP_OPT_NETWORK_TIMEOUT, self::LDAP_NW_TIMEOUT);
|
||||
if($tls) {
|
||||
$isTlsWorking = @$this->ldap->startTls($cr);
|
||||
if(!$isTlsWorking) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
\OCP\Util::writeLog('user_ldap', 'Wiz: Attemping to Bind ', \OCP\Util::DEBUG);
|
||||
//interesting part: do the bind!
|
||||
$login = $this->ldap->bind($cr,
|
||||
$this->configuration->ldapAgentName,
|
||||
$this->configuration->ldapAgentPassword);
|
||||
try {
|
||||
if($tls) {
|
||||
$isTlsWorking = @$this->ldap->startTls($cr);
|
||||
if(!$isTlsWorking) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
\OCP\Util::writeLog('user_ldap', 'Wiz: Attemping to Bind ', \OCP\Util::DEBUG);
|
||||
//interesting part: do the bind!
|
||||
$login = $this->ldap->bind($cr,
|
||||
$this->configuration->ldapAgentName,
|
||||
$this->configuration->ldapAgentPassword
|
||||
);
|
||||
$errNo = $this->ldap->errno($cr);
|
||||
$error = ldap_error($cr);
|
||||
$this->ldap->unbind($cr);
|
||||
} catch(ServerNotAvailableException $e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if($login === true) {
|
||||
$this->ldap->unbind($cr);
|
||||
@@ -1023,9 +1034,6 @@ class Wizard extends LDAPUtility {
|
||||
return true;
|
||||
}
|
||||
|
||||
$errNo = $this->ldap->errno($cr);
|
||||
$error = ldap_error($cr);
|
||||
$this->ldap->unbind($cr);
|
||||
if($errNo === -1 || ($errNo === 2 && $ncc)) {
|
||||
//host, port or TLS wrong
|
||||
return false;
|
||||
|
||||
@@ -293,4 +293,18 @@ class Test_Group_Ldap extends \Test\TestCase {
|
||||
$groupBackend->inGroup($uid, $gid);
|
||||
}
|
||||
|
||||
public function testGetGroupsWithOffset() {
|
||||
$access = $this->getAccessMock();
|
||||
$this->enableGroups($access);
|
||||
|
||||
$access->expects($this->once())
|
||||
->method('ownCloudGroupNames')
|
||||
->will($this->returnValue(array('group1', 'group2')));
|
||||
|
||||
$groupBackend = new GroupLDAP($access);
|
||||
$groups = $groupBackend->getGroups('', 2, 2);
|
||||
|
||||
$this->assertSame(2, count($groups));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user